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 }