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