File: Common\Annotation\ArrowAnnotation.cs
Project: ndp\fx\src\DataVisualization\System.Windows.Forms.DataVisualization.csproj (System.Windows.Forms.DataVisualization)
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
//   Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
//  File:		ArrowAnnotation.cs
//
//  Namespace:	System.Web.UI.WebControls[Windows.Forms].Charting
//
//	Classes:	ArrowAnnotation
//
//  Purpose:	Arrow annotation classes.
//
//	Reviewed:	
//
//===================================================================
 
#region Used namespace
using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Data;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Text;
using System.Drawing.Drawing2D;
#if Microsoft_CONTROL
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;
 
#else
using System.Web;
using System.Web.UI;
using System.Web.UI.DataVisualization.Charting;
using System.Web.UI.DataVisualization.Charting.Data;
using System.Web.UI.DataVisualization.Charting.Utilities;
using System.Web.UI.DataVisualization.Charting.Borders3D;
#endif
 
 
#endregion
 
#if Microsoft_CONTROL
namespace System.Windows.Forms.DataVisualization.Charting
 
#else
namespace System.Web.UI.DataVisualization.Charting
 
#endif
{
	#region Enumeration
 
	/// <summary>
	/// Arrow annotation styles.
	/// <seealso cref="ArrowAnnotation.ArrowStyle"/>
	/// </summary>
	[
	SRDescription("DescriptionAttributeArrowStyle_ArrowStyle")
	]
	public enum ArrowStyle
	{
		/// <summary>
		/// Simple arrow.
		/// </summary>
		Simple,
 
		/// <summary>
		/// Arrow pointing in two directions.
		/// </summary>
		DoubleArrow,
 
		/// <summary>
		/// Arrow with a tail.
		/// </summary>
		Tailed,
	}
 
	#endregion // Enumeration
 
    /// <summary>
    /// <b>ArrowAnnotation</b> is a class class that represents an arrow annotation.
    /// </summary>
    /// <remarks>
    /// Arrow annotations can be used to connect to points on the chart or highlight a
    /// single chart area. Different arrow styles and sizes may be applied.
    /// </remarks>
	[
		SRDescription("DescriptionAttributeArrowAnnotation_ArrowAnnotation"),
	]
#if ASPPERM_35
	[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
#endif
	public class ArrowAnnotation : Annotation
	{
		#region Fields
 
		// Annotation arrow style
		private		ArrowStyle		_arrowStyle = ArrowStyle.Simple;
 
		// Annotation arrow size
		private		int				_arrowSize = 5;
 
		#endregion
 
		#region Construction and Initialization
 
		/// <summary>
		/// Default public constructor.
		/// </summary>
		public ArrowAnnotation() 
            : base()
		{
            base.AnchorAlignment = ContentAlignment.TopLeft;
		}
 
		#endregion
 
		#region Properties
 
		#region Arrow properties
 
		/// <summary>
		/// Gets or sets the arrow style of an arrow annotation.
		/// <seealso cref="ArrowSize"/>
		/// </summary>
		/// <value>
		/// <see cref="ArrowStyle"/> of an annotation.
		/// </value>
		[
		SRCategory("CategoryAttributeAppearance"),
		Bindable(true),
		DefaultValue(ArrowStyle.Simple),
		SRDescription("DescriptionAttributeArrowAnnotation_ArrowStyle"),
		ParenthesizePropertyNameAttribute(true),
		]
		virtual public ArrowStyle ArrowStyle
		{
			get
			{
				return _arrowStyle;
			}
			set
			{
				_arrowStyle = value;
				Invalidate();
			}
		}
 
		/// <summary>
        /// Gets or sets the arrow size in pixels of an arrow annotation.
		/// <seealso cref="ArrowStyle"/>
		/// </summary>
		/// <value>
		/// Integer value that represents arrow size (thickness) in pixels.
		/// </value>
		[
		SRCategory("CategoryAttributeAppearance"),
		Bindable(true),
		DefaultValue(5),
		SRDescription("DescriptionAttributeArrowAnnotation_ArrowSize"),
		ParenthesizePropertyNameAttribute(true),
		]
		virtual public int ArrowSize
		{
			get
			{
				return _arrowSize;
			}
			set
			{
				if(value <= 0)
				{
					throw(new ArgumentOutOfRangeException("value", SR.ExceptionAnnotationArrowSizeIsZero));
				}
				if(value > 100)
				{
                    throw (new ArgumentOutOfRangeException("value", SR.ExceptionAnnotationArrowSizeMustBeLessThen100));
				}
				_arrowSize = value;
				Invalidate();
			}
		}
 
 
		#endregion // Arrow properties
 
		#region Anchor
 
		/// <summary>
        /// Gets or sets an annotation position's alignment to the anchor point.
		/// <seealso cref="Annotation.AnchorX"/>
		/// <seealso cref="Annotation.AnchorY"/>
		/// <seealso cref="Annotation.AnchorDataPoint"/>
		/// <seealso cref="Annotation.AnchorOffsetX"/>
		/// <seealso cref="Annotation.AnchorOffsetY"/>
		/// </summary>
        /// <value>
        /// A <see cref="ContentAlignment"/> value that represents the annotation's alignment to 
        /// the anchor point.
        /// </value>
        /// <remarks>
        /// The annotation must be anchored using either <see cref="Annotation.AnchorDataPoint"/>, or the <see cref="Annotation.AnchorX"/> 
        /// and <see cref="Annotation.AnchorY"/> properties. Its <see cref="Annotation.X"/> and <see cref="Annotation.Y"/> 
        /// properties must be set to <b>Double.NaN</b>.
        /// </remarks>
		[
		SRCategory("CategoryAttributeAnchor"),
		Browsable(false),
		EditorBrowsableAttribute(EditorBrowsableState.Never),
		DefaultValue(typeof(ContentAlignment), "TopLeft"),
		SRDescription("DescriptionAttributeAnchorAlignment"),
		]
		override public ContentAlignment AnchorAlignment
		{
			get
			{
				return base.AnchorAlignment;
			}
			set
			{
				base.AnchorAlignment = value;
			}
		}
 
		#endregion	// Anchoring
 
		#region Other
 
        /// <summary>
        /// Gets or sets an annotation's type name.
        /// </summary>
        /// <remarks>
        /// This property is used to get the name of each annotation type  
        /// (e.g. Line, Rectangle, Ellipse). 
        /// <para>
        /// This property is for internal use and is hidden at design and run time.
        /// </para>
        /// </remarks>
		[
		SRCategory("CategoryAttributeMisc"),
		Bindable(true),
		Browsable(false),
		EditorBrowsableAttribute(EditorBrowsableState.Never),
		DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
		SerializationVisibilityAttribute(SerializationVisibility.Hidden),
		SRDescription("DescriptionAttributeAnnotationType"),
		]
		public override string AnnotationType
		{
			get
			{
				return "Arrow";
			}
		}
 
		/// <summary>
		/// Gets or sets annotation selection points style.
		/// </summary>
		/// <value>
		/// A <see cref="SelectionPointsStyle"/> value that represents annotation
		/// selection style.
		/// </value>
		/// <remarks>
        /// This property is for internal use and is hidden at design and run time.
		/// </remarks>
		[
		SRCategory("CategoryAttributeAppearance"),
		DefaultValue(SelectionPointsStyle.Rectangle),
		ParenthesizePropertyNameAttribute(true),
		Browsable(false),
		EditorBrowsableAttribute(EditorBrowsableState.Never),
		DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Hidden),
		SerializationVisibilityAttribute(SerializationVisibility.Hidden),
		SRDescription("DescriptionAttributeSelectionPointsStyle"),
		]
		override internal SelectionPointsStyle SelectionPointsStyle
		{
			get
			{
				return SelectionPointsStyle.TwoPoints;
			}
		}
 
		#endregion
 
		#endregion
 
		#region Methods
 
		/// <summary>
		/// Paints annotation object on specified graphics.
		/// </summary>
		/// <param name="graphics">
		/// A <see cref="ChartGraphics"/> used to paint annotation object.
		/// </param>
		/// <param name="chart">
		/// Reference to the <see cref="Chart"/> control.
		/// </param>
        override internal void Paint(Chart chart, ChartGraphics graphics)
		{
			// Get annotation position in relative coordinates
			PointF firstPoint = PointF.Empty;
			PointF anchorPoint = PointF.Empty;
			SizeF size = SizeF.Empty;
			GetRelativePosition(out firstPoint, out size, out anchorPoint);
			PointF	secondPoint = new PointF(firstPoint.X + size.Width, firstPoint.Y + size.Height);
 
			// Create selection rectangle
			RectangleF selectionRect = new RectangleF(firstPoint, new SizeF(secondPoint.X - firstPoint.X, secondPoint.Y - firstPoint.Y));
 
			// Check if text position is valid
			if( float.IsNaN(firstPoint.X) || 
				float.IsNaN(firstPoint.Y) || 
				float.IsNaN(secondPoint.X) || 
				float.IsNaN(secondPoint.Y) )
			{
				return;
			}
 
			// Get arrow shape path
            using (GraphicsPath arrowPathAbs = GetArrowPath(graphics, selectionRect))
            {
 
                // Draw arrow shape
                if (this.Common.ProcessModePaint)
                {
                    graphics.DrawPathAbs(
                        arrowPathAbs,
                        (this.BackColor.IsEmpty) ? Color.White : this.BackColor,
                        this.BackHatchStyle,
                        String.Empty,
                        ChartImageWrapMode.Scaled,
                        Color.Empty,
                        ChartImageAlignmentStyle.Center,
                        this.BackGradientStyle,
                        this.BackSecondaryColor,
                        this.LineColor,
                        this.LineWidth,
                        this.LineDashStyle,
                        PenAlignment.Center,
                        this.ShadowOffset,
                        this.ShadowColor);
                }
 
                // Process hot region
                if (this.Common.ProcessModeRegions)
                {
                    // Use callout defined hot region
                    this.Common.HotRegionsList.AddHotRegion(
                        graphics,
                        arrowPathAbs,
                        false,
                        ReplaceKeywords(this.ToolTip),
#if Microsoft_CONTROL
					String.Empty,
					String.Empty,
					String.Empty,
#else // Microsoft_CONTROL
                    ReplaceKeywords(this.Url),
			        ReplaceKeywords(this.MapAreaAttributes),
                    ReplaceKeywords(this.PostBackValue),
#endif // Microsoft_CONTROL
 this,
                        ChartElementType.Annotation);
                }
 
                // Paint selection handles
                PaintSelectionHandles(graphics, selectionRect, null);
            }
		}
 
		/// <summary>
		/// Get arrow path for the specified annotation position
		/// </summary>
		/// <param name="graphics"></param>
		/// <param name="position"></param>
		/// <returns></returns>
		private GraphicsPath GetArrowPath(
			ChartGraphics graphics,
			RectangleF position)
		{
			// Get absolute position
			RectangleF positionAbs = graphics.GetAbsoluteRectangle(position);
			PointF firstPoint = positionAbs.Location;
			PointF secondPoint = new PointF(positionAbs.Right, positionAbs.Bottom);
 
			// Calculate arrow length
			float deltaX = secondPoint.X - firstPoint.X;
			float deltaY = secondPoint.Y - firstPoint.Y;
			float arrowLength = (float)Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
 
			// Create unrotated graphics path for the arrow started at the annotation location
			// and going to the right for the length of the rotated arrow.
			GraphicsPath path = new GraphicsPath();
 
			PointF[]	points = null;
			float		pointerRatio = 2.1f;
			if(this.ArrowStyle == ArrowStyle.Simple)
			{
				points = new PointF[] {
										  firstPoint,
										  new PointF(firstPoint.X + this.ArrowSize*pointerRatio, firstPoint.Y - this.ArrowSize*pointerRatio),
										  new PointF(firstPoint.X + this.ArrowSize*pointerRatio, firstPoint.Y - this.ArrowSize),
										  new PointF(firstPoint.X + arrowLength, firstPoint.Y - this.ArrowSize),
										  new PointF(firstPoint.X + arrowLength, firstPoint.Y + this.ArrowSize),
										  new PointF(firstPoint.X + this.ArrowSize*pointerRatio, firstPoint.Y + this.ArrowSize),
										  new PointF(firstPoint.X + this.ArrowSize*pointerRatio, firstPoint.Y + this.ArrowSize*pointerRatio) };
			}
			else if(this.ArrowStyle == ArrowStyle.DoubleArrow)
			{
				points = new PointF[] {
										  firstPoint,
										  new PointF(firstPoint.X + this.ArrowSize*pointerRatio, firstPoint.Y - this.ArrowSize*pointerRatio),
										  new PointF(firstPoint.X + this.ArrowSize*pointerRatio, firstPoint.Y - this.ArrowSize),
										  new PointF(firstPoint.X + arrowLength - this.ArrowSize*pointerRatio, firstPoint.Y - this.ArrowSize),
										  new PointF(firstPoint.X + arrowLength - this.ArrowSize*pointerRatio, firstPoint.Y - this.ArrowSize*pointerRatio),
										  new PointF(firstPoint.X + arrowLength, firstPoint.Y),
										  new PointF(firstPoint.X + arrowLength - this.ArrowSize*pointerRatio, firstPoint.Y + this.ArrowSize*pointerRatio),
										  new PointF(firstPoint.X + arrowLength - this.ArrowSize*pointerRatio, firstPoint.Y + this.ArrowSize),
										  new PointF(firstPoint.X + this.ArrowSize*pointerRatio, firstPoint.Y + this.ArrowSize),
										  new PointF(firstPoint.X + this.ArrowSize*pointerRatio, firstPoint.Y + this.ArrowSize*pointerRatio) };
			}
			else if(this.ArrowStyle == ArrowStyle.Tailed)
			{
				float		tailRatio = 2.1f;
				points = new PointF[] {
										  firstPoint,
										  new PointF(firstPoint.X + this.ArrowSize*pointerRatio, firstPoint.Y - this.ArrowSize*pointerRatio),
										  new PointF(firstPoint.X + this.ArrowSize*pointerRatio, firstPoint.Y - this.ArrowSize),
										  new PointF(firstPoint.X + arrowLength, firstPoint.Y - this.ArrowSize*tailRatio),
										  new PointF(firstPoint.X + arrowLength - this.ArrowSize*tailRatio, firstPoint.Y),
										  new PointF(firstPoint.X + arrowLength, firstPoint.Y + this.ArrowSize*tailRatio),
										  new PointF(firstPoint.X + this.ArrowSize*pointerRatio, firstPoint.Y + this.ArrowSize),
										  new PointF(firstPoint.X + this.ArrowSize*pointerRatio, firstPoint.Y + this.ArrowSize*pointerRatio) };
			}
			else
			{
                throw (new InvalidOperationException(SR.ExceptionAnnotationArrowStyleUnknown));
			}
 
			path.AddLines(points);
			path.CloseAllFigures();
 
			// Calculate arrow angle
			float angle = (float)(Math.Atan(deltaY / deltaX) * 180f / Math.PI);
			if(deltaX < 0)
			{
				angle += 180f;
			}
 
			// Rotate arrow path around the first point
			using( Matrix matrix = new Matrix() )
			{
				matrix.RotateAt(angle, firstPoint);
				path.Transform(matrix);
			}
 
			return path;
		}
 
		#endregion
	}
}