Xamarin Public Jenkins (auto-signing) 536cd135cc Imported Upstream version 5.4.0.167
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
2017-08-21 15:34:15 +00:00

1682 lines
49 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//-------------------------------------------------------------
// <copyright company=Microsoft Corporation>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: ChartAreaCursor.cs
//
// Namespace: System.Web.UI.WebControls[Windows.Forms].Charting
//
// Classes: Cursor, CursorEventArgs
//
// Purpose: A cursor is a horizontal or vertical line that
// defines a position along an axis. A range selection
// is a range along an axis that is defined by a beginning
// and end position, and is displayed using a semi-transparent
// color.
//
// Both cursors and range selections are implemented by the
// Cursor class, which is exposed as the CursorX and CursorY
// properties of the ChartArea object. The CursorX object is
// for the X axis of a chart area, and the CursorY object is
// for the Y axis. The AxisType property of these objects
// determines if the associated axis is primary or secondary.
//
// Cursors and range selections can be set via end-user
// interaction and programmatically.
//
// NOTE: ASP.NET version of the chart uses this class only
// for appearance and position properties. Drawing of the
// selection and cursor is implemented through client side
// java script.
//
// Reviewed: AG - August 8, 2002
// AG - March 16, 2007
//
//===================================================================
#if WINFORMS_CONTROL
#region Used Namespaces
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.ComponentModel;
using System.Collections;
using System.Windows.Forms.DataVisualization.Charting;
using System.Windows.Forms.DataVisualization.Charting.Data;
using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
using System.Windows.Forms.DataVisualization.Charting.Utilities;
using System.Windows.Forms.DataVisualization.Charting.Borders3D;
using System.Drawing.Design;
using System.Collections.Generic;
#endregion
namespace System.Windows.Forms.DataVisualization.Charting
{
/// <summary>
/// The Cursor class is responsible for chart axes cursor and selection
/// functionality. It contains properties which define visual appearance,
/// position and behavior settings. It also contains methods for
/// drawing cursor and selection in the plotting area.
/// </summary>
[
DefaultProperty("Enabled"),
SRDescription("DescriptionAttributeCursor_Cursor"),
]
public class Cursor : IDisposable
{
#region Cursor constructors and initialization
/// <summary>
/// Public constructor
/// </summary>
public Cursor()
{
}
/// <summary>
/// Initialize cursor class.
/// </summary>
/// <param name="chartArea">Chart area the cursor belongs to.</param>
/// <param name="attachedToXAxis">Indicates which axes should be used X or Y.</param>
internal void Initialize(ChartArea chartArea, AxisName attachedToXAxis)
{
// Set chart are reference
this._chartArea = chartArea;
// Attach cursor to specified axis
this._attachedToXAxis = attachedToXAxis;
}
#endregion
#region Cursor fields
// Reference to the chart area object the cursor belongs to
private ChartArea _chartArea = null;
// Defines which axis the cursor attached to X or Y
private AxisName _attachedToXAxis = AxisName.X;
// Enables/Disables chart area cursor.
private bool _isUserEnabled = false;
// Enables/Disables chart area selection.
private bool _isUserSelectionEnabled = false;
// Indicates that cursor will automatically scroll the area scaleView if necessary.
private bool _autoScroll = true;
// Cursor line color
private Color _lineColor = Color.Red;
// Cursor line width
private int _lineWidth = 1;
// Cursor line style
private ChartDashStyle _lineDashStyle = ChartDashStyle.Solid;
// Chart area selection color
private Color _selectionColor = Color.LightGray;
// AxisName of the axes (primary/secondary) the cursor is attached to
private AxisType _axisType = AxisType.Primary;
// Cursor position
private double _position = Double.NaN;
// Range selection start position.
private double _selectionStart = Double.NaN;
// Range selection end position.
private double _selectionEnd = Double.NaN;
// Cursor movement interval current & original values
private double _interval = 1;
// Cursor movement interval type
private DateTimeIntervalType _intervalType = DateTimeIntervalType.Auto;
// Cursor movement interval offset current & original values
private double _intervalOffset = 0;
// Cursor movement interval offset type
private DateTimeIntervalType _intervalOffsetType = DateTimeIntervalType.Auto;
// Reference to the axis obhect
private Axis _axis = null;
// User selection start point
private PointF _userSelectionStart = PointF.Empty;
// Indicates that selection must be drawn
private bool _drawSelection = true;
// Indicates that events must be fired when position/selection is changed
private bool _fireUserChangingEvent = false;
// Indicates that XXXChanged events must be fired when position/selection is changed
private bool _fireUserChangedEvent = false;
// Scroll size and direction when AutoScroll is set
private MouseEventArgs _mouseMoveArguments = null;
// Timer used to scroll the data while selecting
private System.Windows.Forms.Timer _scrollTimer = new System.Windows.Forms.Timer();
// Indicates that axis data scaleView was scrolled as a result of the mouse move event
private bool _viewScrolledOnMouseMove = false;
#endregion
#region Cursor "Behavior" public properties.
/// <summary>
/// Gets or sets the position of a cursor.
/// </summary>
[
SRCategory("CategoryAttributeBehavior"),
Bindable(true),
DefaultValue(Double.NaN),
SRDescription("DescriptionAttributeCursor_Position"),
ParenthesizePropertyNameAttribute(true),
TypeConverter(typeof(DoubleDateNanValueConverter)),
]
public double Position
{
get
{
return _position;
}
set
{
if(_position != value)
{
_position = value;
// Align cursor in connected areas
if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null)
{
if(!this._chartArea.alignmentInProcess)
{
AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ?
AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal;
this._chartArea.Common.ChartPicture.AlignChartAreasCursor(this._chartArea, orientation, false);
}
}
if(this._chartArea != null && !this._chartArea.alignmentInProcess)
{
this.Invalidate(false);
}
}
}
}
/// <summary>
/// Gets or sets the starting position of a cursor's selected range.
/// </summary>
[
SRCategory("CategoryAttributeBehavior"),
Bindable(true),
DefaultValue(Double.NaN),
SRDescription("DescriptionAttributeCursor_SelectionStart"),
TypeConverter(typeof(DoubleDateNanValueConverter)),
]
public double SelectionStart
{
get
{
return _selectionStart;
}
set
{
if(_selectionStart != value)
{
_selectionStart = value;
// Align cursor in connected areas
if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null)
{
if(!this._chartArea.alignmentInProcess)
{
AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ?
AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal;
this._chartArea.Common.ChartPicture.AlignChartAreasCursor(this._chartArea, orientation, false);
}
}
if(this._chartArea != null && !this._chartArea.alignmentInProcess)
{
this.Invalidate(false);
}
}
}
}
/// <summary>
/// Gets or sets the ending position of a range selection.
/// </summary>
[
SRCategory("CategoryAttributeBehavior"),
Bindable(true),
DefaultValue(Double.NaN),
SRDescription("DescriptionAttributeCursor_SelectionEnd"),
TypeConverter(typeof(DoubleDateNanValueConverter)),
]
public double SelectionEnd
{
get
{
return _selectionEnd;
}
set
{
if(_selectionEnd != value)
{
_selectionEnd = value;
// Align cursor in connected areas
if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null)
{
if(!this._chartArea.alignmentInProcess)
{
AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ?
AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal;
this._chartArea.Common.ChartPicture.AlignChartAreasCursor(this._chartArea, orientation, false);
}
}
if(this._chartArea != null && !this._chartArea.alignmentInProcess)
{
this.Invalidate(false);
}
}
}
}
/// <summary>
/// Gets or sets a property that enables or disables the cursor interface.
/// </summary>
[
SRCategory("CategoryAttributeBehavior"),
Bindable(true),
DefaultValue(false),
SRDescription("DescriptionAttributeCursor_UserEnabled"),
]
public bool IsUserEnabled
{
get
{
return _isUserEnabled;
}
set
{
_isUserEnabled = value;
}
}
/// <summary>
/// Gets or sets a property that enables or disables the range selection interface.
/// </summary>
[
SRCategory("CategoryAttributeBehavior"),
Bindable(true),
DefaultValue(false),
SRDescription("DescriptionAttributeCursor_UserSelection"),
]
public bool IsUserSelectionEnabled
{
get
{
return _isUserSelectionEnabled;
}
set
{
_isUserSelectionEnabled = value;
}
}
/// <summary>
/// Determines if scrolling will occur if a range selection operation
/// extends beyond a boundary of the chart area.
/// </summary>
[
SRCategory("CategoryAttributeBehavior"),
Bindable(true),
DefaultValue(true),
SRDescription("DescriptionAttributeCursor_AutoScroll"),
]
public bool AutoScroll
{
get
{
return _autoScroll;
}
set
{
_autoScroll = value;
}
}
/// <summary>
/// Gets or sets the type of axis that the cursor is attached to.
/// </summary>
[
SRCategory("CategoryAttributeBehavior"),
Bindable(true),
SRDescription("DescriptionAttributeCursor_AxisType"),
DefaultValue(AxisType.Primary)
]
public AxisType AxisType
{
get
{
return _axisType;
}
set
{
_axisType = value;
// Reset reference to the axis object
_axis = null;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets the cursor movement interval.
/// </summary>
[
SRCategory("CategoryAttributeBehavior"),
Bindable(true),
DefaultValue(1.0),
SRDescription("DescriptionAttributeCursor_Interval"),
]
public double Interval
{
get
{
return _interval;
}
set
{
_interval = value;
}
}
/// <summary>
/// Gets or sets the unit of measurement of the Interval property.
/// </summary>
[
SRCategory("CategoryAttributeBehavior"),
Bindable(true),
DefaultValue(DateTimeIntervalType.Auto),
SRDescription("DescriptionAttributeCursor_IntervalType")
]
public DateTimeIntervalType IntervalType
{
get
{
return _intervalType;
}
set
{
_intervalType = (value != DateTimeIntervalType.NotSet) ? value : DateTimeIntervalType.Auto;
}
}
/// <summary>
/// Gets or sets the interval offset, which determines
/// where to draw the cursor and range selection.
/// </summary>
[
SRCategory("CategoryAttributeBehavior"),
Bindable(true),
DefaultValue(0.0),
SRDescription("DescriptionAttributeCursor_IntervalOffset"),
]
public double IntervalOffset
{
get
{
return _intervalOffset;
}
set
{
// Validation
if( value < 0.0 )
{
throw (new ArgumentException(SR.ExceptionCursorIntervalOffsetIsNegative, "value"));
}
_intervalOffset = value;
}
}
/// <summary>
/// Gets or sets the unit of measurement of the IntervalOffset property.
/// </summary>
[
SRCategory("CategoryAttributeBehavior"),
Bindable(true),
DefaultValue(DateTimeIntervalType.Auto),
SRDescription("DescriptionAttributeCursor_IntervalOffsetType"),
]
public DateTimeIntervalType IntervalOffsetType
{
get
{
return _intervalOffsetType;
}
set
{
_intervalOffsetType = (value != DateTimeIntervalType.NotSet) ? value : DateTimeIntervalType.Auto;
}
}
#endregion
#region Cursor "Appearance" public properties
/// <summary>
/// Gets or sets the color the cursor line.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Color), "Red"),
SRDescription("DescriptionAttributeLineColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
]
public Color LineColor
{
get
{
return _lineColor;
}
set
{
_lineColor = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the style of the cursor line.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(ChartDashStyle.Solid),
SRDescription("DescriptionAttributeLineDashStyle"),
]
public ChartDashStyle LineDashStyle
{
get
{
return _lineDashStyle;
}
set
{
_lineDashStyle = value;
this.Invalidate(false);
}
}
/// <summary>
/// Gets or sets the width of the cursor line.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(1),
SRDescription("DescriptionAttributeLineWidth"),
]
public int LineWidth
{
get
{
return _lineWidth;
}
set
{
if(value < 0)
{
throw (new ArgumentOutOfRangeException("value", SR.ExceptionCursorLineWidthIsNegative));
}
_lineWidth = value;
this.Invalidate(true);
}
}
/// <summary>
/// Gets or sets a semi-transparent color that highlights a range of data.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Color), "LightGray"),
SRDescription("DescriptionAttributeCursor_SelectionColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
]
public Color SelectionColor
{
get
{
return _selectionColor;
}
set
{
_selectionColor = value;
this.Invalidate(false);
}
}
#endregion
#region Cursor painting methods
/// <summary>
/// Draws chart area cursor and selection.
/// </summary>
/// <param name="graph">Reference to the ChartGraphics object.</param>
internal void Paint( ChartGraphics graph )
{
//***************************************************
//** Prepare for drawing
//***************************************************
// Do not proceed with painting if cursor is not attached to the axis
if(this.GetAxis() == null ||
this._chartArea == null ||
this._chartArea.Common == null ||
this._chartArea.Common.ChartPicture == null ||
this._chartArea.Common.ChartPicture.isPrinting)
{
return;
}
// Get plot area position
RectangleF plotAreaPosition = this._chartArea.PlotAreaPosition.ToRectangleF();
// Detect if cursor is horizontal or vertical
bool horizontal = true;
if(this.GetAxis().AxisPosition == AxisPosition.Bottom || this.GetAxis().AxisPosition == AxisPosition.Top)
{
horizontal = false;
}
//***************************************************
//** Draw selection
//***************************************************
// Check if selection need to be drawn
if(this._drawSelection &&
!double.IsNaN(this.SelectionStart) &&
!double.IsNaN(this.SelectionEnd) &&
this.SelectionColor != Color.Empty)
{
// Calculate selection rectangle
RectangleF rectSelection = GetSelectionRect(plotAreaPosition);
rectSelection.Intersect(plotAreaPosition);
// Get opposite axis selection rectangle
RectangleF rectOppositeSelection = GetOppositeSelectionRect(plotAreaPosition);
// Draw selection if rectangle is not empty
if(!rectSelection.IsEmpty && rectSelection.Width > 0 && rectSelection.Height > 0)
{
// Limit selection rectangle to the area of the opposite selection
if(!rectOppositeSelection.IsEmpty && rectOppositeSelection.Width > 0 && rectOppositeSelection.Height > 0)
{
rectSelection.Intersect(rectOppositeSelection);
// We do not need to draw selection in the opposite axis
Cursor oppositeCursor =
(_attachedToXAxis == AxisName.X || _attachedToXAxis == AxisName.X2) ?
_chartArea.CursorY : _chartArea.CursorX;
oppositeCursor._drawSelection = false;
}
// Make sure selection is inside plotting area
rectSelection.Intersect(plotAreaPosition);
// If selection rectangle is not empty
if(rectSelection.Width > 0 && rectSelection.Height > 0)
{
// Add transparency to solid colors
Color rangeSelectionColor = this.SelectionColor;
if(rangeSelectionColor.A == 255)
{
rangeSelectionColor = Color.FromArgb(120, rangeSelectionColor);
}
// Draw selection
graph.FillRectangleRel( rectSelection,
rangeSelectionColor,
ChartHatchStyle.None,
"",
ChartImageWrapMode.Tile,
Color.Empty,
ChartImageAlignmentStyle.Center,
GradientStyle.None,
Color.Empty,
Color.Empty,
0,
ChartDashStyle.NotSet,
Color.Empty,
0,
PenAlignment.Inset );
}
}
}
//***************************************************
//** Draw cursor
//***************************************************
// Check if cursor need to be drawn
if(!double.IsNaN(this.Position) &&
this.LineColor != Color.Empty &&
this.LineWidth > 0 &&
this.LineDashStyle != ChartDashStyle.NotSet)
{
// Calculate line position
bool insideArea = false;
PointF point1 = PointF.Empty;
PointF point2 = PointF.Empty;
if(horizontal)
{
// Set cursor coordinates
point1.X = plotAreaPosition.X;
point1.Y = (float)this.GetAxis().GetLinearPosition(this.Position);
point2.X = plotAreaPosition.Right;
point2.Y = point1.Y;
// Check if cursor is inside plotting rect
if(point1.Y >= plotAreaPosition.Y && point1.Y <= plotAreaPosition.Bottom)
{
insideArea = true;
}
}
else
{
// Set cursor coordinates
point1.X = (float)this.GetAxis().GetLinearPosition(this.Position);
point1.Y = plotAreaPosition.Y;
point2.X = point1.X;
point2.Y = plotAreaPosition.Bottom;
// Check if cursor is inside plotting rect
if(point1.X >= plotAreaPosition.X && point1.X <= plotAreaPosition.Right)
{
insideArea = true;
}
}
// Draw cursor if it's inside the chart area plotting rectangle
if(insideArea)
{
graph.DrawLineRel(this.LineColor, this.LineWidth, this.LineDashStyle, point1, point2);
}
}
// Reset draw selection flag
this._drawSelection = true;
}
#endregion
#region Cursor position setting methods
/// <summary>
/// This method sets the position of a cursor within a chart area at a given axis value.
/// </summary>
/// <param name="newPosition">The new position of the cursor. Measured as a value along the relevant axis.</param>
public void SetCursorPosition(double newPosition)
{
// Check if we are setting different value
if(this.Position != newPosition)
{
double newRoundedPosition = RoundPosition(newPosition);
// Send PositionChanging event
if(_fireUserChangingEvent && GetChartObject() != null)
{
CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), newRoundedPosition);
GetChartObject().OnCursorPositionChanging(arguments);
// Check if position values were changed in the event
newRoundedPosition = arguments.NewPosition;
}
// Change position
this.Position = newRoundedPosition;
// Send PositionChanged event
if(_fireUserChangedEvent && GetChartObject() != null)
{
CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), this.Position);
GetChartObject().OnCursorPositionChanged(arguments);
}
}
}
/// <summary>
/// This method displays a cursor at the specified position. Measured in pixels.
/// </summary>
/// <param name="point">A PointF structure that specifies where the cursor will be drawn.</param>
/// <param name="roundToBoundary">If true, the cursor will be drawn along the nearest chart area boundary
/// when the specified position does not fall within a ChartArea object.</param>
public void SetCursorPixelPosition(PointF point, bool roundToBoundary)
{
if(this._chartArea != null && this._chartArea.Common != null && this.GetAxis() != null)
{
PointF relativeCoord = GetPositionInPlotArea(point, roundToBoundary);
if(!relativeCoord.IsEmpty)
{
// Get new cursor position
double newCursorPosition = PositionToCursorPosition(relativeCoord);
// Set new cursor & selection position
this.SetCursorPosition(newCursorPosition);
}
}
}
/// <summary>
/// This method sets the position of a selected range within a chart area at given axis values.
/// </summary>
/// <param name="newStart">The new starting position of the range selection. Measured as a value along the relevant axis..</param>
/// <param name="newEnd">The new ending position of the range selection. Measured as a value along the relevant axis.</param>
public void SetSelectionPosition(double newStart, double newEnd)
{
// Check if we are setting different value
if(this.SelectionStart != newStart || this.SelectionEnd != newEnd)
{
// Send PositionChanging event
double newRoundedSelectionStart = RoundPosition(newStart);
double newRoundedSelectionEnd = RoundPosition(newEnd);
// Send SelectionRangeChanging event
if(_fireUserChangingEvent && GetChartObject() != null)
{
CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), newRoundedSelectionStart, newRoundedSelectionEnd);
GetChartObject().OnSelectionRangeChanging(arguments);
// Check if position values were changed in the event
newRoundedSelectionStart = arguments.NewSelectionStart;
newRoundedSelectionEnd = arguments.NewSelectionEnd;
}
// Change selection position
this._selectionStart = newRoundedSelectionStart;
this._selectionEnd = newRoundedSelectionEnd;
// Align cursor in connected areas
if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null)
{
if(!this._chartArea.alignmentInProcess)
{
AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ?
AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal;
this._chartArea.Common.ChartPicture.AlignChartAreasCursor(this._chartArea, orientation, true);
}
}
if(this._chartArea != null && !this._chartArea.alignmentInProcess)
{
this.Invalidate(false);
}
// Send SelectionRangeChanged event
if(_fireUserChangedEvent && GetChartObject() != null)
{
CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), this.SelectionStart, this.SelectionEnd);
GetChartObject().OnSelectionRangeChanged(arguments);
}
}
}
/// <summary>
/// This method sets the starting and ending positions of a range selection.
/// </summary>
/// <param name="startPoint">A PointF structure that specifies where the range selection begins.</param>
/// <param name="endPoint">A PointF structure that specifies where the range selection ends</param>
/// <param name="roundToBoundary">If true, the starting and ending points will be rounded to the nearest chart area boundary
/// when the specified positions do not fall within a ChartArea object.</param>
public void SetSelectionPixelPosition(PointF startPoint, PointF endPoint, bool roundToBoundary)
{
if(this._chartArea != null && this._chartArea.Common != null && this.GetAxis() != null)
{
// Calculating the start position
double newStart = this.SelectionStart;
if(!startPoint.IsEmpty)
{
PointF relativeCoord = GetPositionInPlotArea(startPoint, roundToBoundary);
if(!relativeCoord.IsEmpty)
{
// Get new selection start position
newStart = PositionToCursorPosition(relativeCoord);
}
}
// Setting the end position
double newEnd = newStart;
if(!endPoint.IsEmpty)
{
PointF relativeCoord = GetPositionInPlotArea(endPoint, roundToBoundary);
if(!relativeCoord.IsEmpty)
{
// Get new selection position
newEnd = PositionToCursorPosition(relativeCoord);
}
}
// Set new selection start & end position
this.SetSelectionPosition(newStart, newEnd);
}
}
#endregion
#region Position rounding methods
/// <summary>
/// Rounds new position of the cursor or range selection
/// </summary>
/// <param name="cursorPosition"></param>
/// <returns></returns>
internal double RoundPosition(double cursorPosition)
{
double roundedPosition = cursorPosition;
if(!double.IsNaN(roundedPosition))
{
// Check if position rounding is required
if(this.GetAxis() != null &&
this.Interval != 0 &&
!double.IsNaN(this.Interval))
{
// Get first series attached to this axis
Series axisSeries = null;
if(_axis.axisType == AxisName.X || _axis.axisType == AxisName.X2)
{
List<string> seriesArray = _axis.ChartArea.GetXAxesSeries((_axis.axisType == AxisName.X) ? AxisType.Primary : AxisType.Secondary, _axis.SubAxisName);
if(seriesArray.Count > 0)
{
string seriesName = seriesArray[0] as string;
axisSeries = _axis.Common.DataManager.Series[seriesName];
if(axisSeries != null && !axisSeries.IsXValueIndexed)
{
axisSeries = null;
}
}
}
// If interval type is not set - use number
DateTimeIntervalType intervalType =
(this.IntervalType == DateTimeIntervalType.Auto) ?
DateTimeIntervalType.Number : this.IntervalType;
// If interval offset type is not set - use interval type
DateTimeIntervalType offsetType =
(this.IntervalOffsetType == DateTimeIntervalType.Auto) ?
intervalType : this.IntervalOffsetType;
// Round numbers
if(intervalType == DateTimeIntervalType.Number)
{
double newRoundedPosition = Math.Round(roundedPosition / this.Interval) * this.Interval;
// Add offset number
if(this.IntervalOffset != 0 &&
!double.IsNaN(IntervalOffset) &&
offsetType != DateTimeIntervalType.Auto)
{
if(this.IntervalOffset > 0)
{
newRoundedPosition += ChartHelper.GetIntervalSize(newRoundedPosition, this.IntervalOffset, offsetType);
}
else
{
newRoundedPosition -= ChartHelper.GetIntervalSize(newRoundedPosition, this.IntervalOffset, offsetType);
}
}
// Find rounded position after/before the current
double nextPosition = newRoundedPosition;
if(newRoundedPosition <= cursorPosition)
{
nextPosition += ChartHelper.GetIntervalSize(newRoundedPosition, this.Interval, intervalType, axisSeries, 0, DateTimeIntervalType.Number, true);
}
else
{
nextPosition -= ChartHelper.GetIntervalSize(newRoundedPosition, this.Interval, intervalType, axisSeries, 0, DateTimeIntervalType.Number, true);
}
// Choose closest rounded position
if(Math.Abs(nextPosition - cursorPosition) > Math.Abs(cursorPosition - newRoundedPosition))
{
roundedPosition = newRoundedPosition;
}
else
{
roundedPosition = nextPosition;
}
}
// Round date/time
else
{
// Find one rounded position prior and one after current position
// Adjust start position depending on the interval and type
double prevPosition = ChartHelper.AlignIntervalStart(cursorPosition, this.Interval, intervalType, axisSeries);
// Adjust start position depending on the interval offset and offset type
if( IntervalOffset != 0 && axisSeries == null)
{
if(this.IntervalOffset > 0)
{
prevPosition += ChartHelper.GetIntervalSize(
prevPosition,
this.IntervalOffset,
offsetType,
axisSeries,
0,
DateTimeIntervalType.Number,
true);
}
else
{
prevPosition += ChartHelper.GetIntervalSize(
prevPosition,
-this.IntervalOffset,
offsetType,
axisSeries,
0,
DateTimeIntervalType.Number,
true);
}
}
// Find rounded position after/before the current
double nextPosition = prevPosition;
if(prevPosition <= cursorPosition)
{
nextPosition += ChartHelper.GetIntervalSize(prevPosition, this.Interval, intervalType, axisSeries, 0, DateTimeIntervalType.Number, true);
}
else
{
nextPosition -= ChartHelper.GetIntervalSize(prevPosition, this.Interval, intervalType, axisSeries, 0, DateTimeIntervalType.Number, true);
}
// Choose closest rounded position
if(Math.Abs(nextPosition - cursorPosition) > Math.Abs(cursorPosition - prevPosition))
{
roundedPosition = prevPosition;
}
else
{
roundedPosition = nextPosition;
}
}
}
}
return roundedPosition;
}
#endregion
#region Mouse events handling for the Cursor
/// <summary>
/// Mouse down event handler.
/// </summary>
internal void Cursor_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Set flag to fire position changing events
_fireUserChangingEvent = true;
_fireUserChangedEvent = false;
// Check if left mouse button was clicked in chart area
if(e.Button == MouseButtons.Left && !GetPositionInPlotArea(new PointF(e.X, e.Y), false).IsEmpty)
{
// Change cursor position and selection start position when mouse down
if(this.IsUserEnabled)
{
SetCursorPixelPosition(new PointF(e.X, e.Y), false);
}
if(this.IsUserSelectionEnabled)
{
this._userSelectionStart = new PointF(e.X, e.Y);
SetSelectionPixelPosition(this._userSelectionStart, PointF.Empty, false);
}
}
// Clear flag to fire position changing events
_fireUserChangingEvent = false;
_fireUserChangedEvent = false;
}
/// <summary>
/// Mouse up event handler.
/// </summary>
internal void Cursor_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
// If in range selection mode
if(!this._userSelectionStart.IsEmpty)
{
// Stop timer
_scrollTimer.Stop();
_mouseMoveArguments = null;
// Check if axis data scaleView zooming UI is enabled
if(this._axis != null &&
this._axis.ScaleView.Zoomable &&
!double.IsNaN(this.SelectionStart) &&
!double.IsNaN(this.SelectionEnd) &&
this.SelectionStart != this.SelectionEnd)
{
// Zoom data scaleView
double start = Math.Min(this.SelectionStart, this.SelectionEnd);
double size = (double)Math.Max(this.SelectionStart, this.SelectionEnd) - start;
bool zoomed = this._axis.ScaleView.Zoom(start, size, DateTimeIntervalType.Number, true, true);
// Clear image buffer
if(this._chartArea.areaBufferBitmap != null && zoomed)
{
this._chartArea.areaBufferBitmap.Dispose();
this._chartArea.areaBufferBitmap = null;
}
// Clear range selection
this.SelectionStart = double.NaN;
this.SelectionEnd = double.NaN;
// NOTE: Fixes issue #6823
// Clear cursor position after the zoom in operation
this.Position = double.NaN;
// Align cursor in connected areas
if(this._chartArea != null && this._chartArea.Common != null && this._chartArea.Common.ChartPicture != null)
{
if(!this._chartArea.alignmentInProcess)
{
AreaAlignmentOrientations orientation = (this._attachedToXAxis == AxisName.X || this._attachedToXAxis == AxisName.X2) ?
AreaAlignmentOrientations.Vertical : AreaAlignmentOrientations.Horizontal;
this._chartArea.Common.ChartPicture.AlignChartAreasZoomed(this._chartArea, orientation, zoomed);
}
}
}
// Fire XXXChanged events
if(GetChartObject() != null)
{
CursorEventArgs arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), this.SelectionStart, this.SelectionEnd);
GetChartObject().OnSelectionRangeChanged(arguments);
arguments = new CursorEventArgs(this._chartArea, this.GetAxis(), this.Position);
GetChartObject().OnCursorPositionChanged(arguments);
}
// Stop range selection mode
this._userSelectionStart = PointF.Empty;
}
}
/// <summary>
/// Mouse move event handler.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Mobility", "CA1601:DoNotUseTimersThatPreventPowerStateChanges", Justification = "The timer is used for simulating scrolling behavior")]
internal void Cursor_MouseMove(System.Windows.Forms.MouseEventArgs e, ref bool handled)
{
// Process range selection
if(this._userSelectionStart != PointF.Empty)
{
// Mouse move event should not be handled by any other chart elements
handled = true;
// Set flag to fire position changing events
_fireUserChangingEvent = true;
_fireUserChangedEvent = false;
// Check if mouse position is outside of the chart area and if not - try scrolling
if(this.AutoScroll)
{
if(this._chartArea != null && this._chartArea.Common != null && this.GetAxis()!= null)
{
// Check if axis data scaleView is enabled
if(!double.IsNaN(this._axis.ScaleView.Position) && !double.IsNaN(this._axis.ScaleView.Size))
{
ScrollType scrollType = ScrollType.SmallIncrement;
bool insideChartArea = true;
double offsetFromBoundary = 0.0;
// Translate mouse pixel coordinates into the relative chart area coordinates
float mouseX = e.X * 100F / ((float)(this._chartArea.Common.Width - 1));
float mouseY = e.Y * 100F / ((float)(this._chartArea.Common.Height - 1));
// Check if coordinate is inside chart plotting area
if(this._axis.AxisPosition == AxisPosition.Bottom || this._axis.AxisPosition == AxisPosition.Top)
{
if(mouseX < this._chartArea.PlotAreaPosition.X)
{
scrollType = ScrollType.SmallDecrement;
insideChartArea = false;
offsetFromBoundary = this._chartArea.PlotAreaPosition.X - mouseX;
}
else if(mouseX > this._chartArea.PlotAreaPosition.Right)
{
scrollType = ScrollType.SmallIncrement;
insideChartArea = false;
offsetFromBoundary = mouseX - this._chartArea.PlotAreaPosition.Right;
}
}
else
{
if(mouseY < this._chartArea.PlotAreaPosition.Y)
{
scrollType = ScrollType.SmallIncrement;
insideChartArea = false;
offsetFromBoundary = this._chartArea.PlotAreaPosition.Y - mouseY;
}
else if(mouseY > this._chartArea.PlotAreaPosition.Bottom)
{
scrollType = ScrollType.SmallDecrement;
insideChartArea = false;
offsetFromBoundary = mouseY - this._chartArea.PlotAreaPosition.Bottom;
}
}
// Try scrolling scaleView position
if(!insideChartArea)
{
// Set flag that data scaleView was scrolled
_viewScrolledOnMouseMove = true;
// Get minimum scroll interval
double scrollInterval = ChartHelper.GetIntervalSize(
this._axis.ScaleView.Position,
this._axis.ScaleView.GetScrollingLineSize(),
this._axis.ScaleView.GetScrollingLineSizeType());
offsetFromBoundary *= 2;
if(offsetFromBoundary > scrollInterval)
{
scrollInterval = ((int)(offsetFromBoundary / scrollInterval)) * scrollInterval;
}
// Scroll axis data scaleView
double newDataViewPosition = this._axis.ScaleView.Position;
if(scrollType == ScrollType.SmallIncrement)
{
newDataViewPosition += scrollInterval;
}
else
{
newDataViewPosition -= scrollInterval;
}
// Scroll axis data scaleView
this._axis.ScaleView.Scroll(newDataViewPosition);
// Save last mouse move arguments
_mouseMoveArguments = new MouseEventArgs(e.Button, e.Clicks, e.X, e.Y, e.Delta);
// Start selection scrolling timer
if(!_scrollTimer.Enabled)
{
// Start timer
_scrollTimer.Tick += new EventHandler(SelectionScrollingTimerEventProcessor);
_scrollTimer.Interval = 200;
_scrollTimer.Start();
}
}
else
{
// Stop timer
_scrollTimer.Stop();
_mouseMoveArguments = null;
}
}
}
}
// Change cursor position and selection end position when mouse moving
if(this.IsUserEnabled)
{
SetCursorPixelPosition(new PointF(e.X, e.Y), true);
}
if(this.IsUserSelectionEnabled)
{
// Set selection
SetSelectionPixelPosition(PointF.Empty, new PointF(e.X, e.Y), true);
}
// Clear flag to fire position changing events
_fireUserChangingEvent = false;
_fireUserChangedEvent = false;
// Clear flag that data scaleView was scrolled
_viewScrolledOnMouseMove = false;
}
}
/// <summary>
/// This is the method to run when the timer is raised.
/// Used to scroll axis data scaleView while mouse is outside of the chart area.
/// </summary>
/// <param name="myObject"></param>
/// <param name="myEventArgs"></param>
private void SelectionScrollingTimerEventProcessor(Object myObject, EventArgs myEventArgs)
{
// Simulate mouse move events
if(_mouseMoveArguments != null)
{
bool handled = false;
this.Cursor_MouseMove(_mouseMoveArguments, ref handled);
}
}
#endregion
#region Cursor helper methods
/// <summary>
/// Helper function which returns a reference to the chart object
/// </summary>
/// <returns>Chart object reference.</returns>
private Chart GetChartObject()
{
if(this._chartArea != null )
{
return this._chartArea.Chart;
}
return null;
}
/// <summary>
/// Get rectangle of the axis range selection.
/// </summary>
/// <returns>Selection rectangle.</returns>
/// <param name="plotAreaPosition">Plot area rectangle.</param>
/// <returns></returns>
private RectangleF GetSelectionRect(RectangleF plotAreaPosition)
{
RectangleF rect = RectangleF.Empty;
if(this._axis != null &&
this.SelectionStart != this.SelectionEnd)
{
double start = (float)this._axis.GetLinearPosition(this.SelectionStart);
double end = (float)this._axis.GetLinearPosition(this.SelectionEnd);
// Detect if cursor is horizontal or vertical
bool horizontal = true;
if(this.GetAxis().AxisPosition == AxisPosition.Bottom || this.GetAxis().AxisPosition == AxisPosition.Top)
{
horizontal = false;
}
if(horizontal)
{
rect.X = plotAreaPosition.X;
rect.Width = plotAreaPosition.Width;
rect.Y = (float)Math.Min(start, end);
rect.Height = (float)Math.Max(start, end) - rect.Y;
}
else
{
rect.Y = plotAreaPosition.Y;
rect.Height = plotAreaPosition.Height;
rect.X = (float)Math.Min(start, end);
rect.Width = (float)Math.Max(start, end) - rect.X;
}
}
return rect;
}
/// <summary>
/// Get rectangle of the opposite axis selection
/// </summary>
/// <param name="plotAreaPosition">Plot area rectangle.</param>
/// <returns>Opposite selection rectangle.</returns>
private RectangleF GetOppositeSelectionRect(RectangleF plotAreaPosition)
{
if(_chartArea != null)
{
// Get opposite cursor
Cursor oppositeCursor =
(_attachedToXAxis == AxisName.X || _attachedToXAxis == AxisName.X2) ?
_chartArea.CursorY : _chartArea.CursorX;
return oppositeCursor.GetSelectionRect(plotAreaPosition);
}
return RectangleF.Empty;
}
/// <summary>
/// Converts X or Y position value to the cursor axis value
/// </summary>
/// <param name="position">Position in relative coordinates.</param>
/// <returns>Cursor position as axis value.</returns>
private double PositionToCursorPosition(PointF position)
{
// Detect if cursor is horizontal or vertical
bool horizontal = true;
if(this.GetAxis().AxisPosition == AxisPosition.Bottom || this.GetAxis().AxisPosition == AxisPosition.Top)
{
horizontal = false;
}
// Convert relative coordinates into axis values
double newCursorPosition = double.NaN;
if(horizontal)
{
newCursorPosition = this.GetAxis().PositionToValue(position.Y);
}
else
{
newCursorPosition = this.GetAxis().PositionToValue(position.X);
}
// Round new position using Step & StepType properties
newCursorPosition = RoundPosition(newCursorPosition);
return newCursorPosition;
}
/// <summary>
/// Checks if specified point is located inside the plotting area.
/// Converts pixel coordinates to relative.
/// </summary>
/// <param name="point">Point coordinates to test.</param>
/// <param name="roundToBoundary">Indicates that coordinates must be rounded to area boundary.</param>
/// <returns>PointF.IsEmpty or relative coordinates in plotting area.</returns>
private PointF GetPositionInPlotArea(PointF point, bool roundToBoundary)
{
PointF result = PointF.Empty;
if(this._chartArea != null && this._chartArea.Common != null && this.GetAxis()!= null)
{
// Translate mouse pixel coordinates into the relative chart area coordinates
result.X = point.X * 100F / ((float)(this._chartArea.Common.Width - 1));
result.Y = point.Y * 100F / ((float)(this._chartArea.Common.Height - 1));
// Round coordinate if it' outside chart plotting area
RectangleF plotAreaPosition = this._chartArea.PlotAreaPosition.ToRectangleF();
if(roundToBoundary)
{
if(result.X < plotAreaPosition.X)
{
result.X = plotAreaPosition.X;
}
if(result.X > plotAreaPosition.Right)
{
result.X = plotAreaPosition.Right;
}
if(result.Y < plotAreaPosition.Y)
{
result.Y = plotAreaPosition.Y;
}
if(result.Y > plotAreaPosition.Bottom)
{
result.Y = plotAreaPosition.Bottom;
}
}
else
{
// Check if coordinate is inside chart plotting area
if(result.X < plotAreaPosition.X ||
result.X > plotAreaPosition.Right ||
result.Y < plotAreaPosition.Y ||
result.Y > plotAreaPosition.Bottom)
{
result = PointF.Empty;
}
}
}
return result;
}
/// <summary>
/// Invalidate chart are with the cursor.
/// </summary>
/// <param name="invalidateArea">Chart area must be invalidated.</param>
private void Invalidate(bool invalidateArea)
{
if(this.GetChartObject() != null && this._chartArea != null && !this.GetChartObject().disableInvalidates)
{
// If data scaleView was scrolled - just invalidate the chart area
if(_viewScrolledOnMouseMove || invalidateArea || this.GetChartObject().dirtyFlag)
{
this._chartArea.Invalidate();
}
// If only cursor/selection position was changed - use optimized drawing algorithm
else
{
// Set flag to redraw cursor/selection only
this.GetChartObject().paintTopLevelElementOnly = true;
// Invalidate and update the chart
this._chartArea.Invalidate();
this.GetChartObject().Update();
// Clear flag to redraw cursor/selection only
this.GetChartObject().paintTopLevelElementOnly = false;
}
}
}
/// <summary>
/// Gets axis objects the cursor is attached to.
/// </summary>
/// <returns>Axis object.</returns>
internal Axis GetAxis()
{
if(_axis == null && _chartArea != null)
{
if(_attachedToXAxis == AxisName.X)
{
_axis = (_axisType == AxisType.Primary) ? _chartArea.AxisX : _chartArea.AxisX2;
}
else
{
_axis = (_axisType == AxisType.Primary) ? _chartArea.AxisY : _chartArea.AxisY2;
}
}
return _axis;
}
#endregion
#region IDisposable Members
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Dispose managed resources
if (this._scrollTimer != null)
{
this._scrollTimer.Dispose();
this._scrollTimer = null;
}
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
/// <summary>
/// The CursorEventArgs class stores the event arguments for cursor and selection events.
/// </summary>
public class CursorEventArgs : EventArgs
{
#region Private fields
// Private fields for properties values storage
private ChartArea _chartArea = null;
private Axis _axis = null;
private double _newPosition = double.NaN;
private double _newSelectionStart = double.NaN;
private double _newSelectionEnd = double.NaN;
#endregion
#region Constructors
/// <summary>
/// CursorEventArgs constructor.
/// </summary>
/// <param name="chartArea">ChartArea of the cursor.</param>
/// <param name="axis">Axis of the cursor.</param>
/// <param name="newPosition">New cursor position.</param>
public CursorEventArgs(ChartArea chartArea, Axis axis, double newPosition)
{
this._chartArea = chartArea;
this._axis = axis;
this._newPosition = newPosition;
this._newSelectionStart = double.NaN;
this._newSelectionEnd = double.NaN;
}
/// <summary>
/// CursorEventArgs constructor.
/// </summary>
/// <param name="chartArea">ChartArea of the cursor.</param>
/// <param name="axis">Axis of the cursor.</param>
/// <param name="newSelectionStart">New range selection starting position.</param>
/// <param name="newSelectionEnd">New range selection ending position.</param>
public CursorEventArgs(ChartArea chartArea, Axis axis, double newSelectionStart, double newSelectionEnd)
{
this._chartArea = chartArea;
this._axis = axis;
this._newPosition = double.NaN;
this._newSelectionStart = newSelectionStart;
this._newSelectionEnd = newSelectionEnd;
}
#endregion
#region Properties
/// <summary>
/// ChartArea of the event.
/// </summary>
[
SRDescription("DescriptionAttributeChartArea"),
]
public ChartArea ChartArea
{
get
{
return _chartArea;
}
}
/// <summary>
/// Axis of the event.
/// </summary>
[
SRDescription("DescriptionAttributeAxis"),
]
public Axis Axis
{
get
{
return _axis;
}
}
/// <summary>
/// New cursor position.
/// </summary>
[
SRDescription("DescriptionAttributeCursorEventArgs_NewPosition"),
]
public double NewPosition
{
get
{
return _newPosition;
}
set
{
_newPosition = value;
}
}
/// <summary>
/// New range selection starting position.
/// </summary>
[
SRDescription("DescriptionAttributeCursorEventArgs_NewSelectionStart"),
]
public double NewSelectionStart
{
get
{
return _newSelectionStart;
}
set
{
_newSelectionStart = value;
}
}
/// <summary>
/// New range selection ending position.
/// </summary>
[
SRDescription("DescriptionAttributeCursorEventArgs_NewSelectionEnd"),
]
public double NewSelectionEnd
{
get
{
return _newSelectionEnd;
}
set
{
_newSelectionEnd = value;
}
}
#endregion
}
}
#endif // #if WINFORMS_CONTROL