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

1902 lines
62 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: CalloutAnnotation.cs
//
// Namespace: System.Web.UI.WebControls[Windows.Forms].Charting
//
// Classes: CalloutAnnotation
//
// Purpose: Callout 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 Enumerations
/// <summary>
/// Annotation callout style.
/// <seealso cref="CalloutAnnotation.CalloutStyle"/>
/// </summary>
[
SRDescription("DescriptionAttributeCalloutStyle_CalloutStyle"),
]
public enum CalloutStyle
{
/// <summary>
/// Callout text is underlined and a line is pointing to the anchor point.
/// </summary>
SimpleLine,
/// <summary>
/// Border is drawn around text and a line is pointing to the anchor point.
/// </summary>
Borderline,
/// <summary>
/// Callout text is inside the cloud and smaller clouds are pointing to the anchor point.
/// </summary>
Cloud,
/// <summary>
/// Rectangle is drawn around the callout text, which is connected with the anchor point.
/// </summary>
Rectangle,
/// <summary>
/// Rounded rectangle is drawn around the callout text, which is connected with the anchor point.
/// </summary>
RoundedRectangle,
/// <summary>
/// Ellipse is drawn around the callout text, which is connected with the anchor point.
/// </summary>
Ellipse,
/// <summary>
/// Perspective rectangle is drawn around the callout text, which is connected with the anchor point.
/// </summary>
Perspective,
}
#endregion
/// <summary>
/// <b>CalloutAnnotation</b> is a class class that represents a callout annotation.
/// </summary>
/// <remarks>
/// Callout annotation is the only annotation that draws a connection between the
/// annotation position and anchor point. It can display text and automatically
/// calculate the required size. Different <see cref="CalloutStyle"/> are supported.
/// </remarks>
[
SRDescription("DescriptionAttributeCalloutAnnotation_CalloutAnnotation"),
]
#if ASPPERM_35
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
#endif
public class CalloutAnnotation : TextAnnotation
{
#region Fields
// Callout anchor type
private LineAnchorCapStyle _calloutAnchorCap = LineAnchorCapStyle.Arrow;
// Callout drawing style
private CalloutStyle _calloutStyle = CalloutStyle.Rectangle;
// Cloud shape path
private static GraphicsPath _cloudPath = null;
// Cloud shape outline path
private static GraphicsPath _cloudOutlinePath = null;
// Cloud shape boundary rectangle
private static RectangleF _cloudBounds = RectangleF.Empty;
#endregion
#region Construction and Initialization
/// <summary>
/// Default public constructor.
/// </summary>
public CalloutAnnotation()
: base()
{
// Changing default values of properties
this.anchorOffsetX = 3.0;
this.anchorOffsetY = 3.0;
this.anchorAlignment = ContentAlignment.BottomLeft;
}
#endregion
#region Properties
#region Callout properties
/// <summary>
/// Gets or sets the annotation callout style.
/// </summary>
/// <value>
/// <see cref="CalloutStyle"/> of the annotation.
/// </value>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(CalloutStyle.Rectangle),
SRDescription("DescriptionAttributeCalloutAnnotation_CalloutStyle"),
ParenthesizePropertyNameAttribute(true),
]
virtual public CalloutStyle CalloutStyle
{
get
{
return _calloutStyle;
}
set
{
_calloutStyle = value;
this.ResetCurrentRelativePosition();
// Reset content size to empty
contentSize = SizeF.Empty;
Invalidate();
}
}
/// <summary>
/// Gets or sets the anchor cap style of a callout line.
/// </summary>
/// <value>
/// A <see cref="LineAnchorCapStyle"/> value used as the anchor cap of a callout line.
/// </value>
/// <remarks>
/// This property sets the anchor cap of the line connecting an annotation to
/// its anchor point. It only applies when SimpleLine or BorderLine
/// are used.
/// </remarks>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(LineAnchorCapStyle.Arrow),
SRDescription("DescriptionAttributeCalloutAnnotation_CalloutAnchorCap"),
]
virtual public LineAnchorCapStyle CalloutAnchorCap
{
get
{
return _calloutAnchorCap;
}
set
{
_calloutAnchorCap = value;
Invalidate();
}
}
#endregion // Callout properties
#region Applicable Annotation Appearance Attributes (set as Browsable)
/// <summary>
/// Gets or sets the color of an annotation line.
/// <seealso cref="LineWidth"/>
/// <seealso cref="LineDashStyle"/>
/// </summary>
/// <value>
/// A <see cref="Color"/> value used to draw an annotation line.
/// </value>
[
SRCategory("CategoryAttributeAppearance"),
Browsable(true),
DefaultValue(typeof(Color), "Black"),
SRDescription("DescriptionAttributeLineColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base)
]
override public Color LineColor
{
get
{
return base.LineColor;
}
set
{
base.LineColor = value;
}
}
/// <summary>
/// Gets or sets the width of an annotation line.
/// <seealso cref="LineColor"/>
/// <seealso cref="LineDashStyle"/>
/// </summary>
/// <value>
/// An integer value defining the width of an annotation line in pixels.
/// </value>
[
SRCategory("CategoryAttributeAppearance"),
Browsable(true),
DefaultValue(1),
SRDescription("DescriptionAttributeLineWidth"),
]
override public int LineWidth
{
get
{
return base.LineWidth;
}
set
{
base.LineWidth = value;
}
}
/// <summary>
/// Gets or sets the style of an annotation line.
/// <seealso cref="LineWidth"/>
/// <seealso cref="LineColor"/>
/// </summary>
/// <value>
/// A <see cref="ChartDashStyle"/> value used to draw an annotation line.
/// </value>
[
SRCategory("CategoryAttributeAppearance"),
Browsable(true),
DefaultValue(ChartDashStyle.Solid),
SRDescription("DescriptionAttributeLineDashStyle"),
]
override public ChartDashStyle LineDashStyle
{
get
{
return base.LineDashStyle;
}
set
{
base.LineDashStyle = value;
}
}
/// <summary>
/// Gets or sets the background color of an annotation.
/// <seealso cref="BackSecondaryColor"/>
/// <seealso cref="BackHatchStyle"/>
/// <seealso cref="BackGradientStyle"/>
/// </summary>
/// <value>
/// A <see cref="Color"/> value used for the background of an annotation.
/// </value>
[
SRCategory("CategoryAttributeAppearance"),
Browsable(true),
DefaultValue(typeof(Color), ""),
SRDescription("DescriptionAttributeBackColor"),
NotifyParentPropertyAttribute(true),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base)
]
override public Color BackColor
{
get
{
return base.BackColor;
}
set
{
base.BackColor = value;
}
}
/// <summary>
/// Gets or sets the background hatch style of an annotation.
/// <seealso cref="BackSecondaryColor"/>
/// <seealso cref="BackColor"/>
/// <seealso cref="BackGradientStyle"/>
/// </summary>
/// <value>
/// A <see cref="ChartHatchStyle"/> value used for the background of an annotation.
/// </value>
/// <remarks>
/// Two colors are used to draw the hatching, <see cref="BackColor"/> and <see cref="BackSecondaryColor"/>.
/// </remarks>
[
SRCategory("CategoryAttributeAppearance"),
Browsable(true),
DefaultValue(ChartHatchStyle.None),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeBackHatchStyle"),
Editor(Editors.HatchStyleEditor.Editor, Editors.HatchStyleEditor.Base)
]
override public ChartHatchStyle BackHatchStyle
{
get
{
return base.BackHatchStyle;
}
set
{
base.BackHatchStyle = value;
}
}
/// <summary>
/// Gets or sets the background gradient style of an annotation.
/// <seealso cref="BackSecondaryColor"/>
/// <seealso cref="BackColor"/>
/// <seealso cref="BackHatchStyle"/>
/// </summary>
/// <value>
/// A <see cref="GradientStyle"/> value used for the background of an annotation.
/// </value>
/// <remarks>
/// Two colors are used to draw the gradient, <see cref="BackColor"/> and <see cref="BackSecondaryColor"/>.
/// </remarks>
[
SRCategory("CategoryAttributeAppearance"),
Browsable(true),
DefaultValue(GradientStyle.None),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeBackGradientStyle"),
Editor(Editors.GradientEditor.Editor, Editors.GradientEditor.Base)
]
override public GradientStyle BackGradientStyle
{
get
{
return base.BackGradientStyle;
}
set
{
base.BackGradientStyle = value;
}
}
/// <summary>
/// Gets or sets the secondary background color of an annotation.
/// <seealso cref="BackColor"/>
/// <seealso cref="BackHatchStyle"/>
/// <seealso cref="BackGradientStyle"/>
/// </summary>
/// <value>
/// A <see cref="Color"/> value used for the secondary color of an annotation background with
/// hatching or gradient fill.
/// </value>
/// <remarks>
/// This color is used with <see cref="BackColor"/> when <see cref="BackHatchStyle"/> or
/// <see cref="BackGradientStyle"/> are used.
/// </remarks>
[
SRCategory("CategoryAttributeAppearance"),
Browsable(true),
DefaultValue(typeof(Color), ""),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeBackSecondaryColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base)
]
override public Color BackSecondaryColor
{
get
{
return base.BackSecondaryColor;
}
set
{
base.BackSecondaryColor = value;
}
}
#endregion
#region Anchor
/// <summary>
/// Gets or sets the x-coordinate offset between the positions of an annotation and its anchor point.
/// <seealso cref="AnchorOffsetY"/>
/// <seealso cref="Annotation.AnchorDataPoint"/>
/// <seealso cref="Annotation.AnchorX"/>
/// <seealso cref="AnchorAlignment"/>
/// </summary>
/// <value>
/// A double value that represents the x-coordinate offset between the positions of an annotation and its anchor point.
/// </value>
/// <remarks>
/// The annotation must be anchored using the <see cref="Annotation.AnchorDataPoint"/> or
/// <see cref="Annotation.AnchorX"/> properties, and its <see cref="Annotation.X"/> property must be set
/// to <b>Double.NaN</b>.
/// </remarks>
[
SRCategory("CategoryAttributeAnchor"),
DefaultValue(3.0),
SRDescription("DescriptionAttributeCalloutAnnotation_AnchorOffsetX"),
RefreshPropertiesAttribute(RefreshProperties.All),
]
override public double AnchorOffsetX
{
get
{
return base.AnchorOffsetX;
}
set
{
base.AnchorOffsetX = value;
}
}
/// <summary>
/// Gets or sets the y-coordinate offset between the positions of an annotation and its anchor point.
/// <seealso cref="Annotation.AnchorOffsetX"/>
/// <seealso cref="Annotation.AnchorDataPoint"/>
/// <seealso cref="Annotation.AnchorY"/>
/// <seealso cref="Annotation.AnchorAlignment"/>
/// </summary>
/// <value>
/// A double value that represents the y-coordinate offset between the positions of an annotation and its anchor point.
/// </value>
/// <remarks>
/// Annotation must be anchored using <see cref="Annotation.AnchorDataPoint"/> or
/// <see cref="Annotation.AnchorY"/> properties and its <see cref="Annotation.Y"/> property must be set
/// to <b>Double.NaN</b>.
/// </remarks>
[
SRCategory("CategoryAttributeAnchor"),
DefaultValue(3.0),
SRDescription("DescriptionAttributeCalloutAnnotation_AnchorOffsetY"),
RefreshPropertiesAttribute(RefreshProperties.All),
]
override public double AnchorOffsetY
{
get
{
return base.AnchorOffsetY;
}
set
{
base.AnchorOffsetY = value;
}
}
/// <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="AnchorOffsetX"/>
/// <seealso cref="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"),
DefaultValue(typeof(ContentAlignment), "BottomLeft"),
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 "Callout";
}
}
/// <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.Rectangle;
}
}
#endregion
#endregion
#region Methods
#region Text Spacing
/// <summary>
/// Gets text spacing on four different sides in relative coordinates.
/// </summary>
/// <param name="annotationRelative">Indicates that spacing is in annotation relative coordinates.</param>
/// <returns>Rectangle with text spacing values.</returns>
internal override RectangleF GetTextSpacing(out bool annotationRelative)
{
RectangleF spacing = base.GetTextSpacing(out annotationRelative);
if(this._calloutStyle == CalloutStyle.Cloud ||
this._calloutStyle == CalloutStyle.Ellipse)
{
spacing = new RectangleF(4f, 4f, 4f, 4f);
annotationRelative = true;
}
else if(this._calloutStyle == CalloutStyle.RoundedRectangle)
{
spacing = new RectangleF(1f, 1f, 1f, 1f);
annotationRelative = true;
}
return spacing;
}
#endregion // Text Spacing
#region Painting
/// <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));
// Adjust negative rectangle width and height
RectangleF rectanglePosition = new RectangleF(selectionRect.Location, selectionRect.Size);
if(rectanglePosition.Width < 0)
{
rectanglePosition.X = rectanglePosition.Right;
rectanglePosition.Width = -rectanglePosition.Width;
}
if(rectanglePosition.Height < 0)
{
rectanglePosition.Y = rectanglePosition.Bottom;
rectanglePosition.Height = -rectanglePosition.Height;
}
// Check if position is valid
if( float.IsNaN(rectanglePosition.X) ||
float.IsNaN(rectanglePosition.Y) ||
float.IsNaN(rectanglePosition.Right) ||
float.IsNaN(rectanglePosition.Bottom) )
{
return;
}
// Paint different style of callouts
GraphicsPath hotRegionPathAbs = null;
if(this.Common.ProcessModePaint)
{
switch(this._calloutStyle)
{
case(CalloutStyle.SimpleLine):
hotRegionPathAbs = DrawRectangleLineCallout(
graphics,
rectanglePosition,
anchorPoint,
false);
break;
case(CalloutStyle.Borderline):
hotRegionPathAbs = DrawRectangleLineCallout(
graphics,
rectanglePosition,
anchorPoint,
true);
break;
case(CalloutStyle.Perspective):
hotRegionPathAbs = DrawPerspectiveCallout(
graphics,
rectanglePosition,
anchorPoint);
break;
case(CalloutStyle.Cloud):
hotRegionPathAbs = DrawCloudCallout(
graphics,
rectanglePosition,
anchorPoint);
break;
case(CalloutStyle.Rectangle):
hotRegionPathAbs = DrawRectangleCallout(
graphics,
rectanglePosition,
anchorPoint);
break;
case(CalloutStyle.Ellipse):
hotRegionPathAbs = DrawRoundedRectCallout(
graphics,
rectanglePosition,
anchorPoint,
true);
break;
case(CalloutStyle.RoundedRectangle):
hotRegionPathAbs = DrawRoundedRectCallout(
graphics,
rectanglePosition,
anchorPoint,
false);
break;
}
}
if(this.Common.ProcessModeRegions)
{
if(hotRegionPathAbs != null)
{
// If there is more then one graphical path split them and create
// image maps for every graphical path separately.
GraphicsPathIterator iterator = new GraphicsPathIterator(hotRegionPathAbs);
// There is more then one path.
using (GraphicsPath subPath = new GraphicsPath())
{
while (iterator.NextMarker(subPath) > 0)
{
// Use callout defined hot region
this.Common.HotRegionsList.AddHotRegion(
graphics,
subPath,
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);
// Reset current path
subPath.Reset();
}
}
}
else
{
// Use rectangular hot region
this.Common.HotRegionsList.AddHotRegion(
rectanglePosition,
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,
String.Empty);
}
}
//Clean up
if (hotRegionPathAbs != null)
hotRegionPathAbs.Dispose();
// Paint selection handles
PaintSelectionHandles(graphics, selectionRect, null);
}
/// <summary>
/// Draws Rounded rectangle or Ellipse style callout.
/// </summary>
/// <param name="graphics">Chart graphics.</param>
/// <param name="rectanglePosition">Position of annotation objet.</param>
/// <param name="anchorPoint">Anchor location.</param>
/// <param name="isEllipse">True if ellipse shape should be used.</param>
/// <returns>Hot region of the callout.</returns>
private GraphicsPath DrawRoundedRectCallout(
ChartGraphics graphics,
RectangleF rectanglePosition,
PointF anchorPoint,
bool isEllipse)
{
// Get absolute position
RectangleF rectanglePositionAbs = graphics.GetAbsoluteRectangle(rectanglePosition);
// NOTE: Fix for issue #6692.
// Do not draw the callout if size is not set. This may happen if callou text is set to empty string.
if (rectanglePositionAbs.Width <= 0 || rectanglePositionAbs.Height <= 0)
{
return null;
}
// Create ellipse path
GraphicsPath ellipsePath = new GraphicsPath();
if(isEllipse)
{
// Add ellipse shape
ellipsePath.AddEllipse(rectanglePositionAbs);
}
else
{
// Add rounded rectangle shape
float radius = Math.Min(rectanglePositionAbs.Width, rectanglePositionAbs.Height);
radius /= 5f;
ellipsePath = this.CreateRoundedRectPath(rectanglePositionAbs, radius);
}
// Draw perspective polygons from anchoring point
if(!float.IsNaN(anchorPoint.X) && !float.IsNaN(anchorPoint.Y))
{
// Check if point is inside annotation position
if(!rectanglePosition.Contains(anchorPoint.X, anchorPoint.Y))
{
// Get absolute anchor point
PointF anchorPointAbs = graphics.GetAbsolutePoint(new PointF(anchorPoint.X, anchorPoint.Y));
// Flatten ellipse path
ellipsePath.Flatten();
// Find point in the path closest to the anchor point
PointF[] points = ellipsePath.PathPoints;
int closestPointIndex = 0;
int index = 0;
float currentDistance = float.MaxValue;
foreach(PointF point in points)
{
float deltaX = point.X - anchorPointAbs.X;
float deltaY = point.Y - anchorPointAbs.Y;
float distance = deltaX * deltaX + deltaY * deltaY;
if(distance < currentDistance)
{
currentDistance = distance;
closestPointIndex = index;
}
++ index;
}
// Change point to the anchor location
points[closestPointIndex] = anchorPointAbs;
// Recreate ellipse path
ellipsePath.Reset();
ellipsePath.AddLines(points);
ellipsePath.CloseAllFigures();
}
}
// Draw ellipse
graphics.DrawPathAbs(
ellipsePath,
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);
// Draw text
DrawText(graphics, rectanglePosition, true, false);
return ellipsePath;
}
/// <summary>
/// Draws Rectangle style callout.
/// </summary>
/// <param name="graphics">Chart graphics.</param>
/// <param name="rectanglePosition">Position of annotation objet.</param>
/// <param name="anchorPoint">Anchor location.</param>
/// <returns>Hot region of the callout.</returns>
private GraphicsPath DrawRectangleCallout(
ChartGraphics graphics,
RectangleF rectanglePosition,
PointF anchorPoint)
{
// Create path for the rectangle connected with anchor point.
GraphicsPath hotRegion = null;
bool anchorVisible = false;
if(!float.IsNaN(anchorPoint.X) && !float.IsNaN(anchorPoint.Y))
{
// Get relative size of a pixel
SizeF pixelSize = graphics.GetRelativeSize(new SizeF(1f, 1f));
// Increase annotation position rectangle by 1 pixel
RectangleF inflatedPosition = new RectangleF(rectanglePosition.Location, rectanglePosition.Size);
inflatedPosition.Inflate(pixelSize);
// Check if point is inside annotation position
if(!inflatedPosition.Contains(anchorPoint.X, anchorPoint.Y))
{
anchorVisible = true;
// Get absolute position
RectangleF rectanglePositionAbs = graphics.GetAbsoluteRectangle(rectanglePosition);
// Get absolute anchor point
PointF anchorPointAbs = graphics.GetAbsolutePoint(new PointF(anchorPoint.X, anchorPoint.Y));
// Calculate anchor pointer thicness
float size = Math.Min(rectanglePositionAbs.Width, rectanglePositionAbs.Height);
size /= 4f;
// Create shape points
PointF[] points = new PointF[7];
if(anchorPoint.X < rectanglePosition.X &&
anchorPoint.Y > rectanglePosition.Bottom)
{
points[0] = rectanglePositionAbs.Location;
points[1] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y);
points[2] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Bottom);
points[3] = new PointF(rectanglePositionAbs.X + size, rectanglePositionAbs.Bottom);
points[4] = anchorPointAbs;
points[5] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom - size);
points[6] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom - size);
}
else if(anchorPoint.X >= rectanglePosition.X &&
anchorPoint.X <= rectanglePosition.Right &&
anchorPoint.Y > rectanglePosition.Bottom)
{
points[0] = rectanglePositionAbs.Location;
points[1] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y);
points[2] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Bottom);
points[3] = new PointF(rectanglePositionAbs.X + rectanglePositionAbs.Width / 2f + size, rectanglePositionAbs.Bottom);
points[4] = anchorPointAbs;
points[5] = new PointF(rectanglePositionAbs.X + rectanglePositionAbs.Width / 2f - size, rectanglePositionAbs.Bottom);
points[6] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
}
else if(anchorPoint.X > rectanglePosition.Right &&
anchorPoint.Y > rectanglePosition.Bottom)
{
points[0] = rectanglePositionAbs.Location;
points[1] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y);
points[2] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Bottom - size);
points[3] = anchorPointAbs;
points[4] = new PointF(rectanglePositionAbs.Right - size, rectanglePositionAbs.Bottom);
points[5] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
points[6] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
}
else if(anchorPoint.X > rectanglePosition.Right &&
anchorPoint.Y <= rectanglePosition.Bottom &&
anchorPoint.Y >= rectanglePosition.Y)
{
points[0] = rectanglePositionAbs.Location;
points[1] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y);
points[2] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y + rectanglePositionAbs.Height / 2f - size);
points[3] = anchorPointAbs;
points[4] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y + rectanglePositionAbs.Height / 2f + size);
points[5] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Bottom);
points[6] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
}
else if(anchorPoint.X > rectanglePosition.Right &&
anchorPoint.Y < rectanglePosition.Y)
{
points[0] = rectanglePositionAbs.Location;
points[1] = new PointF(rectanglePositionAbs.Right - size, rectanglePositionAbs.Y);
points[2] = anchorPointAbs;
points[3] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y + size);
points[4] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Bottom);
points[5] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
points[6] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
}
else if(anchorPoint.X >= rectanglePosition.X &&
anchorPoint.X <= rectanglePosition.Right &&
anchorPoint.Y < rectanglePosition.Y)
{
points[0] = rectanglePositionAbs.Location;
points[1] = new PointF(rectanglePositionAbs.X + rectanglePositionAbs.Width/2f - size, rectanglePositionAbs.Y);
points[2] = anchorPointAbs;
points[3] = new PointF(rectanglePositionAbs.X + rectanglePositionAbs.Width/2f + size, rectanglePositionAbs.Y);
points[4] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y);
points[5] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Bottom);
points[6] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
}
else if(anchorPoint.X < rectanglePosition.X &&
anchorPoint.Y < rectanglePosition.Y)
{
points[0] = anchorPointAbs;
points[1] = new PointF(rectanglePositionAbs.X + size, rectanglePositionAbs.Y);
points[2] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y);
points[3] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Bottom);
points[4] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
points[5] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Y + size);
points[6] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Y + size);
}
else if(anchorPoint.X < rectanglePosition.X &&
anchorPoint.Y >= rectanglePosition.Y &&
anchorPoint.Y <= rectanglePosition.Bottom)
{
points[0] = rectanglePositionAbs.Location;
points[1] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y);
points[2] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Bottom);
points[3] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
points[4] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Y + rectanglePositionAbs.Height/2f + size );
points[5] = anchorPointAbs;
points[6] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Y + rectanglePositionAbs.Height/2f - size );
}
// Create graphics path of the callout
hotRegion = new GraphicsPath();
hotRegion.AddLines(points);
hotRegion.CloseAllFigures();
// Draw callout
graphics.DrawPathAbs(
hotRegion,
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);
}
}
// Draw rectangle if anchor is not visible
if(!anchorVisible)
{
graphics.FillRectangleRel(
rectanglePosition,
this.BackColor,
this.BackHatchStyle,
String.Empty,
ChartImageWrapMode.Scaled,
Color.Empty,
ChartImageAlignmentStyle.Center,
this.BackGradientStyle,
this.BackSecondaryColor,
this.LineColor,
this.LineWidth,
this.LineDashStyle,
this.ShadowColor,
this.ShadowOffset,
PenAlignment.Center);
// Get hot region
hotRegion = new GraphicsPath();
hotRegion.AddRectangle( graphics.GetAbsoluteRectangle(rectanglePosition) );
}
// Draw text
DrawText(graphics, rectanglePosition, false, false);
return hotRegion;
}
/// <summary>
/// Draws Perspective style callout.
/// </summary>
/// <param name="graphics">Chart graphics.</param>
/// <param name="rectanglePosition">Position of annotation objet.</param>
/// <param name="anchorPoint">Anchor location.</param>
/// <returns>Hot region of the cloud.</returns>
private GraphicsPath DrawCloudCallout(
ChartGraphics graphics,
RectangleF rectanglePosition,
PointF anchorPoint)
{
// Get absolute position
RectangleF rectanglePositionAbs = graphics.GetAbsoluteRectangle(rectanglePosition);
// Draw perspective polygons from anchoring point
if(!float.IsNaN(anchorPoint.X) && !float.IsNaN(anchorPoint.Y))
{
// Check if point is inside annotation position
if(!rectanglePosition.Contains(anchorPoint.X, anchorPoint.Y))
{
// Get center point of the cloud
PointF cloudCenterAbs = graphics.GetAbsolutePoint(
new PointF(
rectanglePosition.X + rectanglePosition.Width / 2f,
rectanglePosition.Y + rectanglePosition.Height / 2f) );
// Calculate absolute ellipse size and position
SizeF ellipseSize = graphics.GetAbsoluteSize(
new SizeF(rectanglePosition.Width, rectanglePosition.Height));
ellipseSize.Width /= 10f;
ellipseSize.Height /= 10f;
PointF anchorPointAbs = graphics.GetAbsolutePoint(
new PointF(anchorPoint.X, anchorPoint.Y));
PointF ellipseLocation = anchorPointAbs;
// Get distance between anchor point and center of the cloud
float dxAbs = anchorPointAbs.X - cloudCenterAbs.X;
float dyAbs = anchorPointAbs.Y - cloudCenterAbs.Y;
PointF point = PointF.Empty;
if(anchorPoint.Y < rectanglePosition.Y)
{
point = GetIntersectionY(cloudCenterAbs, anchorPointAbs, rectanglePositionAbs.Y);
if(point.X < rectanglePositionAbs.X)
{
point = GetIntersectionX(cloudCenterAbs, anchorPointAbs, rectanglePositionAbs.X);
}
else if(point.X > rectanglePositionAbs.Right)
{
point = GetIntersectionX(cloudCenterAbs, anchorPointAbs, rectanglePositionAbs.Right);
}
}
else if(anchorPoint.Y > rectanglePosition.Bottom)
{
point = GetIntersectionY(cloudCenterAbs, anchorPointAbs, rectanglePositionAbs.Bottom);
if(point.X < rectanglePositionAbs.X)
{
point = GetIntersectionX(cloudCenterAbs, anchorPointAbs, rectanglePositionAbs.X);
}
else if(point.X > rectanglePositionAbs.Right)
{
point = GetIntersectionX(cloudCenterAbs, anchorPointAbs, rectanglePositionAbs.Right);
}
}
else
{
if(anchorPoint.X < rectanglePosition.X)
{
point = GetIntersectionX(cloudCenterAbs, anchorPointAbs, rectanglePositionAbs.X);
}
else
{
point = GetIntersectionX(cloudCenterAbs, anchorPointAbs, rectanglePositionAbs.Right);
}
}
SizeF size = new SizeF(Math.Abs(cloudCenterAbs.X - point.X), Math.Abs(cloudCenterAbs.Y - point.Y));
if(dxAbs > 0)
dxAbs -= size.Width;
else
dxAbs += size.Width;
if(dyAbs > 0)
dyAbs -= size.Height;
else
dyAbs += size.Height;
// Draw 3 smaller ellipses from anchor point to the cloud
for(int index = 0; index < 3; index++)
{
using( GraphicsPath path = new GraphicsPath() )
{
// Create ellipse path
path.AddEllipse(
ellipseLocation.X - ellipseSize.Width / 2f,
ellipseLocation.Y - ellipseSize.Height / 2f,
ellipseSize.Width,
ellipseSize.Height);
// Draw ellipse
graphics.DrawPathAbs(
path,
this.BackColor,
this.BackHatchStyle,
String.Empty,
ChartImageWrapMode.Scaled,
Color.Empty,
ChartImageAlignmentStyle.Center,
this.BackGradientStyle,
this.BackSecondaryColor,
this.LineColor,
1, // this.LineWidth, NOTE: Cloud supports only 1 pixel border
this.LineDashStyle,
PenAlignment.Center,
this.ShadowOffset,
this.ShadowColor);
// Adjust ellipse size
ellipseSize.Width *= 1.5f;
ellipseSize.Height *= 1.5f;
// Adjust next ellipse position
ellipseLocation.X -= dxAbs / 3f + (index * (dxAbs / 10f) );
ellipseLocation.Y -= dyAbs / 3f + (index * (dyAbs / 10f) );
}
}
}
}
// Draw cloud
GraphicsPath pathCloud = GetCloudPath(rectanglePositionAbs);
graphics.DrawPathAbs(
pathCloud,
this.BackColor,
this.BackHatchStyle,
String.Empty,
ChartImageWrapMode.Scaled,
Color.Empty,
ChartImageAlignmentStyle.Center,
this.BackGradientStyle,
this.BackSecondaryColor,
this.LineColor,
1, // this.LineWidth, NOTE: Cloud supports only 1 pixel border
this.LineDashStyle,
PenAlignment.Center,
this.ShadowOffset,
this.ShadowColor);
// Draw cloud outline (Do not draw in SVG or Flash Animation)
{
using(GraphicsPath pathCloudOutline = GetCloudOutlinePath(rectanglePositionAbs))
{
graphics.DrawPathAbs(
pathCloudOutline,
this.BackColor,
this.BackHatchStyle,
String.Empty,
ChartImageWrapMode.Scaled,
Color.Empty,
ChartImageAlignmentStyle.Center,
this.BackGradientStyle,
this.BackSecondaryColor,
this.LineColor,
1, // this.LineWidth, NOTE: Cloud supports only 1 pixel border
this.LineDashStyle,
PenAlignment.Center);
}
}
// Draw text
DrawText(graphics, rectanglePosition, true, false);
return pathCloud;
}
/// <summary>
/// Draws Perspective style callout.
/// </summary>
/// <param name="graphics">Chart graphics.</param>
/// <param name="rectanglePosition">Position of annotation objet.</param>
/// <param name="anchorPoint">Anchor location.</param>
/// <returns>Hot region of the cloud.</returns>
private GraphicsPath DrawPerspectiveCallout(
ChartGraphics graphics,
RectangleF rectanglePosition,
PointF anchorPoint)
{
// Draw rectangle
graphics.FillRectangleRel(
rectanglePosition,
this.BackColor,
this.BackHatchStyle,
String.Empty,
ChartImageWrapMode.Scaled,
Color.Empty,
ChartImageAlignmentStyle.Center,
this.BackGradientStyle,
this.BackSecondaryColor,
this.LineColor,
this.LineWidth,
this.LineDashStyle,
this.ShadowColor,
0, // Shadow is never drawn
PenAlignment.Center);
// Create hot region path
GraphicsPath hotRegion = new GraphicsPath();
hotRegion.AddRectangle( graphics.GetAbsoluteRectangle(rectanglePosition) );
// Draw text
DrawText(graphics, rectanglePosition, false, false);
// Draw perspective polygons from anchoring point
if(!float.IsNaN(anchorPoint.X) && !float.IsNaN(anchorPoint.Y))
{
// Check if point is inside annotation position
if(!rectanglePosition.Contains(anchorPoint.X, anchorPoint.Y))
{
Color[] perspectivePathColors = new Color[2];
Color color = (this.BackColor.IsEmpty) ? Color.White : this.BackColor;
perspectivePathColors[0] = graphics.GetBrightGradientColor(color, 0.6);
perspectivePathColors[1] = graphics.GetBrightGradientColor(color, 0.8);
GraphicsPath[] perspectivePaths = new GraphicsPath[2];
using(perspectivePaths[0] = new GraphicsPath())
{
using(perspectivePaths[1] = new GraphicsPath())
{
// Convert coordinates to absolute
RectangleF rectanglePositionAbs = graphics.GetAbsoluteRectangle(rectanglePosition);
PointF anchorPointAbs = graphics.GetAbsolutePoint(anchorPoint);
// Create paths of perspective
if(anchorPoint.Y < rectanglePosition.Y)
{
PointF[] points1 = new PointF[3];
points1[0] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Y);
points1[1] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y);
points1[2] = new PointF(anchorPointAbs.X, anchorPointAbs.Y);
perspectivePaths[0].AddLines(points1);
if(anchorPoint.X < rectanglePosition.X)
{
PointF[] points2 = new PointF[3];
points2[0] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
points2[1] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Y);
points2[2] = new PointF(anchorPointAbs.X, anchorPointAbs.Y);
perspectivePaths[1].AddLines(points2);
}
else if(anchorPoint.X > rectanglePosition.Right)
{
PointF[] points2 = new PointF[3];
points2[0] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Bottom);
points2[1] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y);
points2[2] = new PointF(anchorPointAbs.X, anchorPointAbs.Y);
perspectivePaths[1].AddLines(points2);
}
}
else if(anchorPoint.Y > rectanglePosition.Bottom)
{
PointF[] points1 = new PointF[3];
points1[0] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
points1[1] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Bottom);
points1[2] = new PointF(anchorPointAbs.X, anchorPointAbs.Y);
perspectivePaths[0].AddLines(points1);
if(anchorPoint.X < rectanglePosition.X)
{
PointF[] points2 = new PointF[3];
points2[0] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
points2[1] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Y);
points2[2] = new PointF(anchorPointAbs.X, anchorPointAbs.Y);
perspectivePaths[1].AddLines(points2);
}
else if(anchorPoint.X > rectanglePosition.Right)
{
PointF[] points2 = new PointF[3];
points2[0] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Bottom);
points2[1] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y);
points2[2] = new PointF(anchorPointAbs.X, anchorPointAbs.Y);
perspectivePaths[1].AddLines(points2);
}
}
else
{
if(anchorPoint.X < rectanglePosition.X)
{
PointF[] points2 = new PointF[3];
points2[0] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Bottom);
points2[1] = new PointF(rectanglePositionAbs.X, rectanglePositionAbs.Y);
points2[2] = new PointF(anchorPointAbs.X, anchorPointAbs.Y);
perspectivePaths[1].AddLines(points2);
}
else if(anchorPoint.X > rectanglePosition.Right)
{
PointF[] points2 = new PointF[3];
points2[0] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Bottom);
points2[1] = new PointF(rectanglePositionAbs.Right, rectanglePositionAbs.Y);
points2[2] = new PointF(anchorPointAbs.X, anchorPointAbs.Y);
perspectivePaths[1].AddLines(points2);
}
}
// Draw paths if non-empty
int index = 0;
foreach(GraphicsPath path in perspectivePaths)
{
if(path.PointCount > 0)
{
path.CloseAllFigures();
graphics.DrawPathAbs(
path,
perspectivePathColors[index],
this.BackHatchStyle,
String.Empty,
ChartImageWrapMode.Scaled,
Color.Empty,
ChartImageAlignmentStyle.Center,
this.BackGradientStyle,
this.BackSecondaryColor,
this.LineColor,
this.LineWidth,
this.LineDashStyle,
PenAlignment.Center);
// Add area to hot region path
hotRegion.SetMarkers();
hotRegion.AddPath( path, false );
}
++index;
}
}
}
}
}
return hotRegion;
}
/// <summary>
/// Draws SimpleLine or BorderLine style callout.
/// </summary>
/// <param name="graphics">Chart graphics.</param>
/// <param name="rectanglePosition">Position of annotation objet.</param>
/// <param name="anchorPoint">Anchor location.</param>
/// <param name="drawRectangle">If true draws BorderLine style, otherwise SimpleLine.</param>
/// <returns>Hot region of the cloud.</returns>
private GraphicsPath DrawRectangleLineCallout(
ChartGraphics graphics,
RectangleF rectanglePosition,
PointF anchorPoint,
bool drawRectangle)
{
// Rectangle mode
if(drawRectangle)
{
// Draw rectangle
graphics.FillRectangleRel(
rectanglePosition,
this.BackColor,
this.BackHatchStyle,
String.Empty,
ChartImageWrapMode.Scaled,
Color.Empty,
ChartImageAlignmentStyle.Center,
this.BackGradientStyle,
this.BackSecondaryColor,
this.LineColor,
this.LineWidth,
this.LineDashStyle,
this.ShadowColor,
this.ShadowOffset,
PenAlignment.Center);
// Draw text
DrawText(graphics, rectanglePosition, false, false);
}
else
{
// Draw text
rectanglePosition = DrawText(graphics, rectanglePosition, false, true);
SizeF pixelSize = graphics.GetRelativeSize(new SizeF(2f, 2f));
rectanglePosition.Inflate(pixelSize);
}
// Create hot region path
GraphicsPath hotRegion = new GraphicsPath();
hotRegion.AddRectangle( graphics.GetAbsoluteRectangle(rectanglePosition) );
// Define position of text underlying line
PointF textLinePoint1 = new PointF(rectanglePosition.X, rectanglePosition.Bottom);
PointF textLinePoint2 = new PointF(rectanglePosition.Right, rectanglePosition.Bottom);
// Draw line to the anchor point
if(!float.IsNaN(anchorPoint.X) && !float.IsNaN(anchorPoint.Y))
{
// Check if point is inside annotation position
if(!rectanglePosition.Contains(anchorPoint.X, anchorPoint.Y))
{
PointF lineSecondPoint = PointF.Empty;
if(anchorPoint.X < rectanglePosition.X)
{
lineSecondPoint.X = rectanglePosition.X;
}
else if(anchorPoint.X > rectanglePosition.Right)
{
lineSecondPoint.X = rectanglePosition.Right;
}
else
{
lineSecondPoint.X = rectanglePosition.X + rectanglePosition.Width / 2f;
}
if(anchorPoint.Y < rectanglePosition.Y)
{
lineSecondPoint.Y = rectanglePosition.Y;
}
else if(anchorPoint.Y > rectanglePosition.Bottom)
{
lineSecondPoint.Y = rectanglePosition.Bottom;
}
else
{
lineSecondPoint.Y = rectanglePosition.Y + rectanglePosition.Height / 2f;
}
// Set line caps
bool capChanged = false;
LineCap oldStartCap = LineCap.Flat;
if(this.CalloutAnchorCap != LineAnchorCapStyle.None)
{
// Save old pen
capChanged = true;
oldStartCap = graphics.Pen.StartCap;
// Apply anchor cap settings
if(this.CalloutAnchorCap == LineAnchorCapStyle.Arrow)
{
// Adjust arrow size for small line width
if(this.LineWidth < 4)
{
int adjustment = 3 - this.LineWidth;
graphics.Pen.StartCap = LineCap.Custom;
graphics.Pen.CustomStartCap = new AdjustableArrowCap(
this.LineWidth + adjustment,
this.LineWidth + adjustment,
true);
}
else
{
graphics.Pen.StartCap = LineCap.ArrowAnchor;
}
}
else if(this.CalloutAnchorCap == LineAnchorCapStyle.Diamond)
{
graphics.Pen.StartCap = LineCap.DiamondAnchor;
}
else if(this.CalloutAnchorCap == LineAnchorCapStyle.Round)
{
graphics.Pen.StartCap = LineCap.RoundAnchor;
}
else if(this.CalloutAnchorCap == LineAnchorCapStyle.Square)
{
graphics.Pen.StartCap = LineCap.SquareAnchor;
}
}
// Draw callout line
graphics.DrawLineAbs(
this.LineColor,
this.LineWidth,
this.LineDashStyle,
graphics.GetAbsolutePoint(anchorPoint),
graphics.GetAbsolutePoint(lineSecondPoint),
this.ShadowColor,
this.ShadowOffset);
// Create hot region path
using( GraphicsPath linePath = new GraphicsPath() )
{
linePath.AddLine(
graphics.GetAbsolutePoint(anchorPoint),
graphics.GetAbsolutePoint(lineSecondPoint) );
linePath.Widen(new Pen(Color.Black, this.LineWidth + 2));
hotRegion.SetMarkers();
hotRegion.AddPath( linePath, false );
}
// Restore line caps
if(capChanged)
{
graphics.Pen.StartCap = oldStartCap;
}
// Adjust text underlying line position
if(anchorPoint.Y < rectanglePosition.Y)
{
textLinePoint1.Y = rectanglePosition.Y;
textLinePoint2.Y = rectanglePosition.Y;
}
else if(anchorPoint.Y > rectanglePosition.Y &&
anchorPoint.Y < rectanglePosition.Bottom)
{
textLinePoint1.Y = rectanglePosition.Y;
textLinePoint2.Y = rectanglePosition.Bottom;
if(anchorPoint.X < rectanglePosition.X)
{
textLinePoint1.X = rectanglePosition.X;
textLinePoint2.X = rectanglePosition.X;
}
else
{
textLinePoint1.X = rectanglePosition.Right;
textLinePoint2.X = rectanglePosition.Right;
}
}
}
// Draw text underlying line
if(!drawRectangle)
{
graphics.DrawLineAbs(
this.LineColor,
this.LineWidth,
this.LineDashStyle,
graphics.GetAbsolutePoint(textLinePoint1),
graphics.GetAbsolutePoint(textLinePoint2),
this.ShadowColor,
this.ShadowOffset);
// Create hot region path
using( GraphicsPath linePath = new GraphicsPath() )
{
linePath.AddLine(
graphics.GetAbsolutePoint(textLinePoint1),
graphics.GetAbsolutePoint(textLinePoint2) );
linePath.Widen(new Pen(Color.Black, this.LineWidth + 2));
hotRegion.SetMarkers();
hotRegion.AddPath( linePath, false );
}
}
}
return hotRegion;
}
#endregion // Painting
#region Anchor Methods
/// <summary>
/// Checks if annotation draw anything in the anchor position (except selection handle)
/// </summary>
/// <returns>True if annotation "connects" itself and anchor point visually.</returns>
override internal bool IsAnchorDrawn()
{
return true;
}
#endregion // Anchor Methods
#region Helper methods
/// <summary>
/// Gets cloud callout outline graphics path.
/// </summary>
/// <param name="position">Absolute position of the callout cloud.</param>
/// <returns>Cloud outline path.</returns>
private static GraphicsPath GetCloudOutlinePath(RectangleF position)
{
if(_cloudOutlinePath == null)
{
GetCloudPath(position);
}
// Translate and sacle original path to fit specified position
GraphicsPath resultPath = (GraphicsPath)_cloudOutlinePath.Clone();
Matrix matrix = new Matrix();
matrix.Translate(-_cloudBounds.X, -_cloudBounds.Y);
resultPath.Transform(matrix);
matrix = new Matrix();
matrix.Translate(position.X, position.Y);
matrix.Scale(position.Width / _cloudBounds.Width, position.Height / _cloudBounds.Height);
resultPath.Transform(matrix);
return resultPath;
}
/// <summary>
/// Gets cloud callout graphics path.
/// </summary>
/// <param name="position">Absolute position of the callout cloud.</param>
/// <returns>Cloud path.</returns>
private static GraphicsPath GetCloudPath(RectangleF position)
{
// Check if cloud path was already created
if(_cloudPath == null)
{
// Create cloud path
_cloudPath = new GraphicsPath();
_cloudPath.AddBezier(1689.5f, 1998.6f, 1581.8f, 2009.4f, 1500f, 2098.1f, 1500f, 2204f);
_cloudPath.AddBezier(1500f, 2204f, 1499.9f, 2277.2f, 1539.8f, 2345.1f, 1604.4f, 2382.1f);
_cloudPath.AddBezier(1603.3f, 2379.7f, 1566.6f, 2417.8f, 1546.2f, 2468.1f, 1546.2f, 2520.1f);
_cloudPath.AddBezier(1546.2f, 2520.1f, 1546.2f, 2633.7f, 1641.1f, 2725.7f, 1758.1f, 2725.7f);
_cloudPath.AddBezier(1758.1f, 2725.7f, 1766.3f, 2725.6f, 1774.6f, 2725.2f, 1782.8f, 2724.2f);
_cloudPath.AddBezier(1781.7f, 2725.6f, 1848.5f, 2839.4f, 1972.8f, 2909.7f, 2107.3f, 2909.7f);
_cloudPath.AddBezier(2107.3f, 2909.7f, 2175.4f, 2909.7f, 2242.3f, 2891.6f, 2300.6f, 2857.4f);
_cloudPath.AddBezier(2300f, 2857.6f, 2360.9f, 2946.5f, 2463.3f, 2999.7f, 2572.9f, 2999.7f);
_cloudPath.AddBezier(2572.9f, 2999.7f, 2717.5f, 2999.7f, 2845.2f, 2907.4f, 2887.1f, 2772.5f);
_cloudPath.AddBezier(2887.4f, 2774.3f, 2932.1f, 2801.4f, 2983.6f, 2815.7f, 3036.3f, 2815.7f);
_cloudPath.AddBezier(3036.3f, 2815.7f, 3190.7f, 2815.7f, 3316.3f, 2694.8f, 3317.5f, 2544.8f);
_cloudPath.AddBezier(3317f, 2544.1f, 3479.2f, 2521.5f, 3599.7f, 2386.5f, 3599.7f, 2227.2f);
_cloudPath.AddBezier(3599.7f, 2227.2f, 3599.7f, 2156.7f, 3575.7f, 2088.1f, 3531.6f, 2032.2f);
_cloudPath.AddBezier(3530.9f, 2032f, 3544.7f, 2000.6f, 3551.9f, 1966.7f, 3551.9f, 1932.5f);
_cloudPath.AddBezier(3551.9f, 1932.5f, 3551.9f, 1818.6f, 3473.5f, 1718.8f, 3360.7f, 1688.8f);
_cloudPath.AddBezier(3361.6f, 1688.3f, 3341.4f, 1579.3f, 3243.5f, 1500f, 3129.3f, 1500f);
_cloudPath.AddBezier(3129.3f, 1500f, 3059.8f, 1499.9f, 2994f, 1529.6f, 2949.1f, 1580.9f);
_cloudPath.AddBezier(2949.5f, 1581.3f, 2909.4f, 1530f, 2847f, 1500f, 2780.8f, 1500f);
_cloudPath.AddBezier(2780.8f, 1500f, 2700.4f, 1499.9f, 2626.8f, 1544.2f, 2590.9f, 1614.2f);
_cloudPath.AddBezier(2591.7f, 1617.6f, 2543.2f, 1571.1f, 2477.9f, 1545.1f, 2409.8f, 1545.1f);
_cloudPath.AddBezier(2409.8f, 1545.1f, 2313.9f, 1545.1f, 2225.9f, 1596.6f, 2180.8f, 1679f);
_cloudPath.AddBezier(2180.1f, 1680.7f, 2129.7f, 1652f, 2072.4f, 1636.9f, 2014.1f, 1636.9f);
_cloudPath.AddBezier(2014.1f, 1636.9f, 1832.8f, 1636.9f, 1685.9f, 1779.8f, 1685.9f, 1956f);
_cloudPath.AddBezier(1685.9f, 1956f, 1685.8f, 1970.4f, 1686.9f, 1984.8f, 1688.8f, 1999f);
_cloudPath.CloseAllFigures();
// Create cloud outline path
_cloudOutlinePath = new GraphicsPath();
_cloudOutlinePath.AddBezier(1604.4f, 2382.1f, 1636.8f, 2400.6f, 1673.6f, 2410.3f, 1711.2f, 2410.3f);
_cloudOutlinePath.AddBezier(1711.2f, 2410.3f, 1716.6f, 2410.3f, 1722.2f, 2410.2f, 1727.6f, 2409.8f);
_cloudOutlinePath.StartFigure();
_cloudOutlinePath.AddBezier(1782.8f, 2724.2f, 1801.3f, 2722.2f, 1819.4f, 2717.7f, 1836.7f, 2711f);
_cloudOutlinePath.StartFigure();
_cloudOutlinePath.AddBezier(2267.6f, 2797.2f, 2276.1f, 2818.4f, 2287f, 2838.7f, 2300f, 2857.6f);
_cloudOutlinePath.StartFigure();
_cloudOutlinePath.AddBezier(2887.1f, 2772.5f, 2893.8f, 2750.9f, 2898.1f, 2728.7f, 2900f, 2706.3f);
// NOTE: This cloud segment overlaps text too much. Removed for now!
//cloudOutlinePath.StartFigure();
//cloudOutlinePath.AddBezier(3317.5f, 2544.8f, 3317.5f, 2544f, 3317.6f, 2543.3f, 3317.6f, 2542.6f);
//cloudOutlinePath.AddBezier(3317.6f, 2542.6f, 3317.6f, 2438.1f, 3256.1f, 2342.8f, 3159.5f, 2297f);
_cloudOutlinePath.StartFigure();
_cloudOutlinePath.AddBezier(3460.5f, 2124.9f, 3491f, 2099.7f, 3515f, 2067.8f, 3530.9f, 2032f);
_cloudOutlinePath.StartFigure();
_cloudOutlinePath.AddBezier(3365.3f, 1732.2f, 3365.3f, 1731.1f, 3365.4f, 1730.1f, 3365.4f, 1729f);
_cloudOutlinePath.AddBezier(3365.4f, 1729f, 3365.4f, 1715.3f, 3364.1f, 1701.7f, 3361.6f, 1688.3f);
_cloudOutlinePath.StartFigure();
_cloudOutlinePath.AddBezier(2949.1f, 1580.9f, 2934.4f, 1597.8f, 2922.3f, 1616.6f, 2913.1f, 1636.9f);
_cloudOutlinePath.CloseFigure();
_cloudOutlinePath.StartFigure();
_cloudOutlinePath.AddBezier(2590.9f, 1614.2f, 2583.1f, 1629.6f, 2577.2f, 1645.8f, 2573.4f, 1662.5f);
_cloudOutlinePath.StartFigure();
_cloudOutlinePath.AddBezier(2243.3f, 1727.5f, 2224.2f, 1709.4f, 2203f, 1693.8f, 2180.1f, 1680.7f);
_cloudOutlinePath.StartFigure();
_cloudOutlinePath.AddBezier(1688.8f, 1999f, 1691.1f, 2015.7f, 1694.8f, 2032.2f, 1699.9f, 2048.3f);
_cloudOutlinePath.CloseAllFigures();
// Get cloud path bounds
_cloudBounds = _cloudPath.GetBounds();
}
// Translate and sacle original path to fit specified position
GraphicsPath resultPath = (GraphicsPath)_cloudPath.Clone();
Matrix matrix = new Matrix();
matrix.Translate(-_cloudBounds.X, -_cloudBounds.Y);
resultPath.Transform(matrix);
matrix = new Matrix();
matrix.Translate(position.X, position.Y);
matrix.Scale(position.Width / _cloudBounds.Width, position.Height / _cloudBounds.Height);
resultPath.Transform(matrix);
return resultPath;
}
/// <summary>
/// Gets intersection point coordinates between point line and and horizontal
/// line specified by Y coordinate.
/// </summary>
/// <param name="firstPoint">First data point.</param>
/// <param name="secondPoint">Second data point.</param>
/// <param name="pointY">Y coordinate.</param>
/// <returns>Intersection point coordinates.</returns>
internal static PointF GetIntersectionY(PointF firstPoint, PointF secondPoint, float pointY)
{
PointF intersectionPoint = new PointF();
intersectionPoint.Y = pointY;
intersectionPoint.X = (pointY - firstPoint.Y) *
(secondPoint.X - firstPoint.X) /
(secondPoint.Y - firstPoint.Y) +
firstPoint.X;
return intersectionPoint;
}
/// <summary>
/// Gets intersection point coordinates between point line and and vertical
/// line specified by X coordinate.
/// </summary>
/// <param name="firstPoint">First data point.</param>
/// <param name="secondPoint">Second data point.</param>
/// <param name="pointX">X coordinate.</param>
/// <returns>Intersection point coordinates.</returns>
internal static PointF GetIntersectionX(PointF firstPoint, PointF secondPoint, float pointX)
{
PointF intersectionPoint = new PointF();
intersectionPoint.X = pointX;
intersectionPoint.Y = (pointX - firstPoint.X) *
(secondPoint.Y - firstPoint.Y) /
(secondPoint.X - firstPoint.X) +
firstPoint.Y;
return intersectionPoint;
}
/// <summary>
/// Adds a horizontal or vertical line into the path as multiple segments.
/// </summary>
/// <param name="path">Graphics path.</param>
/// <param name="x1">First point X coordinate.</param>
/// <param name="y1">First point Y coordinate.</param>
/// <param name="x2">Second point X coordinate.</param>
/// <param name="y2">Second point Y coordinate.</param>
/// <param name="segments">Number of segments to add.</param>
private void PathAddLineAsSegments(GraphicsPath path, float x1, float y1, float x2, float y2, int segments)
{
if(x1 == x2)
{
float distance = (y2 - y1) / segments;
for(int index = 0; index < segments; index++)
{
path.AddLine(x1, y1, x1, y1 + distance);
y1 += distance;
}
}
else if(y1 == y2)
{
float distance = (x2 - x1) / segments;
for(int index = 0; index < segments; index++)
{
path.AddLine(x1, y1, x1 + distance, y1);
x1 += distance;
}
}
else
{
throw (new InvalidOperationException(SR.ExceptionAnnotationPathAddLineAsSegmentsInvalid));
}
}
/// <summary>
/// Helper function which creates a rounded rectangle path.
/// Extra points are added on the sides to allow anchor connection.
/// </summary>
/// <param name="rect">Rectangle coordinates.</param>
/// <param name="cornerRadius">Corner radius.</param>
/// <returns>Graphics path object.</returns>
private GraphicsPath CreateRoundedRectPath(RectangleF rect, float cornerRadius)
{
// Create rounded rectangle path
GraphicsPath path = new GraphicsPath();
int segments = 10;
PathAddLineAsSegments(path, rect.X+cornerRadius, rect.Y, rect.Right-cornerRadius, rect.Y, segments);
path.AddArc(rect.Right-2f*cornerRadius, rect.Y, 2f*cornerRadius, 2f*cornerRadius, 270, 90);
PathAddLineAsSegments(path, rect.Right, rect.Y + cornerRadius, rect.Right, rect.Bottom - cornerRadius, segments);
path.AddArc(rect.Right-2f*cornerRadius, rect.Bottom-2f*cornerRadius, 2f*cornerRadius, 2f*cornerRadius, 0, 90);
PathAddLineAsSegments(path, rect.Right-cornerRadius, rect.Bottom, rect.X + cornerRadius, rect.Bottom, segments);
path.AddArc(rect.X, rect.Bottom-2f*cornerRadius, 2f*cornerRadius, 2f*cornerRadius, 90, 90);
PathAddLineAsSegments(path, rect.X, rect.Bottom-cornerRadius, rect.X, rect.Y+cornerRadius, segments);
path.AddArc(rect.X, rect.Y, 2f*cornerRadius, 2f*cornerRadius, 180, 90);
return path;
}
#endregion // Helper methods
#endregion
}
}