|
//-------------------------------------------------------------
// <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
}
}
|