1 /*
2  * NPlot - A charting library for .NET
3  *
4  * Windows.PlotSurface2d.cs
5  * Copyright (C) 2003-2006 Matt Howlett and others.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without modification,
9  * are permitted provided that the following conditions are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright notice, this
12  *    list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright notice,
14  *    this list of conditions and the following disclaimer in the documentation
15  *    and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
21  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
26  * OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 using System;
30 using System.Collections;
31 using System.ComponentModel;
32 using System.Drawing;
33 using System.Drawing.Drawing2D;
34 using System.Drawing.Printing;
35 using System.Text;
36 using System.Windows.Forms;
37 
38 namespace NPlot.Windows
39 {
40     /// <summary>
41     /// A Windows.Forms PlotSurface2D control.
42     /// </summary>
43     /// <remarks>
44     /// Unfortunately it's not possible to derive from both Control and NPlot.PlotSurface2D.
45     /// </remarks>
46     [ToolboxBitmap(typeof (PlotSurface2D), "PlotSurface2D.ico")]
47     public class PlotSurface2D : Control, IPlotSurface2D, ISurface
48     {
49         /// <summary>
50         /// This is the signature of the function used for InteractionOccurred events.
51         /// TODO: expand this to include information about the event.
52         /// </summary>
53         /// <param name="sender"></param>
InteractionHandler(object sender)54         public delegate void InteractionHandler(object sender);
55 
56         /// <summary>
57         /// This is the signature of the function used for PreRefresh events.
58         /// </summary>
59         /// <param name="sender"></param>
PreRefreshHandler(object sender)60         public delegate void PreRefreshHandler(object sender);
61 
62         private readonly ArrayList interactions_ = new ArrayList();
63         private readonly NPlot.PlotSurface2D ps_;
64         private IContainer components;
65 
66         private ToolTip coordinates_;
67         private KeyEventArgs lastKeyEventArgs_;
68         private PlotContextMenu rightMenu_;
69 
70         //private ArrayList selectedObjects_;
71 
72         private Axis xAxis1ZoomCache_;
73         private Axis xAxis2ZoomCache_;
74         private Axis yAxis1ZoomCache_;
75         private Axis yAxis2ZoomCache_;
76 
77         /// <summary>
78         /// Default constructor.
79         /// </summary>
PlotSurface2D()80         public PlotSurface2D()
81         {
82             // This call is required by the Windows.Forms Form Designer.
83             InitializeComponent();
84 
85             // double buffer, and update when resize.
86             base.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
87             base.SetStyle(ControlStyles.DoubleBuffer, true);
88             base.SetStyle(ControlStyles.UserPaint, true);
89             base.ResizeRedraw = true;
90 
91             ps_ = new NPlot.PlotSurface2D();
92 
93             InteractionOccured += OnInteractionOccured;
94             PreRefresh += OnPreRefresh;
95         }
96 
97         /// <summary>
98         /// Flag to display a coordinates in a tooltip.
99         /// </summary>
100         [
101             Category("PlotSurface2D"),
102             Description("Whether or not to show coordinates in a tool tip when the mouse hovers above the plot area."),
103             Browsable(true),
104             Bindable(true)
105         ]
106         public bool ShowCoordinates
107         {
108             get { return coordinates_.Active; }
109             set { coordinates_.Active = value; }
110         }
111 
112         /// <summary>
113         /// The physical XAxis1 that was last drawn.
114         /// </summary>
115         [
116             Browsable(false)
117         ]
118         public PhysicalAxis PhysicalXAxis1Cache
119         {
120             get { return ps_.PhysicalXAxis1Cache; }
121         }
122 
123         /// <summary>
124         /// The physical YAxis1 that was last drawn.
125         /// </summary>
126         [
127             Browsable(false)
128         ]
129         public PhysicalAxis PhysicalYAxis1Cache
130         {
131             get { return ps_.PhysicalYAxis1Cache; }
132         }
133 
134         /// <summary>
135         /// The physical XAxis2 that was last drawn.
136         /// </summary>
137         [
138             Browsable(false)
139         ]
140         public PhysicalAxis PhysicalXAxis2Cache
141         {
142             get { return ps_.PhysicalXAxis2Cache; }
143         }
144 
145         /// <summary>
146         /// The physical YAxis2 that was last drawn.
147         /// </summary>
148         [
149             Browsable(false)
150         ]
151         public PhysicalAxis PhysicalYAxis2Cache
152         {
153             get { return ps_.PhysicalYAxis2Cache; }
154         }
155 
156         /// <summary>
157         /// When true, tool tip will display x value as a DateTime. Quick hack - this will probably be
158         /// changed at some point.
159         /// </summary>
160         [Bindable(true), Browsable(true), Category("PlotSurface2D"),
161          Description("When true, tool tip will display x value as a DateTime. Quick hack - this will probably be changed at some point.")]
162         public bool DateTimeToolTip { get; set; }
163 
164         /// <summary>
165         /// Sets the right context menu. Custom menus can be designed by overriding
166         /// NPlot.Windows.PlotSurface2D.ContextMenu.
167         /// </summary>
168         [
169             Browsable(false),
170             Bindable(false)
171         ]
172         public PlotContextMenu RightMenu
173         {
174             get { return rightMenu_; }
175             set
176             {
177                 rightMenu_ = value;
178                 if (rightMenu_ != null)
179                 {
180                     rightMenu_.PlotSurface2D = this;
181                 }
182             }
183         }
184 
185         /// <summary>
186         /// Gets an instance of a NPlot.Windows.PlotSurface2D.ContextMenu that
187         /// is useful in typical situations.
188         /// </summary>
189         public static PlotContextMenu DefaultContextMenu
190         {
191             get { return new PlotContextMenu(); }
192         }
193 
194         /// <summary>
195         /// Allows access to the PlotSurface2D.
196         /// </summary>
197         [
198             Browsable(false),
199             Bindable(false)
200         ]
201         public NPlot.PlotSurface2D Inner
202         {
203             get { return ps_; }
204         }
205 
206         /// <summary>
207         /// Clears the plot and resets to default values.
208         /// </summary>
Clear()209         public void Clear()
210         {
211             xAxis1ZoomCache_ = null;
212             yAxis1ZoomCache_ = null;
213             xAxis2ZoomCache_ = null;
214             yAxis2ZoomCache_ = null;
215             ps_.Clear();
216             interactions_.Clear();
217         }
218 
219         /// <summary>
220         /// Adds a drawable object to the plot surface. If the object is an IPlot,
221         /// the PlotSurface2D axes will also be updated.
222         /// </summary>
223         /// <param name="p">The IDrawable object to add to the plot surface.</param>
Add(IDrawable p)224         public void Add(IDrawable p)
225         {
226             ps_.Add(p);
227         }
228 
229         /// <summary>
230         /// Adds a drawable object to the plot surface against the specified axes. If
231         /// the object is an IPlot, the PlotSurface2D axes will also be updated.
232         /// </summary>
233         /// <param name="p">the IDrawable object to add to the plot surface</param>
234         /// <param name="xp">the x-axis to add the plot against.</param>
235         /// <param name="yp">the y-axis to add the plot against.</param>
Add(IDrawable p, NPlot.PlotSurface2D.XAxisPosition xp, NPlot.PlotSurface2D.YAxisPosition yp)236         public void Add(IDrawable p, NPlot.PlotSurface2D.XAxisPosition xp, NPlot.PlotSurface2D.YAxisPosition yp)
237         {
238             ps_.Add(p, xp, yp);
239         }
240 
241         /// <summary>
242         /// Adds a drawable object to the plot surface. If the object is an IPlot,
243         /// the PlotSurface2D axes will also be updated.
244         /// </summary>
245         /// <param name="p">The IDrawable object to add to the plot surface.</param>
246         /// <param name="zOrder">The z-ordering when drawing (objects with lower numbers are drawn first)</param>
Add(IDrawable p, int zOrder)247         public void Add(IDrawable p, int zOrder)
248         {
249             ps_.Add(p, zOrder);
250         }
251 
252         /// <summary>
253         /// Adds a drawable object to the plot surface against the specified axes. If
254         /// the object is an IPlot, the PlotSurface2D axes will also be updated.
255         /// </summary>
256         /// <param name="p">the IDrawable object to add to the plot surface</param>
257         /// <param name="xp">the x-axis to add the plot against.</param>
258         /// <param name="yp">the y-axis to add the plot against.</param>
259         /// <param name="zOrder">The z-ordering when drawing (objects with lower numbers are drawn first)</param>
Add(IDrawable p, NPlot.PlotSurface2D.XAxisPosition xp, NPlot.PlotSurface2D.YAxisPosition yp, int zOrder)260         public void Add(IDrawable p, NPlot.PlotSurface2D.XAxisPosition xp,
261                         NPlot.PlotSurface2D.YAxisPosition yp, int zOrder)
262         {
263             ps_.Add(p, xp, yp, zOrder);
264         }
265 
266         /// <summary>
267         /// Gets or Sets the legend to use with this plot surface.
268         /// </summary>
269         [
270             Browsable(false),
271             Bindable(false)
272         ]
273         public Legend Legend
274         {
275             get { return ps_.Legend; }
276             set { ps_.Legend = value; }
277         }
278 
279         /// <summary>
280         /// Gets or Sets the legend z-order.
281         /// </summary>
282         [
283             Browsable(true),
284             Bindable(true),
285             Category("PlotSurface2D"),
286             Description("Determines the order with respect to other IDrawables on the plot surface in which the legend is drawn. " +
287                         "The higher this value, the higher the position in the draw order.")
288         ]
289         public int LegendZOrder
290         {
291             get { return ps_.LegendZOrder; }
292             set { ps_.LegendZOrder = value; }
293         }
294 
295         /// <summary>
296         /// Whether or not the title will be scaled according to size of the plot
297         /// surface.
298         /// </summary>
299         [
300             Browsable(true),
301             Bindable(true),
302             Description("Whether or not the title will be scaled according to size of the plot surface."),
303             Category("PlotSurface2D")
304         ]
305         public bool AutoScaleTitle
306         {
307             get { return ps_.AutoScaleTitle; }
308             set { ps_.AutoScaleTitle = value; }
309         }
310 
311         /// <summary>
312         /// When plots are added to the plot surface, the axes they are attached to
313         /// are immediately modified to reflect data of the plot. If
314         /// AutoScaleAutoGeneratedAxes is true when a plot is added, the axes will
315         /// be turned in to auto scaling ones if they are not already [tick marks,
316         /// tick text and label size scaled to size of plot surface]. If false,
317         /// axes will not be autoscaling.
318         /// </summary>
319         [
320             Browsable(true),
321             Bindable(true),
322             Description("When plots are added to the plot surface, the axes they are attached to are immediately modified " +
323                         "to reflect data of the plot. If AutoScaleAutoGeneratedAxes is true when a plot is added, the axes will be " +
324                         "turned in to auto scaling ones if they are not already [tick marks, tick text and label size scaled to size " +
325                         "of plot surface]. If false, axes will not be autoscaling."),
326             Category("PlotSurface2D")
327         ]
328         public bool AutoScaleAutoGeneratedAxes
329         {
330             get { return ps_.AutoScaleAutoGeneratedAxes; }
331             set { ps_.AutoScaleAutoGeneratedAxes = value; }
332         }
333 
334         /// <summary>
335         /// The plot surface title.
336         /// </summary>
337         [
338             Category("PlotSurface2D"),
339             Description("The plot surface title"),
340             Browsable(true),
341             Bindable(true)
342         ]
343         public string Title
344         {
345             get { return ps_.Title; }
346             set
347             {
348                 ps_.Title = value;
349                 //helpful in design view. But crap in applications!
350                 //this.Refresh();
351             }
352         }
353 
354         /// <summary>
355         /// The font used to draw the title.
356         /// </summary>
357         [
358             Category("PlotSurface2D"),
359             Description("The font used to draw the title."),
360             Browsable(true),
361             Bindable(false)
362         ]
363         public Font TitleFont
364         {
365             get { return ps_.TitleFont; }
366             set { ps_.TitleFont = value; }
367         }
368 
369         /// <summary>
370         /// Padding of this width will be left between what is drawn and the control border.
371         /// </summary>
372         [
373             Category("PlotSurface2D"),
374             Description("Padding of this width will be left between what is drawn and the control border."),
375             Browsable(true),
376             Bindable(true)
377         ]
378         public new int Padding
379         {
380             get { return ps_.Padding; }
381             set { ps_.Padding = value; }
382         }
383 
384         /// <summary>
385         /// The first abscissa axis.
386         /// </summary>
387         [
388             Browsable(false)
389         ]
390         public Axis XAxis1
391         {
392             get { return ps_.XAxis1; }
393             set { ps_.XAxis1 = value; }
394         }
395 
396         /// <summary>
397         /// The first ordinate axis.
398         /// </summary>
399         [
400             Browsable(false)
401         ]
402         public Axis YAxis1
403         {
404             get { return ps_.YAxis1; }
405             set { ps_.YAxis1 = value; }
406         }
407 
408         /// <summary>
409         /// The second abscissa axis.
410         /// </summary>
411         [
412             Browsable(false)
413         ]
414         public Axis XAxis2
415         {
416             get { return ps_.XAxis2; }
417             set { ps_.XAxis2 = value; }
418         }
419 
420         /// <summary>
421         /// The second ordinate axis.
422         /// </summary>
423         [
424             Browsable(false)
425         ]
426         public Axis YAxis2
427         {
428             get { return ps_.YAxis2; }
429             set { ps_.YAxis2 = value; }
430         }
431 
432         /// <summary>
433         /// A color used to paint the plot background. Mutually exclusive with PlotBackImage and PlotBackBrush
434         /// </summary>
435         /// <remarks>not browsable or bindable because only set method.</remarks>
436         [
437             Category("PlotSurface2D"),
438             Description("Set the plot background color."),
439             Browsable(true),
440             Bindable(false)
441         ]
442         public Color PlotBackColor
443         {
444             set { ps_.PlotBackColor = value; }
445         }
446 
447         /// <summary>
448         /// An imaged used to paint the plot background. Mutually exclusive with PlotBackColor and PlotBackBrush
449         /// </summary>
450         /// <remarks>not browsable or bindable because only set method.</remarks>
451         [
452             Browsable(false),
453             Bindable(false)
454         ]
455         public System.Drawing.Bitmap PlotBackImage
456         {
457             set { ps_.PlotBackImage = value; }
458         }
459 
460         /// <summary>
461         /// A Rectangle brush used to paint the plot background. Mutually exclusive with PlotBackColor and PlotBackBrush
462         /// </summary>
463         /// <remarks>not browsable or bindable because only set method.</remarks>
464         [
465             Browsable(false),
466             Bindable(false)
467         ]
468         public IRectangleBrush PlotBackBrush
469         {
470             set { ps_.PlotBackBrush = value; }
471         }
472 
473         /// <summary>
474         /// Sets the title to be drawn using a solid brush of this color.
475         /// </summary>
476         /// <remarks>not browsable or bindable because only set method.</remarks>
477         [
478             Browsable(false),
479             Bindable(false)
480         ]
481         public Color TitleColor
482         {
483             set { ps_.TitleColor = value; }
484         }
485 
486         /// <summary>
487         /// The brush used for drawing the title.
488         /// </summary>
489         [
490             Browsable(true),
491             Bindable(true),
492             Description("The brush used for drawing the title."),
493             Category("PlotSurface2D")
494         ]
495         public Brush TitleBrush
496         {
497             get { return ps_.TitleBrush; }
498             set { ps_.TitleBrush = value; }
499         }
500 
501         /// <summary>
502         /// Set smoothing mode for drawing plot objects.
503         /// </summary>
504         [
505             Category("PlotSurface2D"),
506             Description("Set smoothing mode for drawing plot objects."),
507             Browsable(true),
508             Bindable(true)
509         ]
510         public SmoothingMode SmoothingMode
511         {
512             get { return ps_.SmoothingMode; }
513             set { ps_.SmoothingMode = value; }
514         }
515 
516         /// <summary>
517         /// Add an axis constraint to the plot surface. Axis constraints can
518         /// specify relative world-pixel scalings, absolute axis positions etc.
519         /// </summary>
520         /// <param name="c">The axis constraint to add.</param>
AddAxesConstraint(AxesConstraint c)521         public void AddAxesConstraint(AxesConstraint c)
522         {
523             ps_.AddAxesConstraint(c);
524         }
525 
526         /// <summary>
527         /// Remove a drawable object from the plot surface.
528         /// </summary>
529         /// <param name="p">the drawable to remove</param>
530         /// <param name="updateAxes">whether or not to update the axes after removing the idrawable.</param>
Remove(IDrawable p, bool updateAxes)531         public void Remove(IDrawable p, bool updateAxes)
532         {
533             ps_.Remove(p, updateAxes);
534         }
535 
536         /// <summary>
537         /// Gets an array list containing all drawables currently added to the PlotSurface2D.
538         /// </summary>
539         [
540             Browsable(false),
541             Bindable(false)
542         ]
543         public ArrayList Drawables
544         {
545             get { return ps_.Drawables; }
546         }
547 
548         /// <summary>
549         /// All functionality of the OnPaint method is provided by this function.
550         /// This allows use of the all encompasing PlotSurface.
551         /// </summary>
552         /// <param name="pe">the PaintEventArgs from paint event.</param>
553         /// <param name="width">width of the control</param>
554         /// <param name="height">height of the control</param>
DoPaint(PaintEventArgs pe, int width, int height)555         public void DoPaint(PaintEventArgs pe, int width, int height)
556         {
557             PreRefresh(this);
558 
559             foreach (Interactions.Interaction i in interactions_)
560             {
561                 i.DoPaint(pe, width, height);
562             }
563 
564             /*
565             // make sure don't redraw after a refresh.
566             this.horizontalBarPos_ = -1;
567             this.verticalBarPos_ = -1;
568             */
569 
570             Graphics g = pe.Graphics;
571 
572             Rectangle border = new Rectangle(0, 0, width, height);
573 
574             if (g == null)
575             {
576                 throw (new NPlotException("null graphics context!"));
577             }
578 
579             if (ps_ == null)
580             {
581                 throw (new NPlotException("null NPlot.PlotSurface2D"));
582             }
583 
584             if (border == Rectangle.Empty)
585             {
586                 throw (new NPlotException("null border context"));
587             }
588 
589             Draw(g, border);
590         }
591 
592         /// <summary>
593         /// All functionality of the OnMouseDown function is contained here.
594         /// This allows use of the all encompasing PlotSurface.
595         /// </summary>
596         /// <param name="e">The mouse event args from the window we are drawing to.</param>
DoMouseDown(MouseEventArgs e)597         public void DoMouseDown(MouseEventArgs e)
598         {
599             bool dirty = false;
600             foreach (Interactions.Interaction i in interactions_)
601             {
602                 i.DoMouseDown(e, this);
603                 dirty |= i.DoMouseDown(e, this);
604             }
605             if (dirty)
606             {
607                 Refresh();
608             }
609         }
610 
611         /// <summary>
612         /// All functionality of the OnMouseMove function is contained here.
613         /// This allows use of the all encompasing PlotSurface.
614         /// </summary>
615         /// <param name="e">The mouse event args from the window we are drawing to.</param>
616         /// <param name="ctr">The control that the mouse event happened in.</param>
DoMouseMove(MouseEventArgs e, Control ctr)617         public void DoMouseMove(MouseEventArgs e, Control ctr)
618         {
619             bool dirty = false;
620             foreach (Interactions.Interaction i in interactions_)
621             {
622                 i.DoMouseMove(e, ctr, lastKeyEventArgs_);
623                 dirty |= i.DoMouseMove(e, ctr, lastKeyEventArgs_);
624             }
625             if (dirty)
626             {
627                 Refresh();
628             }
629 
630             // Update coordinates if necessary.
631 
632             if (coordinates_.Active)
633             {
634                 // we are here
635                 Point here = new Point(e.X, e.Y);
636                 if (ps_.PlotAreaBoundingBoxCache.Contains(here))
637                 {
638                     coordinates_.ShowAlways = true;
639 
640                     // according to M�ns Erlandson, this can sometimes be the case.
641                     if (PhysicalXAxis1Cache == null)
642                         return;
643                     if (PhysicalYAxis1Cache == null)
644                         return;
645 
646                     double x = PhysicalXAxis1Cache.PhysicalToWorld(here, true);
647                     double y = PhysicalYAxis1Cache.PhysicalToWorld(here, true);
648                     string s = "";
649                     if (!DateTimeToolTip)
650                     {
651                         s = "(" + x.ToString("g4") + "," + y.ToString("g4") + ")";
652                     }
653                     else
654                     {
655                         DateTime dateTime = new DateTime((long) x);
656                         s = dateTime.ToShortDateString() + " " + dateTime.ToLongTimeString() + Environment.NewLine + y.ToString("f4");
657                     }
658                     coordinates_.SetToolTip(this, s);
659                 }
660                 else
661                 {
662                     coordinates_.ShowAlways = false;
663                 }
664             }
665         }
666 
667         /// <summary>
668         /// All functionality of the OnMouseUp function is contained here.
669         /// This allows use of the all encompasing PlotSurface.
670         /// </summary>
671         /// <param name="e">The mouse event args from the window we are drawing to.</param>
672         /// <param name="ctr">The control that the mouse event happened in.</param>
DoMouseUp(MouseEventArgs e, Control ctr)673         public void DoMouseUp(MouseEventArgs e, Control ctr)
674         {
675             bool dirty = false;
676 
677             ArrayList local_interactions = (ArrayList) interactions_.Clone();
678             foreach (Interactions.Interaction i in local_interactions)
679             {
680                 dirty |= i.DoMouseUp(e, ctr);
681             }
682             if (dirty)
683             {
684                 Refresh();
685             }
686 
687             if (e.Button == MouseButtons.Right)
688             {
689                 Point here = new Point(e.X, e.Y);
690                 //selectedObjects_ = ps_.HitTest(here);
691                 if (rightMenu_ != null)
692                     rightMenu_.Menu.Show(ctr, here);
693             }
694         }
695 
696         /// <summary>
697         /// Required method for Designer support - do not modify
698         /// the contents of this method with the code editor.
699         /// </summary>
700         /// <remarks>Modified! :-)</remarks>
InitializeComponent()701         private void InitializeComponent()
702         {
703             components = new Container();
704             coordinates_ = new ToolTip(components);
705             //
706             // PlotSurface2D
707             //
708             BackColor = SystemColors.ControlLightLight;
709             Size = new Size(328, 272);
710         }
711 
712         /// <summary>
713         /// the key down callback
714         /// </summary>
715         /// <param name="e">information pertaining to the event</param>
OnKeyDown(KeyEventArgs e)716         protected override void OnKeyDown(KeyEventArgs e)
717         {
718             lastKeyEventArgs_ = e;
719         }
720 
721         /// <summary>
722         /// The key up callback.
723         /// </summary>
724         /// <param name="e">information pertaining to the event</param>
OnKeyUp(KeyEventArgs e)725         protected override void OnKeyUp(KeyEventArgs e)
726         {
727             lastKeyEventArgs_ = e;
728         }
729 
730         /// <summary>
731         /// the paint event callback.
732         /// </summary>
733         /// <param name="pe">the PaintEventArgs</param>
OnPaint(PaintEventArgs pe)734         protected override void OnPaint(PaintEventArgs pe)
735         {
736             DoPaint(pe, Width, Height);
737             base.OnPaint(pe);
738         }
739 
740         /// <summary>
741         /// Draws the plot surface on the supplied graphics surface [not the control surface].
742         /// </summary>
743         /// <param name="g">The graphics surface on which to draw</param>
744         /// <param name="bounds">
745         /// A bounding box on this surface that denotes the area on the
746         /// surface to confine drawing to.
747         /// </param>
Draw(Graphics g, Rectangle bounds)748         public void Draw(Graphics g, Rectangle bounds)
749         {
750             // If we are not in design mode then draw as normal.
751             if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
752             {
753                 drawDesignMode(g, bounds);
754             }
755 
756             ps_.Draw(g, bounds);
757         }
758 
759         /// <summary>
760         /// Draw a lightweight representation of us for design mode.
761         /// </summary>
drawDesignMode(Graphics g, Rectangle bounds)762         private void drawDesignMode(Graphics g, Rectangle bounds)
763         {
764             g.DrawRectangle(new Pen(Color.Black), bounds.X + 2, bounds.Y + 2, bounds.Width - 4, bounds.Height - 4);
765             g.DrawString("PlotSurface2D: " + Title, TitleFont, TitleBrush, bounds.X + bounds.Width/2.0f, bounds.Y + bounds.Height/2.0f);
766         }
767 
768         /// <summary>
769         /// Mouse down event handler.
770         /// </summary>
771         /// <param name="e">the event args.</param>
OnMouseDown(MouseEventArgs e)772         protected override void OnMouseDown(MouseEventArgs e)
773         {
774             DoMouseDown(e);
775             base.OnMouseDown(e);
776         }
777 
778         /// <summary>
779         /// Mouse Wheel event handler.
780         /// </summary>
781         /// <param name="e">the event args</param>
OnMouseWheel(MouseEventArgs e)782         protected override void OnMouseWheel(MouseEventArgs e)
783         {
784             DoMouseWheel(e);
785             base.OnMouseWheel(e);
786         }
787 
788         /// <summary>
789         /// All functionality of the OnMouseWheel function is containd here.
790         /// This allows use of the all encompasing PlotSurface.
791         /// </summary>
792         /// <param name="e">the event args.</param>
DoMouseWheel(MouseEventArgs e)793         public void DoMouseWheel(MouseEventArgs e)
794         {
795             bool dirty = false;
796             foreach (Interactions.Interaction i in interactions_)
797             {
798                 i.DoMouseWheel(e, this);
799                 dirty |= i.DoMouseWheel(e, this);
800             }
801             if (dirty)
802             {
803                 Refresh();
804             }
805         }
806 
807         /// <summary>
808         /// MouseMove event handler.
809         /// </summary>
810         /// <param name="e">The event arguments.</param>
OnMouseMove(MouseEventArgs e)811         protected override void OnMouseMove(MouseEventArgs e)
812         {
813             DoMouseMove(e, this);
814             base.OnMouseMove(e);
815         }
816 
817         /// <summary>
818         /// MouseLeave event handler. It has to invalidate the control to get rid of
819         /// any remnant of vertical and horizontal guides.
820         /// </summary>
821         /// <param name="e">The event arguments.</param>
OnMouseLeave(EventArgs e)822         protected override void OnMouseLeave(EventArgs e)
823         {
824             DoMouseLeave(e, this);
825             base.OnMouseLeave(e);
826         }
827 
828         /// <summary>
829         /// </summary>
830         /// <param name="e"></param>
831         /// <param name="ctr"></param>
DoMouseLeave(EventArgs e, Control ctr)832         public void DoMouseLeave(EventArgs e, Control ctr)
833         {
834             bool dirty = false;
835             foreach (Interactions.Interaction i in interactions_)
836             {
837                 dirty = i.DoMouseLeave(e, this) || dirty;
838             }
839             if (dirty)
840                 Refresh();
841         }
842 
843         /// <summary>
844         /// mouse up event handler.
845         /// </summary>
846         /// <param name="e">The event arguments.</param>
OnMouseUp(MouseEventArgs e)847         protected override void OnMouseUp(MouseEventArgs e)
848         {
849             DoMouseUp(e, this);
850             base.OnMouseUp(e);
851         }
852 
853         /// <summary>
854         /// sets axes to be those saved in the cache.
855         /// </summary>
OriginalDimensions()856         public void OriginalDimensions()
857         {
858             if (xAxis1ZoomCache_ != null)
859             {
860                 XAxis1 = xAxis1ZoomCache_;
861                 XAxis2 = xAxis2ZoomCache_;
862                 YAxis1 = yAxis1ZoomCache_;
863                 YAxis2 = yAxis2ZoomCache_;
864 
865                 xAxis1ZoomCache_ = null;
866                 xAxis2ZoomCache_ = null;
867                 yAxis1ZoomCache_ = null;
868                 yAxis2ZoomCache_ = null;
869             }
870             Refresh();
871         }
872 
DrawHorizontalSelection(Point start, Point end, UserControl ctr)873         private void DrawHorizontalSelection(Point start, Point end, UserControl ctr)
874         {
875             // the clipping rectangle in screen coordinates
876             Rectangle clip = ctr.RectangleToScreen(
877                 new Rectangle(
878                     ps_.PlotAreaBoundingBoxCache.X,
879                     ps_.PlotAreaBoundingBoxCache.Y,
880                     ps_.PlotAreaBoundingBoxCache.Width,
881                     ps_.PlotAreaBoundingBoxCache.Height));
882 
883             start = ctr.PointToScreen(start);
884             end = ctr.PointToScreen(end);
885 
886             ControlPaint.FillReversibleRectangle(
887                 new Rectangle(Math.Min(start.X, end.X), clip.Y, Math.Abs(end.X - start.X), clip.Height),
888                 Color.White);
889         }
890 
891         /// <summary>
892         /// Print the chart as currently shown by the control
893         /// </summary>
894         /// <param name="preview">If true, show print preview window.</param>
Print(bool preview)895         public void Print(bool preview)
896         {
897             PrintDocument printDocument = new PrintDocument();
898             printDocument.PrintPage += NPlot_PrintPage;
899             printDocument.DefaultPageSettings.Landscape = true;
900 
901             DialogResult result;
902             if (!preview)
903             {
904                 PrintDialog dlg = new PrintDialog();
905                 dlg.Document = printDocument;
906                 result = dlg.ShowDialog();
907             }
908             else
909             {
910                 PrintPreviewDialog dlg = new PrintPreviewDialog();
911                 dlg.Document = printDocument;
912                 result = dlg.ShowDialog();
913             }
914             if (result == DialogResult.OK)
915             {
916                 try
917                 {
918                     printDocument.Print();
919                 }
920                 catch
921                 {
922                     Console.WriteLine("caught\n");
923                 }
924             }
925         }
926 
NPlot_PrintPage(object sender, PrintPageEventArgs ev)927         private void NPlot_PrintPage(object sender, PrintPageEventArgs ev)
928         {
929             Rectangle r = ev.MarginBounds;
930             Draw(ev.Graphics, r);
931             ev.HasMorePages = false;
932         }
933 
934         /// <summary>
935         /// Coppies the chart currently shown in the control to the clipboard as an image.
936         /// </summary>
CopyToClipboard()937         public void CopyToClipboard()
938         {
939             System.Drawing.Bitmap b = new System.Drawing.Bitmap(Width, Height);
940             Graphics g = Graphics.FromImage(b);
941             g.Clear(Color.White);
942             Draw(g, new Rectangle(0, 0, b.Width - 1, b.Height - 1));
943             Clipboard.SetDataObject(b, true);
944         }
945 
946         /// <summary>
947         /// Coppies data in the current plot surface view window to the clipboard
948         /// as text.
949         /// </summary>
CopyDataToClipboard()950         public void CopyDataToClipboard()
951         {
952             StringBuilder sb = new StringBuilder();
953 
954             for (int i = 0; i < ps_.Drawables.Count; ++i)
955             {
956                 IPlot plot = ps_.Drawables[i] as IPlot;
957                 if (plot != null)
958                 {
959                     Axis xAxis = ps_.WhichXAxis(plot);
960                     Axis yAxis = ps_.WhichYAxis(plot);
961 
962                     RectangleD region = new RectangleD(
963                         xAxis.WorldMin,
964                         yAxis.WorldMin,
965                         xAxis.WorldMax - xAxis.WorldMin,
966                         yAxis.WorldMax - yAxis.WorldMin);
967 
968                     plot.WriteData(sb, region, true);
969                 }
970             }
971 
972             Clipboard.SetDataObject(sb.ToString(), true);
973         }
974 
975         /// <summary>
976         /// Remembers the current axes - useful in interactions.
977         /// </summary>
CacheAxes()978         public void CacheAxes()
979         {
980             if (xAxis1ZoomCache_ == null && xAxis2ZoomCache_ == null &&
981                 yAxis1ZoomCache_ == null && yAxis2ZoomCache_ == null)
982             {
983                 if (XAxis1 != null)
984                 {
985                     xAxis1ZoomCache_ = (Axis) XAxis1.Clone();
986                 }
987                 if (XAxis2 != null)
988                 {
989                     xAxis2ZoomCache_ = (Axis) XAxis2.Clone();
990                 }
991                 if (YAxis1 != null)
992                 {
993                     yAxis1ZoomCache_ = (Axis) YAxis1.Clone();
994                 }
995                 if (YAxis2 != null)
996                 {
997                     yAxis2ZoomCache_ = (Axis) YAxis2.Clone();
998                 }
999             }
1000         }
1001 
1002         /// <summary>
1003         /// Adds and interaction to the plotsurface that adds functionality that responds
1004         /// to a set of mouse / keyboard events.
1005         /// </summary>
1006         /// <param name="i">the interaction to add.</param>
AddInteraction(Interactions.Interaction i)1007         public void AddInteraction(Interactions.Interaction i)
1008         {
1009             interactions_.Add(i);
1010         }
1011 
1012         /// <summary>
1013         /// Remove a previously added interaction
1014         /// </summary>
1015         /// <param name="i">interaction to remove</param>
RemoveInteraction(Interactions.Interaction i)1016         public void RemoveInteraction(Interactions.Interaction i)
1017         {
1018             interactions_.Remove(i);
1019         }
1020 
1021         /// <summary>
1022         /// Event is fired when an interaction happens with the plot that causes it to be modified.
1023         /// </summary>
1024         public event InteractionHandler InteractionOccured;
1025 
1026         /// <summary>
1027         /// Default function called when plotsurface modifying interaction occured.
1028         /// Override this, or add method to InteractionOccured event.
1029         /// </summary>
1030         /// <param name="sender"></param>
OnInteractionOccured(object sender)1031         protected void OnInteractionOccured(object sender)
1032         {
1033             // do nothing.
1034         }
1035 
1036         /// <summary>
1037         /// Event fired when we are about to paint.
1038         /// </summary>
1039         public event PreRefreshHandler PreRefresh;
1040 
1041         /// <summary>
1042         /// Default function called just before a refresh happens.
1043         /// </summary>
1044         /// <param name="sender"></param>
OnPreRefresh(object sender)1045         protected void OnPreRefresh(object sender)
1046         {
1047             // do nothing.
1048         }
1049 
1050         /// <summary>
1051         /// Clean up any resources being used.
1052         /// </summary>
Dispose(bool disposing)1053         protected override void Dispose(bool disposing)
1054         {
1055             if (disposing)
1056             {
1057                 if (components != null)
1058                     components.Dispose();
1059             }
1060             base.Dispose(disposing);
1061         }
1062 
1063         #region class PlotContextMenu
1064 
1065         /// <summary>
1066         /// Summary description for ContextMenu.
1067         /// </summary>
1068         public class PlotContextMenu
1069         {
1070             #region IPlotMenuItem
1071 
1072             /// <summary>
1073             /// elements of the MenuItems array list must implement this interface.
1074             /// </summary>
1075             public interface IPlotMenuItem
1076             {
1077                 /// <summary>
1078                 /// Gets the Windows.Forms.MenuItem associated with the PlotMenuItem
1079                 /// </summary>
1080                 MenuItem MenuItem { get; }
1081 
1082                 /// <summary>
1083                 /// This method is called for each menu item before the menu is
1084                 /// displayed. It is useful for implementing check marks, disabling
1085                 /// etc.
1086                 /// </summary>
1087                 /// <param name="plotContextMenu"></param>
OnPopup(PlotContextMenu plotContextMenu)1088                 void OnPopup(PlotContextMenu plotContextMenu);
1089             }
1090 
1091             #endregion
1092 
1093             #region PlotMenuSeparator
1094 
1095             /// <summary>
1096             /// A plot menu item for separators.
1097             /// </summary>
1098             public class PlotMenuSeparator : IPlotMenuItem
1099             {
1100                 private readonly int index_;
1101 
1102                 private readonly MenuItem menuItem_;
1103 
1104                 /// <summary>
1105                 /// Constructor
1106                 /// </summary>
1107                 /// <param name="index"></param>
PlotMenuSeparator(int index)1108                 public PlotMenuSeparator(int index)
1109                 {
1110                     menuItem_ = new MenuItem();
1111                     index_ = index;
1112 
1113                     menuItem_.Index = index_;
1114                     menuItem_.Text = "-";
1115                 }
1116 
1117                 /// <summary>
1118                 /// Index of this menu item in the menu.
1119                 /// </summary>
1120                 public int Index
1121                 {
1122                     get { return index_; }
1123                 }
1124 
1125                 /// <summary>
1126                 /// The Windows.Forms.MenuItem associated with this IPlotMenuItem
1127                 /// </summary>
1128                 public MenuItem MenuItem
1129                 {
1130                     get { return menuItem_; }
1131                 }
1132 
1133                 /// <summary>
1134                 /// </summary>
1135                 /// <param name="plotContextMenu"></param>
OnPopup(PlotContextMenu plotContextMenu)1136                 public void OnPopup(PlotContextMenu plotContextMenu)
1137                 {
1138                     // do nothing.
1139                 }
1140             }
1141 
1142             #endregion
1143 
1144             #region PlotMenuItem
1145 
1146             /// <summary>
1147             /// A Plot menu item suitable for specifying basic menu items
1148             /// </summary>
1149             public class PlotMenuItem : IPlotMenuItem
1150             {
1151                 private readonly EventHandler callback_;
1152                 private readonly int index_;
1153                 private readonly MenuItem menuItem_;
1154                 private readonly string text_;
1155 
1156                 /// <summary>
1157                 /// Constructor
1158                 /// </summary>
1159                 /// <param name="text">Menu item text</param>
1160                 /// <param name="index">Index in the manu</param>
1161                 /// <param name="callback">EventHandler to call if menu selected.</param>
PlotMenuItem(string text, int index, EventHandler callback)1162                 public PlotMenuItem(string text, int index, EventHandler callback)
1163                 {
1164                     text_ = text;
1165                     index_ = index;
1166                     callback_ = callback;
1167 
1168                     menuItem_ = new MenuItem();
1169 
1170                     menuItem_.Index = index;
1171                     menuItem_.Text = text;
1172                     menuItem_.Click += callback;
1173                 }
1174 
1175                 /// <summary>
1176                 /// The text to put in the menu for this menu item.
1177                 /// </summary>
1178                 public string Text
1179                 {
1180                     get { return text_; }
1181                 }
1182 
1183                 /// <summary>
1184                 /// Index of this menu item in the menu.
1185                 /// </summary>
1186                 public int Index
1187                 {
1188                     get { return index_; }
1189                 }
1190 
1191                 /// <summary>
1192                 /// EventHandler to call if menu selected.
1193                 /// </summary>
1194                 public EventHandler Callback
1195                 {
1196                     get { return callback_; }
1197                 }
1198 
1199                 /// <summary>
1200                 /// The Windows.Forms.MenuItem associated with this IPlotMenuItem
1201                 /// </summary>
1202                 public MenuItem MenuItem
1203                 {
1204                     get { return menuItem_; }
1205                 }
1206 
1207                 /// <summary>
1208                 /// Called before menu drawn.
1209                 /// </summary>
1210                 /// <param name="plotContextMenu">The plot menu this item is a member of.</param>
OnPopup(PlotContextMenu plotContextMenu)1211                 public virtual void OnPopup(PlotContextMenu plotContextMenu)
1212                 {
1213                     // do nothing.
1214                 }
1215             }
1216 
1217             #endregion
1218 
1219             #region PlotZoomBackMenuItem
1220 
1221             /// <summary>
1222             /// A Plot Menu Item that provides necessary functionality for the
1223             /// zoom back menu item (graying out if zoomed right out in addition
1224             /// to basic functionality).
1225             /// </summary>
1226             public class PlotZoomBackMenuItem : PlotMenuItem
1227             {
1228                 /// <summary>
1229                 /// Constructor
1230                 /// </summary>
1231                 /// <param name="text">Text associated with this item in the menu.</param>
1232                 /// <param name="index">Index of this item in the menu.</param>
1233                 /// <param name="callback">EventHandler to call when menu item is selected.</param>
PlotZoomBackMenuItem(string text, int index, EventHandler callback)1234                 public PlotZoomBackMenuItem(string text, int index, EventHandler callback)
1235                     : base(text, index, callback)
1236                 {
1237                 }
1238 
1239                 /// <summary>
1240                 /// Called before menu drawn.
1241                 /// </summary>
1242                 /// <param name="plotContextMenu">The plot menu this item is a member of.</param>
OnPopup(PlotContextMenu plotContextMenu)1243                 public override void OnPopup(PlotContextMenu plotContextMenu)
1244                 {
1245                     MenuItem.Enabled = plotContextMenu.plotSurface2D_.xAxis1ZoomCache_ != null;
1246                 }
1247             }
1248 
1249             #endregion
1250 
1251             #region PlotShowCoordinatesMenuItem
1252 
1253             /// <summary>
1254             /// A Plot Menu Item that provides necessary functionality for the
1255             /// show coordinates menu item (tick mark toggle in addition to basic
1256             /// functionality).
1257             /// </summary>
1258             public class PlotShowCoordinatesMenuItem : PlotMenuItem
1259             {
1260                 /// <summary>
1261                 /// Constructor
1262                 /// </summary>
1263                 /// <param name="text">Text associated with this item in the menu.</param>
1264                 /// <param name="index">Index of this item in the menu.</param>
1265                 /// <param name="callback">EventHandler to call when menu item is selected.</param>
PlotShowCoordinatesMenuItem(string text, int index, EventHandler callback)1266                 public PlotShowCoordinatesMenuItem(string text, int index, EventHandler callback)
1267                     : base(text, index, callback)
1268                 {
1269                 }
1270 
1271                 /// <summary>
1272                 /// Called before menu drawn.
1273                 /// </summary>
1274                 /// <param name="plotContextMenu">The plot menu this item is a member of.</param>
OnPopup(PlotContextMenu plotContextMenu)1275                 public override void OnPopup(PlotContextMenu plotContextMenu)
1276                 {
1277                     MenuItem.Checked = plotContextMenu.plotSurface2D_.ShowCoordinates;
1278                 }
1279             }
1280 
1281             #endregion
1282 
1283             private ArrayList menuItems_;
1284 
1285             /// <summary>
1286             /// The PlotSurface2D associated with the context menu. Classes inherited
1287             /// from PlotContextMenu will likely use this to implement their functionality.
1288             /// </summary>
1289             protected PlotSurface2D plotSurface2D_;
1290 
1291             private ContextMenu rightMenu_;
1292 
1293             /// <summary>
1294             /// Constructor creates
1295             /// </summary>
PlotContextMenu()1296             public PlotContextMenu()
1297             {
1298                 ArrayList menuItems = new ArrayList();
1299 
1300                 menuItems = new ArrayList();
1301                 menuItems.Add(new PlotZoomBackMenuItem("Original Dimensions", 0, mnuOriginalDimensions_Click));
1302                 menuItems.Add(new PlotShowCoordinatesMenuItem("Show World Coordinates", 1, mnuDisplayCoordinates_Click));
1303                 menuItems.Add(new PlotMenuSeparator(2));
1304                 menuItems.Add(new PlotMenuItem("Print", 3, mnuPrint_Click));
1305                 menuItems.Add(new PlotMenuItem("Print Preview", 4, mnuPrintPreview_Click));
1306                 menuItems.Add(new PlotMenuItem("Copy To Clipboard", 5, mnuCopyToClipboard_Click));
1307                 menuItems.Add(new PlotMenuItem("Copy Data To Clipboard", 6, mnuCopyDataToClipboard_Click));
1308 
1309                 SetMenuItems(menuItems);
1310             }
1311 
1312             /// <summary>
1313             /// Gets an arraylist of all PlotMenuItems that comprise the
1314             /// menu. If this list is changed, this class must be told to
1315             /// update using the Update method.
1316             /// </summary>
1317             public ArrayList MenuItems
1318             {
1319                 get { return menuItems_; }
1320             }
1321 
1322             /// <summary>
1323             /// The PlotSurface2D associated with the context menu. Generally, the user
1324             /// should not set this. It is used internally by PlotSurface2D.
1325             /// </summary>
1326             public PlotSurface2D PlotSurface2D
1327             {
1328                 set { plotSurface2D_ = value; }
1329             }
1330 
1331             /// <summary>
1332             /// Gets the Windows.Forms context menu managed by this object.
1333             /// </summary>
1334             public ContextMenu Menu
1335             {
1336                 get { return rightMenu_; }
1337             }
1338 
1339             /// <summary>
1340             /// Sets the context menu according to the IPlotMenuItem's in the provided
1341             /// ArrayList. The current menu items can be obtained using the MenuItems
1342             /// property and extended if desired.
1343             /// </summary>
1344             /// <param name="menuItems"></param>
SetMenuItems(ArrayList menuItems)1345             public void SetMenuItems(ArrayList menuItems)
1346             {
1347                 menuItems_ = menuItems;
1348 
1349                 rightMenu_ = new ContextMenu();
1350 
1351                 foreach (IPlotMenuItem item in menuItems_)
1352                 {
1353                     rightMenu_.MenuItems.Add(item.MenuItem);
1354                 }
1355 
1356                 rightMenu_.Popup += rightMenu__Popup;
1357             }
1358 
mnuOriginalDimensions_Click(object sender, EventArgs e)1359             private void mnuOriginalDimensions_Click(object sender, EventArgs e)
1360             {
1361                 plotSurface2D_.OriginalDimensions();
1362             }
1363 
mnuCopyToClipboard_Click(object sender, EventArgs e)1364             private void mnuCopyToClipboard_Click(object sender, EventArgs e)
1365             {
1366                 plotSurface2D_.CopyToClipboard();
1367             }
1368 
mnuCopyDataToClipboard_Click(object sender, EventArgs e)1369             private void mnuCopyDataToClipboard_Click(object sender, EventArgs e)
1370             {
1371                 plotSurface2D_.CopyDataToClipboard();
1372             }
1373 
mnuPrint_Click(object sender, EventArgs e)1374             private void mnuPrint_Click(object sender, EventArgs e)
1375             {
1376                 plotSurface2D_.Print(false);
1377             }
1378 
mnuPrintPreview_Click(object sender, EventArgs e)1379             private void mnuPrintPreview_Click(object sender, EventArgs e)
1380             {
1381                 plotSurface2D_.Print(true);
1382             }
1383 
mnuDisplayCoordinates_Click(object sender, EventArgs e)1384             private void mnuDisplayCoordinates_Click(object sender, EventArgs e)
1385             {
1386                 plotSurface2D_.ShowCoordinates = !plotSurface2D_.ShowCoordinates;
1387             }
1388 
rightMenu__Popup(object sender, EventArgs e)1389             private void rightMenu__Popup(object sender, EventArgs e)
1390             {
1391                 foreach (IPlotMenuItem item in menuItems_)
1392                 {
1393                     item.OnPopup(this);
1394                 }
1395             }
1396         }
1397 
1398         #endregion
1399 
1400         /// <summary>
1401         /// Encapsulates a number of separate "Interactions". An interaction is basically
1402         /// a set of handlers for mouse and keyboard events that work together in a
1403         /// specific way.
1404         /// </summary>
1405         public abstract class Interactions
1406         {
1407             /// <summary>
1408             /// Base class for an interaction. All methods are virtual. Not abstract as not all interactions
1409             /// need to use all methods. Default functionality for each method is to do nothing.
1410             /// </summary>
1411             public class Interaction
1412             {
1413                 /// <summary>
1414                 /// Handler for this interaction if a mouse down event is received.
1415                 /// </summary>
1416                 /// <param name="e">event args</param>
1417                 /// <param name="ctr">reference to the control</param>
1418                 /// <returns>true if plot surface needs refreshing.</returns>
DoMouseDown(MouseEventArgs e, Control ctr)1419                 public virtual bool DoMouseDown(MouseEventArgs e, Control ctr)
1420                 {
1421                     return false;
1422                 }
1423 
1424                 /// <summary>
1425                 /// Handler for this interaction if a mouse up event is received.
1426                 /// </summary>
1427                 /// <param name="e">event args</param>
1428                 /// <param name="ctr">reference to the control</param>
1429                 /// <returns>true if plot surface needs refreshing.</returns>
DoMouseUp(MouseEventArgs e, Control ctr)1430                 public virtual bool DoMouseUp(MouseEventArgs e, Control ctr)
1431                 {
1432                     return false;
1433                 }
1434 
1435                 /// <summary>
1436                 /// Handler for this interaction if a mouse move event is received.
1437                 /// </summary>
1438                 /// <param name="e">event args</param>
1439                 /// <param name="ctr">reference to the control</param>
1440                 /// <param name="lastKeyEventArgs"></param>
1441                 /// <returns>true if plot surface needs refreshing.</returns>
DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)1442                 public virtual bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
1443                 {
1444                     return false;
1445                 }
1446 
1447                 /// <summary>
1448                 /// Handler for this interaction if a mouse move event is received.
1449                 /// </summary>
1450                 /// <param name="e">event args</param>
1451                 /// <param name="ctr">reference to the control</param>
1452                 /// <returns>true if plot surface needs refreshing.</returns>
DoMouseWheel(MouseEventArgs e, Control ctr)1453                 public virtual bool DoMouseWheel(MouseEventArgs e, Control ctr)
1454                 {
1455                     return false;
1456                 }
1457 
1458                 /// <summary>
1459                 /// Handler for this interaction if a mouse Leave event is received.
1460                 /// </summary>
1461                 /// <param name="e">event args</param>
1462                 /// <param name="ctr">reference to the control</param>
1463                 /// <returns>true if the plot surface needs refreshing.</returns>
DoMouseLeave(EventArgs e, Control ctr)1464                 public virtual bool DoMouseLeave(EventArgs e, Control ctr)
1465                 {
1466                     return false;
1467                 }
1468 
1469                 /// <summary>
1470                 /// Handler for this interaction if a paint event is received.
1471                 /// </summary>
1472                 /// <param name="pe">paint event args</param>
1473                 /// <param name="width"></param>
1474                 /// <param name="height"></param>
DoPaint(PaintEventArgs pe, int width, int height)1475                 public virtual void DoPaint(PaintEventArgs pe, int width, int height)
1476                 {
1477                 }
1478             }
1479 
1480             #region RubberBandSelection
1481 
1482             /// <summary>
1483             /// </summary>
1484             public class RubberBandSelection : Interaction
1485             {
1486                 private readonly Point unset_ = new Point(-1, -1);
1487                 private Point endPoint_ = new Point(-1, -1);
1488                 private bool selectionInitiated_;
1489                 private Point startPoint_ = new Point(-1, -1);
1490 
1491                 /// <summary>
1492                 /// </summary>
1493                 /// <param name="e"></param>
1494                 /// <param name="ctr"></param>
DoMouseDown(MouseEventArgs e, Control ctr)1495                 public override bool DoMouseDown(MouseEventArgs e, Control ctr)
1496                 {
1497                     // keep track of the start point and flag that select initiated.
1498                     selectionInitiated_ = true;
1499                     startPoint_.X = e.X;
1500                     startPoint_.Y = e.Y;
1501 
1502                     // invalidate the end point
1503                     endPoint_ = unset_;
1504 
1505                     return false;
1506                 }
1507 
1508                 /// <summary>
1509                 /// </summary>
1510                 /// <param name="e"></param>
1511                 /// <param name="ctr"></param>
1512                 /// <param name="lastKeyEventArgs"></param>
DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)1513                 public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
1514                 {
1515                     if ((e.Button == MouseButtons.Left) && selectionInitiated_)
1516                     {
1517                         // we are here
1518                         Point here = new Point(e.X, e.Y);
1519 
1520                         // delete the previous box
1521                         if (endPoint_ != unset_)
1522                         {
1523                             DrawRubberBand(startPoint_, endPoint_, ctr);
1524                         }
1525                         endPoint_ = here;
1526 
1527                         // and redraw the last one
1528                         DrawRubberBand(startPoint_, endPoint_, ctr);
1529                     }
1530 
1531                     return false;
1532                 }
1533 
1534                 /// <summary>
1535                 /// </summary>
1536                 /// <param name="e"></param>
1537                 /// <param name="ctr"></param>
DoMouseUp(MouseEventArgs e, Control ctr)1538                 public override bool DoMouseUp(MouseEventArgs e, Control ctr)
1539                 {
1540                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
1541 
1542                     // handle left button (selecting region).
1543                     if ((e.Button == MouseButtons.Left) && selectionInitiated_)
1544                     {
1545                         endPoint_.X = e.X;
1546                         endPoint_.Y = e.Y;
1547 
1548                         // flag stopped selecting.
1549                         selectionInitiated_ = false;
1550 
1551                         if (endPoint_ != unset_)
1552                         {
1553                             DrawRubberBand(startPoint_, endPoint_, ctr);
1554                         }
1555 
1556                         Point minPoint = new Point(0, 0);
1557                         minPoint.X = Math.Min(startPoint_.X, endPoint_.X);
1558                         minPoint.Y = Math.Min(startPoint_.Y, endPoint_.Y);
1559 
1560                         Point maxPoint = new Point(0, 0);
1561                         maxPoint.X = Math.Max(startPoint_.X, endPoint_.X);
1562                         maxPoint.Y = Math.Max(startPoint_.Y, endPoint_.Y);
1563 
1564                         Rectangle r = ps.PlotAreaBoundingBoxCache;
1565                         if (minPoint != maxPoint && (r.Contains(minPoint) || r.Contains(maxPoint)))
1566                         {
1567                             ((PlotSurface2D) ctr).CacheAxes();
1568 
1569                             ((PlotSurface2D) ctr).PhysicalXAxis1Cache.SetWorldLimitsFromPhysical(minPoint, maxPoint);
1570                             ((PlotSurface2D) ctr).PhysicalXAxis2Cache.SetWorldLimitsFromPhysical(minPoint, maxPoint);
1571                             ((PlotSurface2D) ctr).PhysicalYAxis1Cache.SetWorldLimitsFromPhysical(maxPoint, minPoint);
1572                             ((PlotSurface2D) ctr).PhysicalYAxis2Cache.SetWorldLimitsFromPhysical(maxPoint, minPoint);
1573 
1574                             // reset the start/end points
1575                             startPoint_ = unset_;
1576                             endPoint_ = unset_;
1577 
1578                             ((PlotSurface2D) ctr).InteractionOccured(this);
1579 
1580                             return true;
1581                         }
1582                     }
1583 
1584                     return false;
1585                 }
1586 
1587                 /// <summary>
1588                 /// Draws a rectangle representing selection area.
1589                 /// </summary>
1590                 /// <param name="start">a corner of the rectangle.</param>
1591                 /// <param name="end">a corner of the rectangle diagonally opposite the first.</param>
1592                 /// <param name="ctr">
1593                 /// The control to draw to - this may not be us, if we have
1594                 /// been contained by a PlotSurface.
1595                 /// </param>
DrawRubberBand(Point start, Point end, Control ctr)1596                 private void DrawRubberBand(Point start, Point end, Control ctr)
1597                 {
1598                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
1599 
1600                     Rectangle rect = new Rectangle();
1601 
1602                     // the clipping rectangle in screen coordinates
1603                     Rectangle clip = ctr.RectangleToScreen(
1604                         new Rectangle(
1605                             ps.PlotAreaBoundingBoxCache.X,
1606                             ps.PlotAreaBoundingBoxCache.Y,
1607                             ps.PlotAreaBoundingBoxCache.Width,
1608                             ps.PlotAreaBoundingBoxCache.Height));
1609 
1610                     // convert to screen coords
1611                     start = ctr.PointToScreen(start);
1612                     end = ctr.PointToScreen(end);
1613 
1614                     // now, "normalize" the rectangle
1615                     if (start.X < end.X)
1616                     {
1617                         rect.X = start.X;
1618                         rect.Width = end.X - start.X;
1619                     }
1620                     else
1621                     {
1622                         rect.X = end.X;
1623                         rect.Width = start.X - end.X;
1624                     }
1625                     if (start.Y < end.Y)
1626                     {
1627                         rect.Y = start.Y;
1628                         rect.Height = end.Y - start.Y;
1629                     }
1630                     else
1631                     {
1632                         rect.Y = end.Y;
1633                         rect.Height = start.Y - end.Y;
1634                     }
1635                     rect = Rectangle.Intersect(rect, clip);
1636 
1637                     ControlPaint.DrawReversibleFrame(
1638                         new Rectangle(rect.X, rect.Y, rect.Width, rect.Height),
1639                         Color.White, FrameStyle.Dashed);
1640                 }
1641             }
1642 
1643             #endregion
1644 
1645             #region HorizontalGuideline
1646 
1647             /// <summary>
1648             /// Horizontal line interaction
1649             /// </summary>
1650             public class HorizontalGuideline : Interaction
1651             {
1652                 private readonly Color color_;
1653                 private int barPos_;
1654 
1655                 /// <summary>
1656                 /// Constructor
1657                 /// </summary>
HorizontalGuideline()1658                 public HorizontalGuideline()
1659                 {
1660                     color_ = Color.Black;
1661                 }
1662 
1663                 /// <summary>
1664                 /// Constructor
1665                 /// </summary>
1666                 /// <param name="lineColor"></param>
HorizontalGuideline(Color lineColor)1667                 public HorizontalGuideline(Color lineColor)
1668                 {
1669                     color_ = lineColor;
1670                 }
1671 
1672                 /// <summary>
1673                 /// </summary>
1674                 /// <param name="pe"></param>
1675                 /// <param name="width"></param>
1676                 /// <param name="height"></param>
DoPaint(PaintEventArgs pe, int width, int height)1677                 public override void DoPaint(PaintEventArgs pe, int width, int height)
1678                 {
1679                     barPos_ = -1;
1680                 }
1681 
1682                 /// <summary>
1683                 /// </summary>
1684                 /// <param name="e"></param>
1685                 /// <param name="ctr"></param>
1686                 /// <param name="lastKeyEventArgs"></param>
DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)1687                 public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
1688                 {
1689                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
1690 
1691                     // if mouse isn't in plot region, then don't draw horizontal line
1692                     if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < ps.PlotAreaBoundingBoxCache.Right &&
1693                         e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < (ps.PlotAreaBoundingBoxCache.Bottom - 1))
1694                     {
1695                         if (ps.PhysicalXAxis1Cache != null)
1696                         {
1697                             // the clipping rectangle in screen coordinates
1698                             Rectangle clip = ctr.RectangleToScreen(
1699                                 new Rectangle(
1700                                     ps.PlotAreaBoundingBoxCache.X,
1701                                     ps.PlotAreaBoundingBoxCache.Y,
1702                                     ps.PlotAreaBoundingBoxCache.Width,
1703                                     ps.PlotAreaBoundingBoxCache.Height));
1704 
1705                             Point p = ctr.PointToScreen(new Point(e.X, e.Y));
1706 
1707                             if (barPos_ != -1)
1708                             {
1709                                 ControlPaint.DrawReversibleLine(
1710                                     new Point(clip.Left, barPos_),
1711                                     new Point(clip.Right, barPos_), color_);
1712                             }
1713 
1714                             if (p.Y < clip.Bottom && p.Y > clip.Top)
1715                             {
1716                                 ControlPaint.DrawReversibleLine(
1717                                     new Point(clip.Left, p.Y),
1718                                     new Point(clip.Right, p.Y), color_);
1719 
1720                                 barPos_ = p.Y;
1721                             }
1722                             else
1723                             {
1724                                 barPos_ = -1;
1725                             }
1726                         }
1727                     }
1728                     else
1729                     {
1730                         if (barPos_ != -1)
1731                         {
1732                             Rectangle clip = ctr.RectangleToScreen(
1733                                 new Rectangle(
1734                                     ps.PlotAreaBoundingBoxCache.X,
1735                                     ps.PlotAreaBoundingBoxCache.Y,
1736                                     ps.PlotAreaBoundingBoxCache.Width,
1737                                     ps.PlotAreaBoundingBoxCache.Height));
1738 
1739                             ControlPaint.DrawReversibleLine(
1740                                 new Point(clip.Left, barPos_),
1741                                 new Point(clip.Right, barPos_), color_);
1742                             barPos_ = -1;
1743                         }
1744                     }
1745 
1746                     return false;
1747                 }
1748 
1749                 /// <summary>
1750                 /// </summary>
1751                 /// <param name="e"></param>
1752                 /// <param name="ctr"></param>
1753                 /// <returns></returns>
DoMouseLeave(EventArgs e, Control ctr)1754                 public override bool DoMouseLeave(EventArgs e, Control ctr)
1755                 {
1756                     if (barPos_ != -1)
1757                     {
1758                         NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
1759 
1760                         Rectangle clip = ctr.RectangleToScreen(
1761                             new Rectangle(
1762                                 ps.PlotAreaBoundingBoxCache.X,
1763                                 ps.PlotAreaBoundingBoxCache.Y,
1764                                 ps.PlotAreaBoundingBoxCache.Width,
1765                                 ps.PlotAreaBoundingBoxCache.Height));
1766 
1767                         ControlPaint.DrawReversibleLine(
1768                             new Point(clip.Left, barPos_),
1769                             new Point(clip.Right, barPos_), color_);
1770 
1771                         barPos_ = -1;
1772                     }
1773                     return false;
1774                 }
1775             }
1776 
1777             #endregion
1778 
1779             #region VerticalGuideline
1780 
1781             /// <summary>
1782             /// </summary>
1783             public class VerticalGuideline : Interaction
1784             {
1785                 private readonly Color color_;
1786                 private int barPos_;
1787 
1788                 /// <summary>
1789                 /// </summary>
VerticalGuideline()1790                 public VerticalGuideline()
1791                 {
1792                     color_ = Color.Black;
1793                 }
1794 
1795                 /// <summary>
1796                 /// </summary>
1797                 /// <param name="lineColor"></param>
VerticalGuideline(Color lineColor)1798                 public VerticalGuideline(Color lineColor)
1799                 {
1800                     color_ = lineColor;
1801                 }
1802 
1803                 /// <summary>
1804                 /// </summary>
1805                 /// <param name="pe"></param>
1806                 /// <param name="width"></param>
1807                 /// <param name="height"></param>
DoPaint(PaintEventArgs pe, int width, int height)1808                 public override void DoPaint(PaintEventArgs pe, int width, int height)
1809                 {
1810                     barPos_ = -1;
1811                 }
1812 
1813                 /// <summary>
1814                 /// </summary>
1815                 /// <param name="e"></param>
1816                 /// <param name="ctr"></param>
1817                 /// <param name="lastKeyEventArgs"></param>
DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)1818                 public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
1819                 {
1820                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
1821 
1822                     // if mouse isn't in plot region, then don't draw horizontal line
1823                     if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < (ps.PlotAreaBoundingBoxCache.Right - 1) &&
1824                         e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
1825                     {
1826                         if (ps.PhysicalXAxis1Cache != null)
1827                         {
1828                             // the clipping rectangle in screen coordinates
1829                             Rectangle clip = ctr.RectangleToScreen(
1830                                 new Rectangle(
1831                                     ps.PlotAreaBoundingBoxCache.X,
1832                                     ps.PlotAreaBoundingBoxCache.Y,
1833                                     ps.PlotAreaBoundingBoxCache.Width,
1834                                     ps.PlotAreaBoundingBoxCache.Height));
1835 
1836                             Point p = ctr.PointToScreen(new Point(e.X, e.Y));
1837 
1838                             if (barPos_ != -1)
1839                             {
1840                                 ControlPaint.DrawReversibleLine(
1841                                     new Point(barPos_, clip.Top),
1842                                     new Point(barPos_, clip.Bottom), color_);
1843                             }
1844 
1845                             if (p.X < clip.Right && p.X > clip.Left)
1846                             {
1847                                 ControlPaint.DrawReversibleLine(
1848                                     new Point(p.X, clip.Top),
1849                                     new Point(p.X, clip.Bottom), color_);
1850                                 barPos_ = p.X;
1851                             }
1852                             else
1853                             {
1854                                 barPos_ = -1;
1855                             }
1856                         }
1857                     }
1858                     else
1859                     {
1860                         if (barPos_ != -1)
1861                         {
1862                             Rectangle clip = ctr.RectangleToScreen(
1863                                 new Rectangle(
1864                                     ps.PlotAreaBoundingBoxCache.X,
1865                                     ps.PlotAreaBoundingBoxCache.Y,
1866                                     ps.PlotAreaBoundingBoxCache.Width,
1867                                     ps.PlotAreaBoundingBoxCache.Height)
1868                                 );
1869 
1870                             ControlPaint.DrawReversibleLine(
1871                                 new Point(barPos_, clip.Top),
1872                                 new Point(barPos_, clip.Bottom), color_);
1873 
1874                             barPos_ = -1;
1875                         }
1876                     }
1877 
1878                     return false;
1879                 }
1880 
1881                 /// <summary>
1882                 /// Handler for mouse leave event
1883                 /// </summary>
1884                 /// <param name="e">event args</param>
1885                 /// <param name="ctr"></param>
1886                 /// <returns></returns>
DoMouseLeave(EventArgs e, Control ctr)1887                 public override bool DoMouseLeave(EventArgs e, Control ctr)
1888                 {
1889                     if (barPos_ != -1)
1890                     {
1891                         NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
1892 
1893                         Rectangle clip = ctr.RectangleToScreen(
1894                             new Rectangle(
1895                                 ps.PlotAreaBoundingBoxCache.X,
1896                                 ps.PlotAreaBoundingBoxCache.Y,
1897                                 ps.PlotAreaBoundingBoxCache.Width,
1898                                 ps.PlotAreaBoundingBoxCache.Height));
1899 
1900                         ControlPaint.DrawReversibleLine(
1901                             new Point(barPos_, clip.Top),
1902                             new Point(barPos_, clip.Bottom), color_);
1903                         barPos_ = -1;
1904                     }
1905                     return false;
1906                 }
1907             }
1908 
1909             #endregion
1910 
1911             #region HorizontalDrag
1912 
1913             /// <summary>
1914             /// </summary>
1915             public class HorizontalDrag : Interaction
1916             {
1917                 private readonly Point unset_ = new Point(-1, -1);
1918                 private bool dragInitiated_;
1919                 private Point lastPoint_ = new Point(-1, -1);
1920 
1921                 /// <summary>
1922                 /// </summary>
1923                 /// <param name="e"></param>
1924                 /// <param name="ctr"></param>
DoMouseDown(MouseEventArgs e, Control ctr)1925                 public override bool DoMouseDown(MouseEventArgs e, Control ctr)
1926                 {
1927                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
1928 
1929                     if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < (ps.PlotAreaBoundingBoxCache.Right) &&
1930                         e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
1931                     {
1932                         dragInitiated_ = true;
1933 
1934                         lastPoint_.X = e.X;
1935                         lastPoint_.Y = e.Y;
1936                     }
1937 
1938                     return false;
1939                 }
1940 
1941                 /// <summary>
1942                 /// </summary>
1943                 /// <param name="e"></param>
1944                 /// <param name="ctr"></param>
1945                 /// <param name="lastKeyEventArgs"></param>
DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)1946                 public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
1947                 {
1948                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
1949 
1950                     if ((e.Button == MouseButtons.Left) && dragInitiated_)
1951                     {
1952                         int diffX = e.X - lastPoint_.X;
1953 
1954                         ((PlotSurface2D) ctr).CacheAxes();
1955 
1956                         // original code was using PixelWorldLength of the physical axis
1957                         // but it was not working for non-linear axes - the code below works
1958                         // in all cases
1959                         if (ps.XAxis1 != null)
1960                         {
1961                             Axis axis = ps.XAxis1;
1962                             PointF pMin = ps.PhysicalXAxis1Cache.PhysicalMin;
1963                             PointF pMax = ps.PhysicalXAxis1Cache.PhysicalMax;
1964 
1965                             PointF physicalWorldMin = pMin;
1966                             PointF physicalWorldMax = pMax;
1967                             physicalWorldMin.X -= diffX;
1968                             physicalWorldMax.X -= diffX;
1969                             double newWorldMin = axis.PhysicalToWorld(physicalWorldMin, pMin, pMax, false);
1970                             double newWorldMax = axis.PhysicalToWorld(physicalWorldMax, pMin, pMax, false);
1971                             axis.WorldMin = newWorldMin;
1972                             axis.WorldMax = newWorldMax;
1973                         }
1974                         if (ps.XAxis2 != null)
1975                         {
1976                             Axis axis = ps.XAxis2;
1977                             PointF pMin = ps.PhysicalXAxis2Cache.PhysicalMin;
1978                             PointF pMax = ps.PhysicalXAxis2Cache.PhysicalMax;
1979 
1980                             PointF physicalWorldMin = pMin;
1981                             PointF physicalWorldMax = pMax;
1982                             physicalWorldMin.X -= diffX;
1983                             physicalWorldMax.X -= diffX;
1984                             double newWorldMin = axis.PhysicalToWorld(physicalWorldMin, pMin, pMax, false);
1985                             double newWorldMax = axis.PhysicalToWorld(physicalWorldMax, pMin, pMax, false);
1986                             axis.WorldMin = newWorldMin;
1987                             axis.WorldMax = newWorldMax;
1988                         }
1989 
1990                         lastPoint_ = new Point(e.X, e.Y);
1991 
1992                         ((PlotSurface2D) ctr).InteractionOccured(this);
1993 
1994                         return true;
1995                     }
1996 
1997                     return false;
1998                 }
1999 
2000                 /// <summary>
2001                 /// </summary>
2002                 /// <param name="e"></param>
2003                 /// <param name="ctr"></param>
DoMouseUp(MouseEventArgs e, Control ctr)2004                 public override bool DoMouseUp(MouseEventArgs e, Control ctr)
2005                 {
2006                     if ((e.Button == MouseButtons.Left) && dragInitiated_)
2007                     {
2008                         lastPoint_ = unset_;
2009                         dragInitiated_ = false;
2010                     }
2011                     return false;
2012                 }
2013             }
2014 
2015             #endregion
2016 
2017             #region VerticalDrag
2018 
2019             /// <summary>
2020             /// </summary>
2021             public class VerticalDrag : Interaction
2022             {
2023                 private readonly Point unset_ = new Point(-1, -1);
2024                 private bool dragInitiated_;
2025                 private Point lastPoint_ = new Point(-1, -1);
2026 
2027                 /// <summary>
2028                 /// </summary>
2029                 /// <param name="e"></param>
2030                 /// <param name="ctr"></param>
DoMouseDown(MouseEventArgs e, Control ctr)2031                 public override bool DoMouseDown(MouseEventArgs e, Control ctr)
2032                 {
2033                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
2034 
2035                     if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < (ps.PlotAreaBoundingBoxCache.Right) &&
2036                         e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
2037                     {
2038                         dragInitiated_ = true;
2039 
2040                         lastPoint_.X = e.X;
2041                         lastPoint_.Y = e.Y;
2042                     }
2043 
2044                     return false;
2045                 }
2046 
2047                 /// <summary>
2048                 /// </summary>
2049                 /// <param name="e"></param>
2050                 /// <param name="ctr"></param>
2051                 /// <param name="lastKeyEventArgs"></param>
DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)2052                 public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
2053                 {
2054                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
2055 
2056                     if ((e.Button == MouseButtons.Left) && dragInitiated_)
2057                     {
2058                         int diffY = e.Y - lastPoint_.Y;
2059 
2060                         ((PlotSurface2D) ctr).CacheAxes();
2061 
2062                         if (ps.YAxis1 != null)
2063                         {
2064                             Axis axis = ps.YAxis1;
2065                             PointF pMin = ps.PhysicalYAxis1Cache.PhysicalMin;
2066                             PointF pMax = ps.PhysicalYAxis1Cache.PhysicalMax;
2067 
2068                             PointF physicalWorldMin = pMin;
2069                             PointF physicalWorldMax = pMax;
2070                             physicalWorldMin.Y -= diffY;
2071                             physicalWorldMax.Y -= diffY;
2072                             double newWorldMin = axis.PhysicalToWorld(physicalWorldMin, pMin, pMax, false);
2073                             double newWorldMax = axis.PhysicalToWorld(physicalWorldMax, pMin, pMax, false);
2074                             axis.WorldMin = newWorldMin;
2075                             axis.WorldMax = newWorldMax;
2076                         }
2077                         if (ps.YAxis2 != null)
2078                         {
2079                             Axis axis = ps.YAxis2;
2080                             PointF pMin = ps.PhysicalYAxis2Cache.PhysicalMin;
2081                             PointF pMax = ps.PhysicalYAxis2Cache.PhysicalMax;
2082 
2083                             PointF physicalWorldMin = pMin;
2084                             PointF physicalWorldMax = pMax;
2085                             physicalWorldMin.Y -= diffY;
2086                             physicalWorldMax.Y -= diffY;
2087                             double newWorldMin = axis.PhysicalToWorld(physicalWorldMin, pMin, pMax, false);
2088                             double newWorldMax = axis.PhysicalToWorld(physicalWorldMax, pMin, pMax, false);
2089                             axis.WorldMin = newWorldMin;
2090                             axis.WorldMax = newWorldMax;
2091                         }
2092 
2093                         lastPoint_ = new Point(e.X, e.Y);
2094 
2095                         ((PlotSurface2D) ctr).InteractionOccured(this);
2096 
2097                         return true;
2098                     }
2099 
2100                     return false;
2101                 }
2102 
2103                 /// <summary>
2104                 /// </summary>
2105                 /// <param name="e"></param>
2106                 /// <param name="ctr"></param>
DoMouseUp(MouseEventArgs e, Control ctr)2107                 public override bool DoMouseUp(MouseEventArgs e, Control ctr)
2108                 {
2109                     if ((e.Button == MouseButtons.Left) && dragInitiated_)
2110                     {
2111                         lastPoint_ = unset_;
2112                         dragInitiated_ = false;
2113                     }
2114 
2115                     return false;
2116                 }
2117             }
2118 
2119             #endregion
2120 
2121             #region HorizontalRangeSelection
2122 
2123             /// <summary>
2124             /// This plot intraction allows the user to select horizontal regions.
2125             /// </summary>
2126             public class HorizontalRangeSelection : Interaction
2127             {
2128                 private readonly Point unset_ = new Point(-1, -1);
2129                 private Point endPoint_ = new Point(-1, -1);
2130                 private int minimumPixelDistanceForSelect_ = 5;
2131                 private Point previousPoint_ = new Point(-1, -1);
2132                 private bool selectionInitiated_;
2133                 private double smallestAllowedRange_ = double.Epsilon*100.0;
2134                 private Point startPoint_ = new Point(-1, -1);
2135 
2136                 /// <summary>
2137                 /// Default constructor
2138                 /// </summary>
HorizontalRangeSelection()2139                 public HorizontalRangeSelection()
2140                 {
2141                 }
2142 
2143                 /// <summary>
2144                 /// Constructor
2145                 /// </summary>
2146                 /// <param name="smallestAllowedRange">the smallest distance between the selected xmin and xmax for the selection to be performed.</param>
HorizontalRangeSelection(double smallestAllowedRange)2147                 public HorizontalRangeSelection(double smallestAllowedRange)
2148                 {
2149                     smallestAllowedRange_ = smallestAllowedRange;
2150                 }
2151 
2152                 /// <summary>
2153                 /// The minimum width of the selected region (in pixels) for the interaction to zoom.
2154                 /// </summary>
2155                 public int MinimumPixelDistanceForSelect
2156                 {
2157                     get { return minimumPixelDistanceForSelect_; }
2158                     set { minimumPixelDistanceForSelect_ = value; }
2159                 }
2160 
2161                 /// <summary>
2162                 /// The smallest range (distance between world min and world max) selectable.
2163                 /// If a smaller region is selected, the selection will do nothing.
2164                 /// </summary>
2165                 public double SmallestAllowedRange
2166                 {
2167                     get { return smallestAllowedRange_; }
2168                     set { smallestAllowedRange_ = value; }
2169                 }
2170 
2171                 /// <summary>
2172                 /// Handler for mouse down event for this interaction
2173                 /// </summary>
2174                 /// <param name="e">the mouse event args</param>
2175                 /// <param name="ctr">the plot surface this event applies to</param>
DoMouseDown(MouseEventArgs e, Control ctr)2176                 public override bool DoMouseDown(MouseEventArgs e, Control ctr)
2177                 {
2178                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
2179 
2180                     if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < ps.PlotAreaBoundingBoxCache.Right &&
2181                         e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
2182                     {
2183                         // keep track of the start point and flag that select initiated.
2184                         selectionInitiated_ = true;
2185                         startPoint_.X = e.X;
2186                         startPoint_.Y = e.Y;
2187 
2188                         previousPoint_.X = e.X;
2189                         previousPoint_.Y = e.Y;
2190 
2191                         // invalidate the end point
2192                         endPoint_ = unset_;
2193 
2194                         return false;
2195                     }
2196 
2197                     selectionInitiated_ = false;
2198                     endPoint_ = unset_;
2199                     startPoint_ = unset_;
2200                     return false;
2201                 }
2202 
2203                 /// <summary>
2204                 /// Handler for mouse move event for this interaction
2205                 /// </summary>
2206                 /// <param name="e">the mouse event args</param>
2207                 /// <param name="ctr">the plot surface this event applies to</param>
2208                 /// <param name="lastKeyEventArgs"></param>
DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)2209                 public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
2210                 {
2211                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
2212 
2213                     // if dragging on axis to zoom.
2214                     if ((e.Button == MouseButtons.Left) && selectionInitiated_)
2215                     {
2216                         Point endPoint_ = previousPoint_;
2217                         if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < ps.PlotAreaBoundingBoxCache.Right &&
2218                             e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
2219                         {
2220                             endPoint_ = new Point(e.X, e.Y);
2221                             DrawHorizontalSelection(previousPoint_, endPoint_, ctr);
2222                             previousPoint_ = endPoint_;
2223                         }
2224                         else
2225                         {
2226                             endPoint_ = new Point(e.X, e.Y);
2227                             if (e.X < ps.PlotAreaBoundingBoxCache.Left) endPoint_.X = ps.PlotAreaBoundingBoxCache.Left + 1;
2228                             if (e.X > ps.PlotAreaBoundingBoxCache.Right) endPoint_.X = ps.PlotAreaBoundingBoxCache.Right - 1;
2229                             DrawHorizontalSelection(previousPoint_, endPoint_, ctr);
2230                             previousPoint_ = endPoint_;
2231                         }
2232                     }
2233 
2234                     return false;
2235                 }
2236 
2237                 /// <summary>
2238                 /// Handler for mouse up event for this interaction
2239                 /// </summary>
2240                 /// <param name="e">the mouse event args</param>
2241                 /// <param name="ctr">the plot surface this event applies to</param>
DoMouseUp(MouseEventArgs e, Control ctr)2242                 public override bool DoMouseUp(MouseEventArgs e, Control ctr)
2243                 {
2244                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
2245 
2246                     if ((e.Button == MouseButtons.Left) && selectionInitiated_)
2247                     {
2248                         endPoint_.X = e.X;
2249                         endPoint_.Y = e.Y;
2250                         if (e.X < ps.PlotAreaBoundingBoxCache.Left) endPoint_.X = ps.PlotAreaBoundingBoxCache.Left + 1;
2251                         if (e.X > ps.PlotAreaBoundingBoxCache.Right) endPoint_.X = ps.PlotAreaBoundingBoxCache.Right - 1;
2252 
2253                         // flag stopped selecting.
2254                         selectionInitiated_ = false;
2255 
2256                         if (endPoint_ != unset_)
2257                         {
2258                             DrawHorizontalSelection(startPoint_, endPoint_, ctr);
2259                         }
2260 
2261                         // ignore very small selections
2262                         if (Math.Abs(endPoint_.X - startPoint_.X) < minimumPixelDistanceForSelect_)
2263                         {
2264                             return false;
2265                         }
2266 
2267                         ((PlotSurface2D) ctr).CacheAxes();
2268 
2269                         // determine the new x axis 1 world limits (and check to see if they are far enough appart).
2270                         double xAxis1Min = double.NaN;
2271                         double xAxis1Max = double.NaN;
2272                         if (ps.XAxis1 != null)
2273                         {
2274                             int x1 = Math.Min(endPoint_.X, startPoint_.X);
2275                             int x2 = Math.Max(endPoint_.X, startPoint_.X);
2276                             int y = ps.PhysicalXAxis1Cache.PhysicalMax.Y;
2277 
2278                             xAxis1Min = ps.PhysicalXAxis1Cache.PhysicalToWorld(new Point(x1, y), true);
2279                             xAxis1Max = ps.PhysicalXAxis1Cache.PhysicalToWorld(new Point(x2, y), true);
2280                             if (xAxis1Max - xAxis1Min < smallestAllowedRange_)
2281                             {
2282                                 return false;
2283                             }
2284                         }
2285 
2286                         // determine the new x axis 2 world limits (and check to see if they are far enough appart).
2287                         double xAxis2Min = double.NaN;
2288                         double xAxis2Max = double.NaN;
2289                         if (ps.XAxis2 != null)
2290                         {
2291                             int x1 = Math.Min(endPoint_.X, startPoint_.X);
2292                             int x2 = Math.Max(endPoint_.X, startPoint_.X);
2293                             int y = ps.PhysicalXAxis2Cache.PhysicalMax.Y;
2294 
2295                             xAxis2Min = ps.PhysicalXAxis2Cache.PhysicalToWorld(new Point(x1, y), true);
2296                             xAxis2Max = ps.PhysicalXAxis2Cache.PhysicalToWorld(new Point(x2, y), true);
2297                             if (xAxis2Max - xAxis2Min < smallestAllowedRange_)
2298                             {
2299                                 return false;
2300                             }
2301                         }
2302 
2303                         // now actually update the world limits.
2304 
2305                         if (ps.XAxis1 != null)
2306                         {
2307                             ps.XAxis1.WorldMax = xAxis1Max;
2308                             ps.XAxis1.WorldMin = xAxis1Min;
2309                         }
2310 
2311                         if (ps.XAxis2 != null)
2312                         {
2313                             ps.XAxis2.WorldMax = xAxis2Max;
2314                             ps.XAxis2.WorldMin = xAxis2Min;
2315                         }
2316 
2317                         ((PlotSurface2D) ctr).InteractionOccured(this);
2318 
2319                         return true;
2320                     }
2321 
2322                     return false;
2323                 }
2324 
DrawHorizontalSelection(Point start, Point end, Control ctr)2325                 private void DrawHorizontalSelection(Point start, Point end, Control ctr)
2326                 {
2327                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
2328 
2329                     // the clipping rectangle in screen coordinates
2330                     Rectangle clip = ctr.RectangleToScreen(
2331                         new Rectangle(
2332                             ps.PlotAreaBoundingBoxCache.X,
2333                             ps.PlotAreaBoundingBoxCache.Y,
2334                             ps.PlotAreaBoundingBoxCache.Width,
2335                             ps.PlotAreaBoundingBoxCache.Height));
2336 
2337                     start = ctr.PointToScreen(start);
2338                     end = ctr.PointToScreen(end);
2339 
2340                     ControlPaint.FillReversibleRectangle(
2341                         new Rectangle(Math.Min(start.X, end.X), clip.Y, Math.Abs(end.X - start.X), clip.Height),
2342                         Color.White);
2343                 }
2344             }
2345 
2346             #endregion
2347 
2348             #region AxisDrag
2349 
2350             /// <summary>
2351             /// </summary>
2352             public class AxisDrag : Interaction
2353             {
2354                 private readonly bool enableDragWithCtr_;
2355 
2356                 private Axis axis_;
2357                 private bool doing_;
2358                 private Point lastPoint_;
2359                 private PhysicalAxis physicalAxis_;
2360                 private float sensitivity_ = 200.0f;
2361                 private Point startPoint_;
2362 
2363                 /// <summary>
2364                 /// </summary>
2365                 /// <param name="enableDragWithCtr"></param>
AxisDrag(bool enableDragWithCtr)2366                 public AxisDrag(bool enableDragWithCtr)
2367                 {
2368                     enableDragWithCtr_ = enableDragWithCtr;
2369                 }
2370 
2371                 /// <summary>
2372                 /// </summary>
2373                 /// <value></value>
2374                 public float Sensitivity
2375                 {
2376                     get { return sensitivity_; }
2377                     set { sensitivity_ = value; }
2378                 }
2379 
2380                 /// <summary>
2381                 /// </summary>
2382                 /// <param name="e"></param>
2383                 /// <param name="ctr"></param>
DoMouseDown(MouseEventArgs e, Control ctr)2384                 public override bool DoMouseDown(MouseEventArgs e, Control ctr)
2385                 {
2386                     // if the mouse is inside the plot area [the tick marks are here and part of the
2387                     // axis], then don't invoke drag.
2388                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
2389                     if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < ps.PlotAreaBoundingBoxCache.Right &&
2390                         e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
2391                     {
2392                         return false;
2393                     }
2394 
2395                     if ((e.Button == MouseButtons.Left))
2396                     {
2397                         // see if hit with axis.
2398                         ArrayList objects = ps.HitTest(new Point(e.X, e.Y));
2399 
2400                         foreach (object o in objects)
2401                         {
2402                             if (o is Axis)
2403                             {
2404                                 doing_ = true;
2405                                 axis_ = (Axis) o;
2406 
2407                                 //PhysicalAxis[] physicalAxisList = new[]
2408                                 //    {ps.PhysicalXAxis1Cache, ps.PhysicalXAxis2Cache, ps.PhysicalYAxis1Cache, ps.PhysicalYAxis2Cache};
2409 
2410                                 if (ps.PhysicalXAxis1Cache.Axis == axis_)
2411                                     physicalAxis_ = ps.PhysicalXAxis1Cache;
2412                                 else if (ps.PhysicalXAxis2Cache.Axis == axis_)
2413                                     physicalAxis_ = ps.PhysicalXAxis2Cache;
2414                                 else if (ps.PhysicalYAxis1Cache.Axis == axis_)
2415                                     physicalAxis_ = ps.PhysicalYAxis1Cache;
2416                                 else if (ps.PhysicalYAxis2Cache.Axis == axis_)
2417                                     physicalAxis_ = ps.PhysicalYAxis2Cache;
2418 
2419                                 lastPoint_ = startPoint_ = new Point(e.X, e.Y);
2420 
2421                                 return false;
2422                             }
2423                         }
2424                     }
2425 
2426                     return false;
2427                 }
2428 
2429                 /// <summary>
2430                 /// </summary>
2431                 /// <param name="e"></param>
2432                 /// <param name="ctr"></param>
2433                 /// <param name="lastKeyEventArgs"></param>
DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)2434                 public override bool DoMouseMove(MouseEventArgs e, Control ctr, KeyEventArgs lastKeyEventArgs)
2435                 {
2436                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
2437 
2438                     // if dragging on axis to zoom.
2439                     if ((e.Button == MouseButtons.Left) && doing_ && physicalAxis_ != null)
2440                     {
2441                         if (enableDragWithCtr_ && lastKeyEventArgs != null && lastKeyEventArgs.Control)
2442                         {
2443                         }
2444                         else
2445                         {
2446                             float dist =
2447                                 (e.X - lastPoint_.X) +
2448                                 (-e.Y + lastPoint_.Y);
2449 
2450                             lastPoint_ = new Point(e.X, e.Y);
2451 
2452                             if (dist > sensitivity_/3.0f)
2453                             {
2454                                 dist = sensitivity_/3.0f;
2455                             }
2456 
2457                             PointF pMin = physicalAxis_.PhysicalMin;
2458                             PointF pMax = physicalAxis_.PhysicalMax;
2459                             double physicalWorldLength = Math.Sqrt((pMax.X - pMin.X)*(pMax.X - pMin.X) + (pMax.Y - pMin.Y)*(pMax.Y - pMin.Y));
2460 
2461                             float prop = (float) (physicalWorldLength*dist/sensitivity_);
2462                             prop *= 2;
2463 
2464                             ((PlotSurface2D) ctr).CacheAxes();
2465 
2466                             float relativePosX = (startPoint_.X - pMin.X)/(pMax.X - pMin.X);
2467                             float relativePosY = (startPoint_.Y - pMin.Y)/(pMax.Y - pMin.Y);
2468 
2469                             if (float.IsInfinity(relativePosX) || float.IsNaN(relativePosX)) relativePosX = 0.0f;
2470                             if (float.IsInfinity(relativePosY) || float.IsNaN(relativePosY)) relativePosY = 0.0f;
2471 
2472                             PointF physicalWorldMin = pMin;
2473                             PointF physicalWorldMax = pMax;
2474 
2475                             physicalWorldMin.X += relativePosX*prop;
2476                             physicalWorldMax.X -= (1 - relativePosX)*prop;
2477                             physicalWorldMin.Y -= relativePosY*prop;
2478                             physicalWorldMax.Y += (1 - relativePosY)*prop;
2479 
2480                             double newWorldMin = axis_.PhysicalToWorld(physicalWorldMin, pMin, pMax, false);
2481                             double newWorldMax = axis_.PhysicalToWorld(physicalWorldMax, pMin, pMax, false);
2482                             axis_.WorldMin = newWorldMin;
2483                             axis_.WorldMax = newWorldMax;
2484 
2485                             ((PlotSurface2D) ctr).InteractionOccured(this);
2486 
2487                             return true;
2488                         }
2489                     }
2490 
2491                     return false;
2492                 }
2493 
2494                 /// <summary>
2495                 /// </summary>
2496                 /// <param name="e"></param>
2497                 /// <param name="ctr"></param>
DoMouseUp(MouseEventArgs e, Control ctr)2498                 public override bool DoMouseUp(MouseEventArgs e, Control ctr)
2499                 {
2500                     if (doing_)
2501                     {
2502                         doing_ = false;
2503                         axis_ = null;
2504                         physicalAxis_ = null;
2505                         lastPoint_ = new Point();
2506                     }
2507 
2508                     return false;
2509                 }
2510             }
2511 
2512             #endregion
2513 
2514             #region MouseWheelZoom
2515 
2516             /// <summary>
2517             /// </summary>
2518             public class MouseWheelZoom : Interaction
2519             {
2520                 //private Point point_ = new Point(-1, -1);
2521                 private float sensitivity_ = 60.0f;
2522 
2523                 /// <summary>
2524                 /// Number of screen pixels equivalent to one wheel step.
2525                 /// </summary>
2526                 public float Sensitivity
2527                 {
2528                     get { return sensitivity_; }
2529                     set { sensitivity_ = value; }
2530                 }
2531 
2532                 //private bool mouseDown_ = false;
2533 
2534                 /// <summary>
2535                 /// </summary>
2536                 /// <param name="e"></param>
2537                 /// <param name="ctr"></param>
DoMouseUp(MouseEventArgs e, Control ctr)2538                 public override bool DoMouseUp(MouseEventArgs e, Control ctr)
2539                 {
2540                     //mouseDown_ = false;
2541                     return false;
2542                 }
2543 
2544                 /// <summary>
2545                 /// </summary>
2546                 /// <param name="e"></param>
2547                 /// <param name="ctr"></param>
DoMouseDown(MouseEventArgs e, Control ctr)2548                 public override bool DoMouseDown(MouseEventArgs e, Control ctr)
2549                 {
2550                     //NPlot.PlotSurface2D ps = ((Windows.PlotSurface2D)ctr).Inner;
2551 
2552                     //if (e.X > ps.PlotAreaBoundingBoxCache.Left && e.X < ps.PlotAreaBoundingBoxCache.Right &&
2553                     //    e.Y > ps.PlotAreaBoundingBoxCache.Top && e.Y < ps.PlotAreaBoundingBoxCache.Bottom)
2554                     //{
2555                     //    point_.X = e.X;
2556                     //    point_.Y = e.Y;
2557                     //    mouseDown_ = true;
2558                     //}
2559                     return false;
2560                 }
2561 
2562                 /// <summary>
2563                 /// </summary>
2564                 /// <param name="e"></param>
2565                 /// <param name="ctr"></param>
DoMouseWheel(MouseEventArgs e, Control ctr)2566                 public override bool DoMouseWheel(MouseEventArgs e, Control ctr)
2567                 {
2568                     NPlot.PlotSurface2D ps = ((PlotSurface2D) ctr).Inner;
2569 
2570                     ((PlotSurface2D) ctr).CacheAxes();
2571 
2572                     float delta = e.Delta/(float) e.Delta;
2573                     delta *= sensitivity_;
2574 
2575                     Axis axis = null;
2576                     PointF pMin = PointF.Empty;
2577                     PointF pMax = PointF.Empty;
2578                     KeyEventArgs keyArgs = ((PlotSurface2D) ctr).lastKeyEventArgs_;
2579                     bool zoom = (keyArgs != null && keyArgs.Control);
2580 
2581                     if (keyArgs != null && keyArgs.Shift)
2582                     {
2583                         axis = ps.YAxis1;
2584                         pMin = ps.PhysicalYAxis1Cache.PhysicalMin;
2585                         pMax = ps.PhysicalYAxis1Cache.PhysicalMax;
2586                     }
2587                     else
2588                     {
2589                         axis = ps.XAxis1;
2590                         pMin = ps.PhysicalXAxis1Cache.PhysicalMin;
2591                         pMax = ps.PhysicalXAxis1Cache.PhysicalMax;
2592                     }
2593                     if (axis == null) return false;
2594 
2595                     PointF physicalWorldMin = pMin;
2596                     PointF physicalWorldMax = pMax;
2597                     physicalWorldMin.X -= delta;
2598                     physicalWorldMax.X -= (zoom) ? -delta : delta;
2599                     physicalWorldMin.Y += delta;
2600                     physicalWorldMax.Y += (zoom) ? -delta : delta;
2601                     double newWorldMin = axis.PhysicalToWorld(physicalWorldMin, pMin, pMax, false);
2602                     double newWorldMax = axis.PhysicalToWorld(physicalWorldMax, pMin, pMax, false);
2603                     axis.WorldMin = newWorldMin;
2604                     axis.WorldMax = newWorldMax;
2605 
2606                     ((PlotSurface2D) ctr).InteractionOccured(this);
2607 
2608                     return true;
2609                 }
2610             }
2611 
2612             #endregion
2613         }
2614     }
2615 }