1 //------------------------------------------------------------- 2 // <copyright company=�Microsoft Corporation�> 3 // Copyright � Microsoft Corporation. All Rights Reserved. 4 // </copyright> 5 //------------------------------------------------------------- 6 // @owner=alexgor, deliant 7 //================================================================= 8 // File: ChartAreaCursor.cs 9 // 10 // Namespace: System.Web.UI.WebControls[Windows.Forms].Charting 11 // 12 // Classes: Cursor, CursorEventArgs 13 // 14 // Purpose: A cursor is a horizontal or vertical line that 15 // defines a position along an axis. A range selection 16 // is a range along an axis that is defined by a beginning 17 // and end position, and is displayed using a semi-transparent 18 // color. 19 // 20 // Both cursors and range selections are implemented by the 21 // Cursor class, which is exposed as the CursorX and CursorY 22 // properties of the ChartArea object. The CursorX object is 23 // for the X axis of a chart area, and the CursorY object is 24 // for the Y axis. The AxisType property of these objects 25 // determines if the associated axis is primary or secondary. 26 // 27 // Cursors and range selections can be set via end-user 28 // interaction and programmatically. 29 // 30 // NOTE: ASP.NET version of the chart uses this class only 31 // for appearance and position properties. Drawing of the 32 // selection and cursor is implemented through client side 33 // java script. 34 // 35 // Reviewed: AG - August 8, 2002 36 // AG - March 16, 2007 37 // 38 //=================================================================== 39 40 #if WINFORMS_CONTROL 41 42 #region Used Namespaces 43 44 using System; 45 using System.Drawing; 46 using System.Drawing.Drawing2D; 47 using System.ComponentModel; 48 using System.Collections; 49 using System.Windows.Forms.DataVisualization.Charting; 50 using System.Windows.Forms.DataVisualization.Charting.Data; 51 using System.Windows.Forms.DataVisualization.Charting.ChartTypes; 52 using System.Windows.Forms.DataVisualization.Charting.Utilities; 53 using System.Windows.Forms.DataVisualization.Charting.Borders3D; 54 using System.Drawing.Design; 55 using System.Collections.Generic; 56 57 #endregion 58 59 namespace System.Windows.Forms.DataVisualization.Charting 60 { 61 /// <summary> 62 /// The Cursor class is responsible for chart axes cursor and selection 63 /// functionality. It contains properties which define visual appearance, 64 /// position and behavior settings. It also contains methods for 65 /// drawing cursor and selection in the plotting area. 66 /// </summary> 67 [ 68 DefaultProperty("Enabled"), 69 SRDescription("DescriptionAttributeCursor_Cursor"), 70 ] 71 public class Cursor : IDisposable 72 { 73 #region Cursor constructors and initialization 74 75 /// <summary> 76 /// Public constructor 77 /// </summary> Cursor()78 public Cursor() 79 { 80 } 81 82 /// <summary> 83 /// Initialize cursor class. 84 /// </summary> 85 /// <param name="chartArea">Chart area the cursor belongs to.</param> 86 /// <param name="attachedToXAxis">Indicates which axes should be used X or Y.</param> Initialize(ChartArea chartArea, AxisName attachedToXAxis)87 internal void Initialize(ChartArea chartArea, AxisName attachedToXAxis) 88 { 89 // Set chart are reference 90 this._chartArea = chartArea; 91 92 // Attach cursor to specified axis 93 this._attachedToXAxis = attachedToXAxis; 94 } 95 96 #endregion 97 98 #region Cursor fields 99 100 // Reference to the chart area object the cursor belongs to 101 private ChartArea _chartArea = null; 102 103 // Defines which axis the cursor attached to X or Y 104 private AxisName _attachedToXAxis = AxisName.X; 105 106 // Enables/Disables chart area cursor. 107 private bool _isUserEnabled = false; 108 109 // Enables/Disables chart area selection. 110 private bool _isUserSelectionEnabled = false; 111 112 // Indicates that cursor will automatically scroll the area scaleView if necessary. 113 private bool _autoScroll = true; 114 115 // Cursor line color 116 private Color _lineColor = Color.Red; 117 118 // Cursor line width 119 private int _lineWidth = 1; 120 121 // Cursor line style 122 private ChartDashStyle _lineDashStyle = ChartDashStyle.Solid; 123 124 // Chart area selection color 125 private Color _selectionColor = Color.LightGray; 126 127 // AxisName of the axes (primary/secondary) the cursor is attached to 128 private AxisType _axisType = AxisType.Primary; 129 130 // Cursor position 131 private double _position = Double.NaN; 132 133 // Range selection start position. 134 private double _selectionStart = Double.NaN; 135 136 // Range selection end position. 137 private double _selectionEnd = Double.NaN; 138 139 // Cursor movement interval current & original values 140 private double _interval = 1; 141 142 // Cursor movement interval type 143 private DateTimeIntervalType _intervalType = DateTimeIntervalType.Auto; 144 145 // Cursor movement interval offset current & original values 146 private double _intervalOffset = 0; 147 148 // Cursor movement interval offset type 149 private DateTimeIntervalType _intervalOffsetType = DateTimeIntervalType.Auto; 150 151 // Reference to the axis obhect 152 private Axis _axis = null; 153 154 // User selection start point 155 private PointF _userSelectionStart = PointF.Empty; 156 157 // Indicates that selection must be drawn 158 private bool _drawSelection = true; 159 160 // Indicates that events must be fired when position/selection is changed 161 private bool _fireUserChangingEvent = false; 162 163 // Indicates that XXXChanged events must be fired when position/selection is changed 164 private bool _fireUserChangedEvent = false; 165 166 // Scroll size and direction when AutoScroll is set 167 private MouseEventArgs _mouseMoveArguments = null; 168 169 // Timer used to scroll the data while selecting 170 private System.Windows.Forms.Timer _scrollTimer = new System.Windows.Forms.Timer(); 171 172 // Indicates that axis data scaleView was scrolled as a result of the mouse move event 173 private bool _viewScrolledOnMouseMove = false; 174 175 #endregion 176 177 #region Cursor "Behavior" public properties. 178 179 /// <summary> 180 /// Gets or sets the position of a cursor. 181 /// </summary> 182 [ 183 SRCategory("CategoryAttributeBehavior"), 184 Bindable(true), 185 DefaultValue(Double.NaN), 186 SRDescription("DescriptionAttributeCursor_Position"), 187 ParenthesizePropertyNameAttribute(true), 188 TypeConverter(typeof(DoubleDateNanValueConverter)), 189 ] 190 public double Position 191 { 192 get 193 { 194 return _position; 195 } 196 set 197 { 198 if(_position != value) 199 { 200 _position = value; 201 202 // Align cursor in connected areas 203 if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null) 204 { 205 if(!this._chartArea.alignmentInProcess) 206 { 207 AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ? 208 AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal; 209 this._chartArea.Common.ChartPicture.AlignChartAreasCursor(this._chartArea, orientation, false); 210 } 211 } 212 213 if(this._chartArea != null && !this._chartArea.alignmentInProcess) 214 { 215 this.Invalidate(false); 216 } 217 218 } 219 } 220 } 221 222 /// <summary> 223 /// Gets or sets the starting position of a cursor's selected range. 224 /// </summary> 225 [ 226 SRCategory("CategoryAttributeBehavior"), 227 Bindable(true), 228 DefaultValue(Double.NaN), 229 SRDescription("DescriptionAttributeCursor_SelectionStart"), 230 TypeConverter(typeof(DoubleDateNanValueConverter)), 231 ] 232 public double SelectionStart 233 { 234 get 235 { 236 return _selectionStart; 237 } 238 set 239 { 240 if(_selectionStart != value) 241 { 242 _selectionStart = value; 243 244 // Align cursor in connected areas 245 if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null) 246 { 247 if(!this._chartArea.alignmentInProcess) 248 { 249 AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ? 250 AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal; 251 this._chartArea.Common.ChartPicture.AlignChartAreasCursor(this._chartArea, orientation, false); 252 } 253 } 254 255 if(this._chartArea != null && !this._chartArea.alignmentInProcess) 256 { 257 this.Invalidate(false); 258 } 259 260 } 261 } 262 } 263 264 /// <summary> 265 /// Gets or sets the ending position of a range selection. 266 /// </summary> 267 [ 268 SRCategory("CategoryAttributeBehavior"), 269 Bindable(true), 270 DefaultValue(Double.NaN), 271 SRDescription("DescriptionAttributeCursor_SelectionEnd"), 272 TypeConverter(typeof(DoubleDateNanValueConverter)), 273 ] 274 public double SelectionEnd 275 { 276 get 277 { 278 return _selectionEnd; 279 } 280 set 281 { 282 if(_selectionEnd != value) 283 { 284 _selectionEnd = value; 285 286 // Align cursor in connected areas 287 if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null) 288 { 289 if(!this._chartArea.alignmentInProcess) 290 { 291 AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ? 292 AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal; 293 this._chartArea.Common.ChartPicture.AlignChartAreasCursor(this._chartArea, orientation, false); 294 } 295 } 296 297 if(this._chartArea != null && !this._chartArea.alignmentInProcess) 298 { 299 this.Invalidate(false); 300 } 301 } 302 } 303 } 304 305 /// <summary> 306 /// Gets or sets a property that enables or disables the cursor interface. 307 /// </summary> 308 [ 309 SRCategory("CategoryAttributeBehavior"), 310 Bindable(true), 311 DefaultValue(false), 312 SRDescription("DescriptionAttributeCursor_UserEnabled"), 313 ] 314 public bool IsUserEnabled 315 { 316 get 317 { 318 return _isUserEnabled; 319 } 320 set 321 { 322 _isUserEnabled = value; 323 } 324 } 325 326 /// <summary> 327 /// Gets or sets a property that enables or disables the range selection interface. 328 /// </summary> 329 [ 330 SRCategory("CategoryAttributeBehavior"), 331 Bindable(true), 332 DefaultValue(false), 333 SRDescription("DescriptionAttributeCursor_UserSelection"), 334 ] 335 public bool IsUserSelectionEnabled 336 { 337 get 338 { 339 return _isUserSelectionEnabled; 340 } 341 set 342 { 343 _isUserSelectionEnabled = value; 344 } 345 } 346 347 /// <summary> 348 /// Determines if scrolling will occur if a range selection operation 349 /// extends beyond a boundary of the chart area. 350 /// </summary> 351 [ 352 SRCategory("CategoryAttributeBehavior"), 353 Bindable(true), 354 DefaultValue(true), 355 SRDescription("DescriptionAttributeCursor_AutoScroll"), 356 ] 357 public bool AutoScroll 358 { 359 get 360 { 361 return _autoScroll; 362 } 363 set 364 { 365 _autoScroll = value; 366 } 367 } 368 369 /// <summary> 370 /// Gets or sets the type of axis that the cursor is attached to. 371 /// </summary> 372 [ 373 SRCategory("CategoryAttributeBehavior"), 374 Bindable(true), 375 SRDescription("DescriptionAttributeCursor_AxisType"), 376 DefaultValue(AxisType.Primary) 377 ] 378 public AxisType AxisType 379 { 380 get 381 { 382 return _axisType; 383 } 384 set 385 { 386 _axisType = value; 387 388 // Reset reference to the axis object 389 _axis = null; 390 391 this.Invalidate(true); 392 } 393 } 394 395 /// <summary> 396 /// Gets or sets the cursor movement interval. 397 /// </summary> 398 [ 399 SRCategory("CategoryAttributeBehavior"), 400 Bindable(true), 401 DefaultValue(1.0), 402 SRDescription("DescriptionAttributeCursor_Interval"), 403 ] 404 public double Interval 405 { 406 get 407 { 408 return _interval; 409 } 410 set 411 { 412 _interval = value; 413 } 414 } 415 416 /// <summary> 417 /// Gets or sets the unit of measurement of the Interval property. 418 /// </summary> 419 [ 420 SRCategory("CategoryAttributeBehavior"), 421 Bindable(true), 422 DefaultValue(DateTimeIntervalType.Auto), 423 SRDescription("DescriptionAttributeCursor_IntervalType") 424 ] 425 public DateTimeIntervalType IntervalType 426 { 427 get 428 { 429 return _intervalType; 430 } 431 set 432 { 433 _intervalType = (value != DateTimeIntervalType.NotSet) ? value : DateTimeIntervalType.Auto; 434 } 435 } 436 437 438 /// <summary> 439 /// Gets or sets the interval offset, which determines 440 /// where to draw the cursor and range selection. 441 /// </summary> 442 [ 443 SRCategory("CategoryAttributeBehavior"), 444 Bindable(true), 445 DefaultValue(0.0), 446 SRDescription("DescriptionAttributeCursor_IntervalOffset"), 447 ] 448 public double IntervalOffset 449 { 450 get 451 { 452 return _intervalOffset; 453 } 454 set 455 { 456 // Validation 457 if( value < 0.0 ) 458 { 459 throw (new ArgumentException(SR.ExceptionCursorIntervalOffsetIsNegative, "value")); 460 } 461 462 _intervalOffset = value; 463 } 464 } 465 466 /// <summary> 467 /// Gets or sets the unit of measurement of the IntervalOffset property. 468 /// </summary> 469 [ 470 SRCategory("CategoryAttributeBehavior"), 471 Bindable(true), 472 DefaultValue(DateTimeIntervalType.Auto), 473 SRDescription("DescriptionAttributeCursor_IntervalOffsetType"), 474 ] 475 public DateTimeIntervalType IntervalOffsetType 476 { 477 get 478 { 479 return _intervalOffsetType; 480 } 481 set 482 { 483 _intervalOffsetType = (value != DateTimeIntervalType.NotSet) ? value : DateTimeIntervalType.Auto; 484 } 485 } 486 #endregion 487 488 #region Cursor "Appearance" public properties 489 490 /// <summary> 491 /// Gets or sets the color the cursor line. 492 /// </summary> 493 [ 494 SRCategory("CategoryAttributeAppearance"), 495 Bindable(true), 496 DefaultValue(typeof(Color), "Red"), 497 SRDescription("DescriptionAttributeLineColor"), 498 TypeConverter(typeof(ColorConverter)), 499 Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base), 500 ] 501 public Color LineColor 502 { 503 get 504 { 505 return _lineColor; 506 } 507 set 508 { 509 _lineColor = value; 510 this.Invalidate(false); 511 } 512 } 513 514 /// <summary> 515 /// Gets or sets the style of the cursor line. 516 /// </summary> 517 [ 518 SRCategory("CategoryAttributeAppearance"), 519 Bindable(true), 520 DefaultValue(ChartDashStyle.Solid), 521 SRDescription("DescriptionAttributeLineDashStyle"), 522 ] 523 public ChartDashStyle LineDashStyle 524 { 525 get 526 { 527 return _lineDashStyle; 528 } 529 set 530 { 531 _lineDashStyle = value; 532 this.Invalidate(false); 533 } 534 } 535 536 /// <summary> 537 /// Gets or sets the width of the cursor line. 538 /// </summary> 539 [ 540 SRCategory("CategoryAttributeAppearance"), 541 Bindable(true), 542 DefaultValue(1), 543 SRDescription("DescriptionAttributeLineWidth"), 544 ] 545 public int LineWidth 546 { 547 get 548 { 549 return _lineWidth; 550 } 551 set 552 { 553 if(value < 0) 554 { 555 throw (new ArgumentOutOfRangeException("value", SR.ExceptionCursorLineWidthIsNegative)); 556 } 557 _lineWidth = value; 558 this.Invalidate(true); 559 } 560 } 561 562 /// <summary> 563 /// Gets or sets a semi-transparent color that highlights a range of data. 564 /// </summary> 565 [ 566 SRCategory("CategoryAttributeAppearance"), 567 Bindable(true), 568 DefaultValue(typeof(Color), "LightGray"), 569 SRDescription("DescriptionAttributeCursor_SelectionColor"), 570 TypeConverter(typeof(ColorConverter)), 571 Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base), 572 ] 573 public Color SelectionColor 574 { 575 get 576 { 577 return _selectionColor; 578 } 579 set 580 { 581 _selectionColor = value; 582 this.Invalidate(false); 583 } 584 } 585 586 #endregion 587 588 #region Cursor painting methods 589 590 /// <summary> 591 /// Draws chart area cursor and selection. 592 /// </summary> 593 /// <param name="graph">Reference to the ChartGraphics object.</param> Paint( ChartGraphics graph )594 internal void Paint( ChartGraphics graph ) 595 { 596 //*************************************************** 597 //** Prepare for drawing 598 //*************************************************** 599 600 // Do not proceed with painting if cursor is not attached to the axis 601 if(this.GetAxis() == null || 602 this._chartArea == null || 603 this._chartArea.Common == null || 604 this._chartArea.Common.ChartPicture == null || 605 this._chartArea.Common.ChartPicture.isPrinting) 606 { 607 return; 608 } 609 610 // Get plot area position 611 RectangleF plotAreaPosition = this._chartArea.PlotAreaPosition.ToRectangleF(); 612 613 // Detect if cursor is horizontal or vertical 614 bool horizontal = true; 615 if(this.GetAxis().AxisPosition == AxisPosition.Bottom || this.GetAxis().AxisPosition == AxisPosition.Top) 616 { 617 horizontal = false; 618 } 619 620 //*************************************************** 621 //** Draw selection 622 //*************************************************** 623 624 // Check if selection need to be drawn 625 if(this._drawSelection && 626 !double.IsNaN(this.SelectionStart) && 627 !double.IsNaN(this.SelectionEnd) && 628 this.SelectionColor != Color.Empty) 629 { 630 // Calculate selection rectangle 631 RectangleF rectSelection = GetSelectionRect(plotAreaPosition); 632 rectSelection.Intersect(plotAreaPosition); 633 634 // Get opposite axis selection rectangle 635 RectangleF rectOppositeSelection = GetOppositeSelectionRect(plotAreaPosition); 636 637 // Draw selection if rectangle is not empty 638 if(!rectSelection.IsEmpty && rectSelection.Width > 0 && rectSelection.Height > 0) 639 { 640 // Limit selection rectangle to the area of the opposite selection 641 if(!rectOppositeSelection.IsEmpty && rectOppositeSelection.Width > 0 && rectOppositeSelection.Height > 0) 642 { 643 rectSelection.Intersect(rectOppositeSelection); 644 645 // We do not need to draw selection in the opposite axis 646 Cursor oppositeCursor = 647 (_attachedToXAxis == AxisName.X || _attachedToXAxis == AxisName.X2) ? 648 _chartArea.CursorY : _chartArea.CursorX; 649 oppositeCursor._drawSelection = false; 650 } 651 652 // Make sure selection is inside plotting area 653 rectSelection.Intersect(plotAreaPosition); 654 655 // If selection rectangle is not empty 656 if(rectSelection.Width > 0 && rectSelection.Height > 0) 657 { 658 // Add transparency to solid colors 659 Color rangeSelectionColor = this.SelectionColor; 660 if(rangeSelectionColor.A == 255) 661 { 662 rangeSelectionColor = Color.FromArgb(120, rangeSelectionColor); 663 } 664 665 // Draw selection 666 graph.FillRectangleRel( rectSelection, 667 rangeSelectionColor, 668 ChartHatchStyle.None, 669 "", 670 ChartImageWrapMode.Tile, 671 Color.Empty, 672 ChartImageAlignmentStyle.Center, 673 GradientStyle.None, 674 Color.Empty, 675 Color.Empty, 676 0, 677 ChartDashStyle.NotSet, 678 Color.Empty, 679 0, 680 PenAlignment.Inset ); 681 } 682 } 683 } 684 685 //*************************************************** 686 //** Draw cursor 687 //*************************************************** 688 689 // Check if cursor need to be drawn 690 if(!double.IsNaN(this.Position) && 691 this.LineColor != Color.Empty && 692 this.LineWidth > 0 && 693 this.LineDashStyle != ChartDashStyle.NotSet) 694 { 695 // Calculate line position 696 bool insideArea = false; 697 PointF point1 = PointF.Empty; 698 PointF point2 = PointF.Empty; 699 if(horizontal) 700 { 701 // Set cursor coordinates 702 point1.X = plotAreaPosition.X; 703 point1.Y = (float)this.GetAxis().GetLinearPosition(this.Position); 704 point2.X = plotAreaPosition.Right; 705 point2.Y = point1.Y; 706 707 // Check if cursor is inside plotting rect 708 if(point1.Y >= plotAreaPosition.Y && point1.Y <= plotAreaPosition.Bottom) 709 { 710 insideArea = true; 711 } 712 } 713 else 714 { 715 // Set cursor coordinates 716 point1.X = (float)this.GetAxis().GetLinearPosition(this.Position); 717 point1.Y = plotAreaPosition.Y; 718 point2.X = point1.X; 719 point2.Y = plotAreaPosition.Bottom; 720 721 // Check if cursor is inside plotting rect 722 if(point1.X >= plotAreaPosition.X && point1.X <= plotAreaPosition.Right) 723 { 724 insideArea = true; 725 } 726 } 727 728 // Draw cursor if it's inside the chart area plotting rectangle 729 if(insideArea) 730 { 731 graph.DrawLineRel(this.LineColor, this.LineWidth, this.LineDashStyle, point1, point2); 732 } 733 } 734 // Reset draw selection flag 735 this._drawSelection = true; 736 } 737 738 #endregion 739 740 #region Cursor position setting methods 741 742 /// <summary> 743 /// This method sets the position of a cursor within a chart area at a given axis value. 744 /// </summary> 745 /// <param name="newPosition">The new position of the cursor. Measured as a value along the relevant axis.</param> SetCursorPosition(double newPosition)746 public void SetCursorPosition(double newPosition) 747 { 748 // Check if we are setting different value 749 if(this.Position != newPosition) 750 { 751 double newRoundedPosition = RoundPosition(newPosition); 752 // Send PositionChanging event 753 if(_fireUserChangingEvent && GetChartObject() != null) 754 { 755 CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), newRoundedPosition); 756 GetChartObject().OnCursorPositionChanging(arguments); 757 758 // Check if position values were changed in the event 759 newRoundedPosition = arguments.NewPosition; 760 } 761 762 // Change position 763 this.Position = newRoundedPosition; 764 765 // Send PositionChanged event 766 if(_fireUserChangedEvent && GetChartObject() != null) 767 { 768 CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), this.Position); 769 GetChartObject().OnCursorPositionChanged(arguments); 770 } 771 } 772 } 773 774 775 /// <summary> 776 /// This method displays a cursor at the specified position. Measured in pixels. 777 /// </summary> 778 /// <param name="point">A PointF structure that specifies where the cursor will be drawn.</param> 779 /// <param name="roundToBoundary">If true, the cursor will be drawn along the nearest chart area boundary 780 /// when the specified position does not fall within a ChartArea object.</param> SetCursorPixelPosition(PointF point, bool roundToBoundary)781 public void SetCursorPixelPosition(PointF point, bool roundToBoundary) 782 { 783 if(this._chartArea != null && this._chartArea.Common != null && this.GetAxis() != null) 784 { 785 PointF relativeCoord = GetPositionInPlotArea(point, roundToBoundary); 786 if(!relativeCoord.IsEmpty) 787 { 788 // Get new cursor position 789 double newCursorPosition = PositionToCursorPosition(relativeCoord); 790 791 // Set new cursor & selection position 792 this.SetCursorPosition(newCursorPosition); 793 } 794 } 795 } 796 797 /// <summary> 798 /// This method sets the position of a selected range within a chart area at given axis values. 799 /// </summary> 800 /// <param name="newStart">The new starting position of the range selection. Measured as a value along the relevant axis..</param> 801 /// <param name="newEnd">The new ending position of the range selection. Measured as a value along the relevant axis.</param> SetSelectionPosition(double newStart, double newEnd)802 public void SetSelectionPosition(double newStart, double newEnd) 803 { 804 // Check if we are setting different value 805 if(this.SelectionStart != newStart || this.SelectionEnd != newEnd) 806 { 807 // Send PositionChanging event 808 double newRoundedSelectionStart = RoundPosition(newStart); 809 double newRoundedSelectionEnd = RoundPosition(newEnd); 810 811 // Send SelectionRangeChanging event 812 if(_fireUserChangingEvent && GetChartObject() != null) 813 { 814 CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), newRoundedSelectionStart, newRoundedSelectionEnd); 815 GetChartObject().OnSelectionRangeChanging(arguments); 816 817 // Check if position values were changed in the event 818 newRoundedSelectionStart = arguments.NewSelectionStart; 819 newRoundedSelectionEnd = arguments.NewSelectionEnd; 820 } 821 822 // Change selection position 823 this._selectionStart = newRoundedSelectionStart; 824 this._selectionEnd = newRoundedSelectionEnd; 825 826 // Align cursor in connected areas 827 if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null) 828 { 829 if(!this._chartArea.alignmentInProcess) 830 { 831 AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ? 832 AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal; 833 this._chartArea.Common.ChartPicture.AlignChartAreasCursor(this._chartArea, orientation, true); 834 } 835 } 836 837 if(this._chartArea != null && !this._chartArea.alignmentInProcess) 838 { 839 this.Invalidate(false); 840 } 841 842 // Send SelectionRangeChanged event 843 if(_fireUserChangedEvent && GetChartObject() != null) 844 { 845 CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), this.SelectionStart, this.SelectionEnd); 846 GetChartObject().OnSelectionRangeChanged(arguments); 847 } 848 } 849 } 850 851 852 /// <summary> 853 /// This method sets the starting and ending positions of a range selection. 854 /// </summary> 855 /// <param name="startPoint">A PointF structure that specifies where the range selection begins.</param> 856 /// <param name="endPoint">A PointF structure that specifies where the range selection ends</param> 857 /// <param name="roundToBoundary">If true, the starting and ending points will be rounded to the nearest chart area boundary 858 /// when the specified positions do not fall within a ChartArea object.</param> SetSelectionPixelPosition(PointF startPoint, PointF endPoint, bool roundToBoundary)859 public void SetSelectionPixelPosition(PointF startPoint, PointF endPoint, bool roundToBoundary) 860 { 861 if(this._chartArea != null && this._chartArea.Common != null && this.GetAxis() != null) 862 { 863 // Calculating the start position 864 double newStart = this.SelectionStart; 865 if(!startPoint.IsEmpty) 866 { 867 PointF relativeCoord = GetPositionInPlotArea(startPoint, roundToBoundary); 868 if(!relativeCoord.IsEmpty) 869 { 870 // Get new selection start position 871 newStart = PositionToCursorPosition(relativeCoord); 872 } 873 } 874 875 // Setting the end position 876 double newEnd = newStart; 877 if(!endPoint.IsEmpty) 878 { 879 PointF relativeCoord = GetPositionInPlotArea(endPoint, roundToBoundary); 880 if(!relativeCoord.IsEmpty) 881 { 882 // Get new selection position 883 newEnd = PositionToCursorPosition(relativeCoord); 884 } 885 } 886 887 // Set new selection start & end position 888 this.SetSelectionPosition(newStart, newEnd); 889 } 890 } 891 892 #endregion 893 894 #region Position rounding methods 895 896 /// <summary> 897 /// Rounds new position of the cursor or range selection 898 /// </summary> 899 /// <param name="cursorPosition"></param> 900 /// <returns></returns> RoundPosition(double cursorPosition)901 internal double RoundPosition(double cursorPosition) 902 { 903 double roundedPosition = cursorPosition; 904 905 if(!double.IsNaN(roundedPosition)) 906 { 907 // Check if position rounding is required 908 if(this.GetAxis() != null && 909 this.Interval != 0 && 910 !double.IsNaN(this.Interval)) 911 { 912 // Get first series attached to this axis 913 Series axisSeries = null; 914 if(_axis.axisType == AxisName.X || _axis.axisType == AxisName.X2) 915 { 916 List<string> seriesArray = _axis.ChartArea.GetXAxesSeries((_axis.axisType == AxisName.X) ? AxisType.Primary : AxisType.Secondary, _axis.SubAxisName); 917 if(seriesArray.Count > 0) 918 { 919 string seriesName = seriesArray[0] as string; 920 axisSeries = _axis.Common.DataManager.Series[seriesName]; 921 if(axisSeries != null && !axisSeries.IsXValueIndexed) 922 { 923 axisSeries = null; 924 } 925 } 926 } 927 928 // If interval type is not set - use number 929 DateTimeIntervalType intervalType = 930 (this.IntervalType == DateTimeIntervalType.Auto) ? 931 DateTimeIntervalType.Number : this.IntervalType; 932 933 // If interval offset type is not set - use interval type 934 DateTimeIntervalType offsetType = 935 (this.IntervalOffsetType == DateTimeIntervalType.Auto) ? 936 intervalType : this.IntervalOffsetType; 937 938 // Round numbers 939 if(intervalType == DateTimeIntervalType.Number) 940 { 941 double newRoundedPosition = Math.Round(roundedPosition / this.Interval) * this.Interval; 942 943 // Add offset number 944 if(this.IntervalOffset != 0 && 945 !double.IsNaN(IntervalOffset) && 946 offsetType != DateTimeIntervalType.Auto) 947 { 948 if(this.IntervalOffset > 0) 949 { 950 newRoundedPosition += ChartHelper.GetIntervalSize(newRoundedPosition, this.IntervalOffset, offsetType); 951 } 952 else 953 { 954 newRoundedPosition -= ChartHelper.GetIntervalSize(newRoundedPosition, this.IntervalOffset, offsetType); 955 } 956 } 957 958 // Find rounded position after/before the current 959 double nextPosition = newRoundedPosition; 960 if(newRoundedPosition <= cursorPosition) 961 { 962 nextPosition += ChartHelper.GetIntervalSize(newRoundedPosition, this.Interval, intervalType, axisSeries, 0, DateTimeIntervalType.Number, true); 963 } 964 else 965 { 966 nextPosition -= ChartHelper.GetIntervalSize(newRoundedPosition, this.Interval, intervalType, axisSeries, 0, DateTimeIntervalType.Number, true); 967 } 968 969 // Choose closest rounded position 970 if(Math.Abs(nextPosition - cursorPosition) > Math.Abs(cursorPosition - newRoundedPosition)) 971 { 972 roundedPosition = newRoundedPosition; 973 } 974 else 975 { 976 roundedPosition = nextPosition; 977 } 978 979 } 980 981 // Round date/time 982 else 983 { 984 // Find one rounded position prior and one after current position 985 // Adjust start position depending on the interval and type 986 double prevPosition = ChartHelper.AlignIntervalStart(cursorPosition, this.Interval, intervalType, axisSeries); 987 988 // Adjust start position depending on the interval offset and offset type 989 if( IntervalOffset != 0 && axisSeries == null) 990 { 991 if(this.IntervalOffset > 0) 992 { 993 prevPosition += ChartHelper.GetIntervalSize( 994 prevPosition, 995 this.IntervalOffset, 996 offsetType, 997 axisSeries, 998 0, 999 DateTimeIntervalType.Number, 1000 true); 1001 } 1002 else 1003 { 1004 prevPosition += ChartHelper.GetIntervalSize( 1005 prevPosition, 1006 -this.IntervalOffset, 1007 offsetType, 1008 axisSeries, 1009 0, 1010 DateTimeIntervalType.Number, 1011 true); 1012 } 1013 } 1014 1015 // Find rounded position after/before the current 1016 double nextPosition = prevPosition; 1017 if(prevPosition <= cursorPosition) 1018 { 1019 nextPosition += ChartHelper.GetIntervalSize(prevPosition, this.Interval, intervalType, axisSeries, 0, DateTimeIntervalType.Number, true); 1020 } 1021 else 1022 { 1023 nextPosition -= ChartHelper.GetIntervalSize(prevPosition, this.Interval, intervalType, axisSeries, 0, DateTimeIntervalType.Number, true); 1024 } 1025 1026 // Choose closest rounded position 1027 if(Math.Abs(nextPosition - cursorPosition) > Math.Abs(cursorPosition - prevPosition)) 1028 { 1029 roundedPosition = prevPosition; 1030 } 1031 else 1032 { 1033 roundedPosition = nextPosition; 1034 } 1035 } 1036 } 1037 } 1038 1039 return roundedPosition; 1040 } 1041 #endregion 1042 1043 #region Mouse events handling for the Cursor 1044 1045 /// <summary> 1046 /// Mouse down event handler. 1047 /// </summary> Cursor_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)1048 internal void Cursor_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) 1049 { 1050 // Set flag to fire position changing events 1051 _fireUserChangingEvent = true; 1052 _fireUserChangedEvent = false; 1053 1054 // Check if left mouse button was clicked in chart area 1055 if(e.Button == MouseButtons.Left && !GetPositionInPlotArea(new PointF(e.X, e.Y), false).IsEmpty) 1056 { 1057 // Change cursor position and selection start position when mouse down 1058 if(this.IsUserEnabled) 1059 { 1060 SetCursorPixelPosition(new PointF(e.X, e.Y), false); 1061 } 1062 if(this.IsUserSelectionEnabled) 1063 { 1064 this._userSelectionStart = new PointF(e.X, e.Y); 1065 SetSelectionPixelPosition(this._userSelectionStart, PointF.Empty, false); 1066 } 1067 } 1068 1069 // Clear flag to fire position changing events 1070 _fireUserChangingEvent = false; 1071 _fireUserChangedEvent = false; 1072 } 1073 1074 /// <summary> 1075 /// Mouse up event handler. 1076 /// </summary> Cursor_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)1077 internal void Cursor_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) 1078 { 1079 // If in range selection mode 1080 if(!this._userSelectionStart.IsEmpty) 1081 { 1082 // Stop timer 1083 _scrollTimer.Stop(); 1084 _mouseMoveArguments = null; 1085 1086 // Check if axis data scaleView zooming UI is enabled 1087 if(this._axis != null && 1088 this._axis.ScaleView.Zoomable && 1089 !double.IsNaN(this.SelectionStart) && 1090 !double.IsNaN(this.SelectionEnd) && 1091 this.SelectionStart != this.SelectionEnd) 1092 { 1093 // Zoom data scaleView 1094 double start = Math.Min(this.SelectionStart, this.SelectionEnd); 1095 double size = (double)Math.Max(this.SelectionStart, this.SelectionEnd) - start; 1096 bool zoomed = this._axis.ScaleView.Zoom(start, size, DateTimeIntervalType.Number, true, true); 1097 1098 // Clear image buffer 1099 if(this._chartArea.areaBufferBitmap != null && zoomed) 1100 { 1101 this._chartArea.areaBufferBitmap.Dispose(); 1102 this._chartArea.areaBufferBitmap = null; 1103 } 1104 1105 // Clear range selection 1106 this.SelectionStart = double.NaN; 1107 this.SelectionEnd = double.NaN; 1108 1109 // NOTE: Fixes issue #6823 1110 // Clear cursor position after the zoom in operation 1111 this.Position = double.NaN; 1112 1113 // Align cursor in connected areas 1114 if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null) 1115 { 1116 if(!this._chartArea.alignmentInProcess) 1117 { 1118 AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ? 1119 AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal; 1120 this._chartArea.Common.ChartPicture.AlignChartAreasZoomed(this._chartArea, orientation, zoomed); 1121 } 1122 } 1123 } 1124 1125 // Fire XXXChanged events 1126 if(GetChartObject() != null) 1127 { 1128 CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), this.SelectionStart, this.SelectionEnd); 1129 GetChartObject().OnSelectionRangeChanged(arguments); 1130 1131 arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), this.Position); 1132 GetChartObject().OnCursorPositionChanged(arguments); 1133 } 1134 1135 // Stop range selection mode 1136 this._userSelectionStart = PointF.Empty; 1137 } 1138 } 1139 1140 /// <summary> 1141 /// Mouse move event handler. 1142 /// </summary> 1143 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Mobility", "CA1601:DoNotUseTimersThatPreventPowerStateChanges", Justification = "The timer is used for simulating scrolling behavior")] Cursor_MouseMove(System.Windows.Forms.MouseEventArgs e, ref bool handled)1144 internal void Cursor_MouseMove(System.Windows.Forms.MouseEventArgs e, ref bool handled) 1145 { 1146 // Process range selection 1147 if(this._userSelectionStart != PointF.Empty) 1148 { 1149 // Mouse move event should not be handled by any other chart elements 1150 handled = true; 1151 1152 // Set flag to fire position changing events 1153 _fireUserChangingEvent = true; 1154 _fireUserChangedEvent = false; 1155 1156 // Check if mouse position is outside of the chart area and if not - try scrolling 1157 if(this.AutoScroll) 1158 { 1159 if(this._chartArea != null && this._chartArea.Common != null && this.GetAxis()!= null) 1160 { 1161 // Check if axis data scaleView is enabled 1162 if(!double.IsNaN(this._axis.ScaleView.Position) && !double.IsNaN(this._axis.ScaleView.Size)) 1163 { 1164 ScrollType scrollType = ScrollType.SmallIncrement; 1165 bool insideChartArea = true; 1166 double offsetFromBoundary = 0.0; 1167 1168 // Translate mouse pixel coordinates into the relative chart area coordinates 1169 float mouseX = e.X * 100F / ((float)(this._chartArea.Common.Width - 1)); 1170 float mouseY = e.Y * 100F / ((float)(this._chartArea.Common.Height - 1)); 1171 1172 // Check if coordinate is inside chart plotting area 1173 if(this._axis.AxisPosition == AxisPosition.Bottom || this._axis.AxisPosition == AxisPosition.Top) 1174 { 1175 if(mouseX < this._chartArea.PlotAreaPosition.X) 1176 { 1177 scrollType = ScrollType.SmallDecrement; 1178 insideChartArea = false; 1179 offsetFromBoundary = this._chartArea.PlotAreaPosition.X - mouseX; 1180 } 1181 else if(mouseX > this._chartArea.PlotAreaPosition.Right) 1182 { 1183 scrollType = ScrollType.SmallIncrement; 1184 insideChartArea = false; 1185 offsetFromBoundary = mouseX - this._chartArea.PlotAreaPosition.Right; 1186 } 1187 } 1188 else 1189 { 1190 if(mouseY < this._chartArea.PlotAreaPosition.Y) 1191 { 1192 scrollType = ScrollType.SmallIncrement; 1193 insideChartArea = false; 1194 offsetFromBoundary = this._chartArea.PlotAreaPosition.Y - mouseY; 1195 } 1196 else if(mouseY > this._chartArea.PlotAreaPosition.Bottom) 1197 { 1198 scrollType = ScrollType.SmallDecrement; 1199 insideChartArea = false; 1200 offsetFromBoundary = mouseY - this._chartArea.PlotAreaPosition.Bottom; 1201 } 1202 } 1203 1204 // Try scrolling scaleView position 1205 if(!insideChartArea) 1206 { 1207 // Set flag that data scaleView was scrolled 1208 _viewScrolledOnMouseMove = true; 1209 1210 // Get minimum scroll interval 1211 double scrollInterval = ChartHelper.GetIntervalSize( 1212 this._axis.ScaleView.Position, 1213 this._axis.ScaleView.GetScrollingLineSize(), 1214 this._axis.ScaleView.GetScrollingLineSizeType()); 1215 offsetFromBoundary *= 2; 1216 if(offsetFromBoundary > scrollInterval) 1217 { 1218 scrollInterval = ((int)(offsetFromBoundary / scrollInterval)) * scrollInterval; 1219 } 1220 1221 // Scroll axis data scaleView 1222 double newDataViewPosition = this._axis.ScaleView.Position; 1223 if(scrollType == ScrollType.SmallIncrement) 1224 { 1225 newDataViewPosition += scrollInterval; 1226 } 1227 else 1228 { 1229 newDataViewPosition -= scrollInterval; 1230 } 1231 1232 // Scroll axis data scaleView 1233 this._axis.ScaleView.Scroll(newDataViewPosition); 1234 1235 // Save last mouse move arguments 1236 _mouseMoveArguments = new MouseEventArgs(e.Button, e.Clicks, e.X, e.Y, e.Delta); 1237 1238 // Start selection scrolling timer 1239 if(!_scrollTimer.Enabled) 1240 { 1241 // Start timer 1242 _scrollTimer.Tick += new EventHandler(SelectionScrollingTimerEventProcessor); 1243 _scrollTimer.Interval = 200; 1244 _scrollTimer.Start(); 1245 } 1246 } 1247 else 1248 { 1249 // Stop timer 1250 _scrollTimer.Stop(); 1251 _mouseMoveArguments = null; 1252 } 1253 } 1254 } 1255 } 1256 1257 // Change cursor position and selection end position when mouse moving 1258 if(this.IsUserEnabled) 1259 { 1260 SetCursorPixelPosition(new PointF(e.X, e.Y), true); 1261 } 1262 if(this.IsUserSelectionEnabled) 1263 { 1264 // Set selection 1265 SetSelectionPixelPosition(PointF.Empty, new PointF(e.X, e.Y), true); 1266 } 1267 1268 // Clear flag to fire position changing events 1269 _fireUserChangingEvent = false; 1270 _fireUserChangedEvent = false; 1271 1272 // Clear flag that data scaleView was scrolled 1273 _viewScrolledOnMouseMove = false; 1274 1275 } 1276 } 1277 1278 /// <summary> 1279 /// This is the method to run when the timer is raised. 1280 /// Used to scroll axis data scaleView while mouse is outside of the chart area. 1281 /// </summary> 1282 /// <param name="myObject"></param> 1283 /// <param name="myEventArgs"></param> SelectionScrollingTimerEventProcessor(Object myObject, EventArgs myEventArgs)1284 private void SelectionScrollingTimerEventProcessor(Object myObject, EventArgs myEventArgs) 1285 { 1286 // Simulate mouse move events 1287 if(_mouseMoveArguments != null) 1288 { 1289 bool handled = false; 1290 this.Cursor_MouseMove(_mouseMoveArguments, ref handled); 1291 } 1292 } 1293 1294 #endregion 1295 1296 #region Cursor helper methods 1297 1298 /// <summary> 1299 /// Helper function which returns a reference to the chart object 1300 /// </summary> 1301 /// <returns>Chart object reference.</returns> GetChartObject()1302 private Chart GetChartObject() 1303 { 1304 if(this._chartArea != null ) 1305 { 1306 return this._chartArea.Chart; 1307 } 1308 1309 return null; 1310 } 1311 1312 /// <summary> 1313 /// Get rectangle of the axis range selection. 1314 /// </summary> 1315 /// <returns>Selection rectangle.</returns> 1316 /// <param name="plotAreaPosition">Plot area rectangle.</param> 1317 /// <returns></returns> GetSelectionRect(RectangleF plotAreaPosition)1318 private RectangleF GetSelectionRect(RectangleF plotAreaPosition) 1319 { 1320 RectangleF rect = RectangleF.Empty; 1321 1322 if(this._axis != null && 1323 this.SelectionStart != this.SelectionEnd) 1324 { 1325 double start = (float)this._axis.GetLinearPosition(this.SelectionStart); 1326 double end = (float)this._axis.GetLinearPosition(this.SelectionEnd); 1327 1328 // Detect if cursor is horizontal or vertical 1329 bool horizontal = true; 1330 if(this.GetAxis().AxisPosition == AxisPosition.Bottom || this.GetAxis().AxisPosition == AxisPosition.Top) 1331 { 1332 horizontal = false; 1333 } 1334 1335 if(horizontal) 1336 { 1337 rect.X = plotAreaPosition.X; 1338 rect.Width = plotAreaPosition.Width; 1339 rect.Y = (float)Math.Min(start, end); 1340 rect.Height = (float)Math.Max(start, end) - rect.Y; 1341 } 1342 else 1343 { 1344 rect.Y = plotAreaPosition.Y; 1345 rect.Height = plotAreaPosition.Height; 1346 rect.X = (float)Math.Min(start, end); 1347 rect.Width = (float)Math.Max(start, end) - rect.X; 1348 } 1349 } 1350 1351 return rect; 1352 } 1353 1354 /// <summary> 1355 /// Get rectangle of the opposite axis selection 1356 /// </summary> 1357 /// <param name="plotAreaPosition">Plot area rectangle.</param> 1358 /// <returns>Opposite selection rectangle.</returns> GetOppositeSelectionRect(RectangleF plotAreaPosition)1359 private RectangleF GetOppositeSelectionRect(RectangleF plotAreaPosition) 1360 { 1361 if(_chartArea != null) 1362 { 1363 // Get opposite cursor 1364 Cursor oppositeCursor = 1365 (_attachedToXAxis == AxisName.X || _attachedToXAxis == AxisName.X2) ? 1366 _chartArea.CursorY : _chartArea.CursorX; 1367 return oppositeCursor.GetSelectionRect(plotAreaPosition); 1368 } 1369 1370 return RectangleF.Empty; 1371 } 1372 1373 /// <summary> 1374 /// Converts X or Y position value to the cursor axis value 1375 /// </summary> 1376 /// <param name="position">Position in relative coordinates.</param> 1377 /// <returns>Cursor position as axis value.</returns> PositionToCursorPosition(PointF position)1378 private double PositionToCursorPosition(PointF position) 1379 { 1380 // Detect if cursor is horizontal or vertical 1381 bool horizontal = true; 1382 if(this.GetAxis().AxisPosition == AxisPosition.Bottom || this.GetAxis().AxisPosition == AxisPosition.Top) 1383 { 1384 horizontal = false; 1385 } 1386 1387 // Convert relative coordinates into axis values 1388 double newCursorPosition = double.NaN; 1389 if(horizontal) 1390 { 1391 newCursorPosition = this.GetAxis().PositionToValue(position.Y); 1392 } 1393 else 1394 { 1395 newCursorPosition = this.GetAxis().PositionToValue(position.X); 1396 } 1397 1398 // Round new position using Step & StepType properties 1399 newCursorPosition = RoundPosition(newCursorPosition); 1400 1401 return newCursorPosition; 1402 } 1403 1404 1405 /// <summary> 1406 /// Checks if specified point is located inside the plotting area. 1407 /// Converts pixel coordinates to relative. 1408 /// </summary> 1409 /// <param name="point">Point coordinates to test.</param> 1410 /// <param name="roundToBoundary">Indicates that coordinates must be rounded to area boundary.</param> 1411 /// <returns>PointF.IsEmpty or relative coordinates in plotting area.</returns> GetPositionInPlotArea(PointF point, bool roundToBoundary)1412 private PointF GetPositionInPlotArea(PointF point, bool roundToBoundary) 1413 { 1414 PointF result = PointF.Empty; 1415 1416 if(this._chartArea != null && this._chartArea.Common != null && this.GetAxis()!= null) 1417 { 1418 // Translate mouse pixel coordinates into the relative chart area coordinates 1419 result.X = point.X * 100F / ((float)(this._chartArea.Common.Width - 1)); 1420 result.Y = point.Y * 100F / ((float)(this._chartArea.Common.Height - 1)); 1421 1422 // Round coordinate if it' outside chart plotting area 1423 RectangleF plotAreaPosition = this._chartArea.PlotAreaPosition.ToRectangleF(); 1424 if(roundToBoundary) 1425 { 1426 if(result.X < plotAreaPosition.X) 1427 { 1428 result.X = plotAreaPosition.X; 1429 } 1430 if(result.X > plotAreaPosition.Right) 1431 { 1432 result.X = plotAreaPosition.Right; 1433 } 1434 if(result.Y < plotAreaPosition.Y) 1435 { 1436 result.Y = plotAreaPosition.Y; 1437 } 1438 if(result.Y > plotAreaPosition.Bottom) 1439 { 1440 result.Y = plotAreaPosition.Bottom; 1441 } 1442 } 1443 else 1444 { 1445 // Check if coordinate is inside chart plotting area 1446 if(result.X < plotAreaPosition.X || 1447 result.X > plotAreaPosition.Right || 1448 result.Y < plotAreaPosition.Y || 1449 result.Y > plotAreaPosition.Bottom) 1450 { 1451 result = PointF.Empty; 1452 } 1453 } 1454 } 1455 1456 return result; 1457 } 1458 1459 /// <summary> 1460 /// Invalidate chart are with the cursor. 1461 /// </summary> 1462 /// <param name="invalidateArea">Chart area must be invalidated.</param> Invalidate(bool invalidateArea)1463 private void Invalidate(bool invalidateArea) 1464 { 1465 if(this.GetChartObject() != null && this._chartArea != null && !this.GetChartObject().disableInvalidates) 1466 { 1467 // If data scaleView was scrolled - just invalidate the chart area 1468 if(_viewScrolledOnMouseMove || invalidateArea || this.GetChartObject().dirtyFlag) 1469 { 1470 this._chartArea.Invalidate(); 1471 } 1472 1473 // If only cursor/selection position was changed - use optimized drawing algorithm 1474 else 1475 { 1476 // Set flag to redraw cursor/selection only 1477 this.GetChartObject().paintTopLevelElementOnly = true; 1478 1479 // Invalidate and update the chart 1480 this._chartArea.Invalidate(); 1481 this.GetChartObject().Update(); 1482 1483 // Clear flag to redraw cursor/selection only 1484 this.GetChartObject().paintTopLevelElementOnly = false; 1485 } 1486 } 1487 } 1488 1489 /// <summary> 1490 /// Gets axis objects the cursor is attached to. 1491 /// </summary> 1492 /// <returns>Axis object.</returns> GetAxis()1493 internal Axis GetAxis() 1494 { 1495 if(_axis == null && _chartArea != null) 1496 { 1497 if(_attachedToXAxis == AxisName.X) 1498 { 1499 _axis = (_axisType == AxisType.Primary) ? _chartArea.AxisX : _chartArea.AxisX2; 1500 } 1501 else 1502 { 1503 _axis = (_axisType == AxisType.Primary) ? _chartArea.AxisY : _chartArea.AxisY2; 1504 } 1505 } 1506 1507 return _axis; 1508 } 1509 1510 #endregion 1511 1512 #region IDisposable Members 1513 1514 /// <summary> 1515 /// Releases unmanaged and - optionally - managed resources 1516 /// </summary> 1517 /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> Dispose(bool disposing)1518 protected virtual void Dispose(bool disposing) 1519 { 1520 if (disposing) 1521 { 1522 // Dispose managed resources 1523 if (this._scrollTimer != null) 1524 { 1525 this._scrollTimer.Dispose(); 1526 this._scrollTimer = null; 1527 } 1528 } 1529 } 1530 1531 /// <summary> 1532 /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. 1533 /// </summary> Dispose()1534 public void Dispose() 1535 { 1536 Dispose(true); 1537 GC.SuppressFinalize(this); 1538 } 1539 1540 #endregion 1541 } 1542 1543 /// <summary> 1544 /// The CursorEventArgs class stores the event arguments for cursor and selection events. 1545 /// </summary> 1546 public class CursorEventArgs : EventArgs 1547 { 1548 #region Private fields 1549 1550 // Private fields for properties values storage 1551 private ChartArea _chartArea = null; 1552 private Axis _axis = null; 1553 private double _newPosition = double.NaN; 1554 private double _newSelectionStart = double.NaN; 1555 private double _newSelectionEnd = double.NaN; 1556 1557 #endregion 1558 1559 #region Constructors 1560 1561 /// <summary> 1562 /// CursorEventArgs constructor. 1563 /// </summary> 1564 /// <param name="chartArea">ChartArea of the cursor.</param> 1565 /// <param name="axis">Axis of the cursor.</param> 1566 /// <param name="newPosition">New cursor position.</param> CursorEventArgs(ChartArea chartArea, Axis axis, double newPosition)1567 public CursorEventArgs(ChartArea chartArea, Axis axis, double newPosition) 1568 { 1569 this._chartArea = chartArea; 1570 this._axis = axis; 1571 this._newPosition = newPosition; 1572 this._newSelectionStart = double.NaN; 1573 this._newSelectionEnd = double.NaN; 1574 } 1575 1576 /// <summary> 1577 /// CursorEventArgs constructor. 1578 /// </summary> 1579 /// <param name="chartArea">ChartArea of the cursor.</param> 1580 /// <param name="axis">Axis of the cursor.</param> 1581 /// <param name="newSelectionStart">New range selection starting position.</param> 1582 /// <param name="newSelectionEnd">New range selection ending position.</param> CursorEventArgs(ChartArea chartArea, Axis axis, double newSelectionStart, double newSelectionEnd)1583 public CursorEventArgs(ChartArea chartArea, Axis axis, double newSelectionStart, double newSelectionEnd) 1584 { 1585 this._chartArea = chartArea; 1586 this._axis = axis; 1587 this._newPosition = double.NaN; 1588 this._newSelectionStart = newSelectionStart; 1589 this._newSelectionEnd = newSelectionEnd; 1590 } 1591 1592 #endregion 1593 1594 #region Properties 1595 1596 /// <summary> 1597 /// ChartArea of the event. 1598 /// </summary> 1599 [ 1600 SRDescription("DescriptionAttributeChartArea"), 1601 ] 1602 public ChartArea ChartArea 1603 { 1604 get 1605 { 1606 return _chartArea; 1607 } 1608 } 1609 1610 /// <summary> 1611 /// Axis of the event. 1612 /// </summary> 1613 [ 1614 SRDescription("DescriptionAttributeAxis"), 1615 ] 1616 public Axis Axis 1617 { 1618 get 1619 { 1620 return _axis; 1621 } 1622 } 1623 1624 /// <summary> 1625 /// New cursor position. 1626 /// </summary> 1627 [ 1628 SRDescription("DescriptionAttributeCursorEventArgs_NewPosition"), 1629 ] 1630 public double NewPosition 1631 { 1632 get 1633 { 1634 return _newPosition; 1635 } 1636 set 1637 { 1638 _newPosition = value; 1639 } 1640 } 1641 1642 /// <summary> 1643 /// New range selection starting position. 1644 /// </summary> 1645 [ 1646 SRDescription("DescriptionAttributeCursorEventArgs_NewSelectionStart"), 1647 ] 1648 public double NewSelectionStart 1649 { 1650 get 1651 { 1652 return _newSelectionStart; 1653 } 1654 set 1655 { 1656 _newSelectionStart = value; 1657 } 1658 } 1659 1660 /// <summary> 1661 /// New range selection ending position. 1662 /// </summary> 1663 [ 1664 SRDescription("DescriptionAttributeCursorEventArgs_NewSelectionEnd"), 1665 ] 1666 public double NewSelectionEnd 1667 { 1668 get 1669 { 1670 return _newSelectionEnd; 1671 } 1672 set 1673 { 1674 _newSelectionEnd = value; 1675 } 1676 } 1677 1678 #endregion 1679 } 1680 } 1681 1682 #endif // #if WINFORMS_CONTROL