|
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: SmartLabelStyle.cs
//
// Namespace: DataVisualization.Charting
//
// Classes: SmartLabelStyle, SmartLabelStyle
//
// Purpose: Smart Labels are used to avoid data point's labels
// overlapping. SmartLabelStyle class is exposed from
// the Series and Annotation classes and allows enabling
// and adjusting of SmartLabelStyle algorithm. SmartLabelStyle class
// exposes a set of helper utility methods and store
// information about labels in a chart area.
//
// Reviewed: AG - Microsoft 14, 2007
//
//===================================================================
#region Used namespaces
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.Drawing2D;
#if Microsoft_CONTROL
using System.Windows.Forms.DataVisualization.Charting.Data;
using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
using System.Windows.Forms.DataVisualization.Charting.Utilities;
using System.Windows.Forms.DataVisualization.Charting.Borders3D;
using System.Windows.Forms.DataVisualization.Charting;
using System.Globalization;
using System.ComponentModel.Design.Serialization;
using System.Reflection;
using System.Windows.Forms.Design;
#else
using System.Web;
using System.Web.UI;
using System.Globalization;
using System.Web.UI.DataVisualization.Charting;
using System.Web.UI.DataVisualization.Charting.Data;
using System.Web.UI.DataVisualization.Charting.ChartTypes;
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>
/// Line anchor cap style.
/// </summary>
[
SRDescription("DescriptionAttributeLineAnchorCapStyle_LineAnchorCapStyle")
]
public enum LineAnchorCapStyle
{
/// <summary>
/// No line anchor cap.
/// </summary>
None,
/// <summary>
/// Arrow line anchor cap.
/// </summary>
Arrow,
/// <summary>
/// Diamond line anchor cap.
/// </summary>
Diamond,
/// <summary>
/// Square line anchor cap.
/// </summary>
Square,
/// <summary>
/// Round line anchor cap.
/// </summary>
Round
}
/// <summary>
/// Data point label callout style.
/// </summary>
[
SRDescription("DescriptionAttributeLabelCalloutStyle_LabelCalloutStyle")
]
public enum LabelCalloutStyle
{
/// <summary>
/// Label connected with the marker using just a line.
/// </summary>
None,
/// <summary>
/// Label is undelined and connected with the marker using a line.
/// </summary>
Underlined,
/// <summary>
/// Box is drawn around the label and it's connected with the marker using a line.
/// </summary>
Box
}
/// <summary>
/// Data point label outside of the plotting area style.
/// </summary>
[
SRDescription("DescriptionAttributeLabelOutsidePlotAreaStyle_LabelOutsidePlotAreaStyle")
]
public enum LabelOutsidePlotAreaStyle
{
/// <summary>
/// Labels can be positioned outside of the plotting area.
/// </summary>
Yes,
/// <summary>
/// Labels can not be positioned outside of the plotting area.
/// </summary>
No,
/// <summary>
/// Labels can be partially outside of the plotting area.
/// </summary>
Partial
}
#endregion
/// <summary>
/// SmartLabelStyle class is used to enable and configure the
/// SmartLabelStyle algorithm for data point labels and annotations.
/// In most of the cases it is enough just to enable the algorithm,
/// but this class also contains properties which allow controlling
/// how the labels are moved around to avoid collisions. Visual
/// appearance of callouts can also be set through this class.
/// </summary>
[
DefaultProperty("Enabled"),
SRDescription("DescriptionAttributeSmartLabelsStyle_SmartLabelsStyle"),
TypeConverter(typeof(NoNameExpandableObjectConverter))
]
#if ASPPERM_35
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[AspNetHostingPermission(System.Security.Permissions.SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
#endif
public class SmartLabelStyle
{
#region Fields
// Reference to the series this style belongs to
internal object chartElement = null;
// Indicates if SmartLabelStyle algorithm is enabled.
private bool _enabled = true;
// Indicates that marker overlapping by label is allowed.
private bool _isMarkerOverlappingAllowed = false;
// Indicates that overlapped labels that can't be repositioned will be hidden.
private bool _isOverlappedHidden = true;
// Possible moving directions for the overlapped SmartLabelStyle.
private LabelAlignmentStyles _movingDirection = LabelAlignmentStyles.Top | LabelAlignmentStyles.Bottom | LabelAlignmentStyles.Right | LabelAlignmentStyles.Left | LabelAlignmentStyles.TopLeft | LabelAlignmentStyles.TopRight | LabelAlignmentStyles.BottomLeft | LabelAlignmentStyles.BottomRight;
// Minimum distance the overlapped SmartLabelStyle can be moved from the marker. Distance is measured in pixels.
private double _minMovingDistance = 0.0;
// Maximum distance the overlapped SmartLabelStyle can be moved from the marker. Distance is measured in pixels.
private double _maxMovingDistance = 30.0;
// Defines if SmartLabelStyle are allowed to be drawn outside of the plotting area.
private LabelOutsidePlotAreaStyle _allowOutsidePlotArea = LabelOutsidePlotAreaStyle.Partial;
// Callout style of the repositioned SmartLabelStyle.
private LabelCalloutStyle _calloutStyle = LabelCalloutStyle.Underlined;
// Label callout line color.
private Color _calloutLineColor = Color.Black;
// Label callout line style.
private ChartDashStyle _calloutLineDashStyle = ChartDashStyle.Solid;
// Label callout back color. Applies to the Box style only!
private Color _calloutBackColor = Color.Transparent;
// Label callout line width.
private int _calloutLineWidth = 1;
// Label callout line anchor cap.
private LineAnchorCapStyle _calloutLineAnchorCapStyle = LineAnchorCapStyle.Arrow;
#endregion
#region Constructors and initialization
/// <summary>
/// Default public constructor.
/// </summary>
public SmartLabelStyle()
{
this.chartElement = null;
}
/// <summary>
/// Constructor.
/// </summary>
/// <param name="chartElement">Chart element this style belongs to.</param>
internal SmartLabelStyle(Object chartElement)
{
this.chartElement = chartElement;
}
#endregion
#region Properties
/// <summary>
/// SmartLabelStyle algorithm enabled flag.
/// </summary>
[
SRCategory("CategoryAttributeMisc"),
Bindable(true),
DefaultValue(true),
SRDescription("DescriptionAttributeEnabled13"),
ParenthesizePropertyNameAttribute(true),
]
virtual public bool Enabled
{
get
{
return _enabled;
}
set
{
_enabled = value;
Invalidate();
}
}
/// <summary>
/// Indicates that marker overlapping by label is allowed.
/// </summary>
[
SRCategory("CategoryAttributeMisc"),
Bindable(true),
DefaultValue(false),
SRDescription("DescriptionAttributeMarkerOverlapping"),
]
virtual public bool IsMarkerOverlappingAllowed
{
get
{
return _isMarkerOverlappingAllowed;
}
set
{
_isMarkerOverlappingAllowed = value;
Invalidate();
}
}
/// <summary>
/// Indicates that overlapped labels that can't be repositioned will be hidden.
/// </summary>
[
SRCategory("CategoryAttributeMisc"),
Bindable(true),
DefaultValue(true),
SRDescription("DescriptionAttributeHideOverlapped"),
]
virtual public bool IsOverlappedHidden
{
get
{
return _isOverlappedHidden;
}
set
{
_isOverlappedHidden = value;
Invalidate();
}
}
/// <summary>
/// Possible moving directions for the overlapped SmartLabelStyle.
/// </summary>
[
SRCategory("CategoryAttributeMisc"),
Bindable(true),
DefaultValue(typeof(LabelAlignmentStyles), "Top, Bottom, Right, Left, TopLeft, TopRight, BottomLeft, BottomRight"),
SRDescription("DescriptionAttributeMovingDirection"),
Editor(Editors.FlagsEnumUITypeEditor.Editor, Editors.FlagsEnumUITypeEditor.Base),
]
virtual public LabelAlignmentStyles MovingDirection
{
get
{
return _movingDirection;
}
set
{
if(value == 0)
{
throw (new InvalidOperationException(SR.ExceptionSmartLabelsDirectionUndefined));
}
_movingDirection = value;
Invalidate();
}
}
/// <summary>
/// Minimum distance the overlapped SmartLabelStyle can be moved from the marker. Distance is measured in pixels.
/// </summary>
[
SRCategory("CategoryAttributeMisc"),
Bindable(true),
DefaultValue(0.0),
SRDescription("DescriptionAttributeMinMovingDistance"),
]
virtual public double MinMovingDistance
{
get
{
return _minMovingDistance;
}
set
{
if(value < 0)
{
throw (new InvalidOperationException(SR.ExceptionSmartLabelsMinMovingDistanceIsNegative));
}
_minMovingDistance = value;
Invalidate();
}
}
/// <summary>
/// Maximum distance the overlapped SmartLabelStyle can be moved from the marker. Distance is measured in pixels.
/// </summary>
[
SRCategory("CategoryAttributeMisc"),
Bindable(true),
DefaultValue(30.0),
SRDescription("DescriptionAttributeMaxMovingDistance"),
]
virtual public double MaxMovingDistance
{
get
{
return _maxMovingDistance;
}
set
{
if(value < 0 )
{
throw (new InvalidOperationException(SR.ExceptionSmartLabelsMaxMovingDistanceIsNegative));
}
_maxMovingDistance = value;
Invalidate();
}
}
/// <summary>
/// Defines if SmartLabelStyle are allowed to be drawn outside of the plotting area.
/// </summary>
[
SRCategory("CategoryAttributeMisc"),
Bindable(true),
DefaultValue(LabelOutsidePlotAreaStyle.Partial),
SRDescription("DescriptionAttributeAllowOutsidePlotArea"),
]
virtual public LabelOutsidePlotAreaStyle AllowOutsidePlotArea
{
get
{
return _allowOutsidePlotArea;
}
set
{
_allowOutsidePlotArea = value;
Invalidate();
}
}
/// <summary>
/// Callout style of the repositioned SmartLabelStyle.
/// </summary>
[
SRCategory("CategoryAttributeMisc"),
Bindable(true),
DefaultValue(LabelCalloutStyle.Underlined),
SRDescription("DescriptionAttributeCalloutStyle3"),
]
virtual public LabelCalloutStyle CalloutStyle
{
get
{
return _calloutStyle;
}
set
{
_calloutStyle = value;
Invalidate();
}
}
/// <summary>
/// Label callout line color.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Color), "Black"),
SRDescription("DescriptionAttributeCalloutLineColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
]
virtual public Color CalloutLineColor
{
get
{
return _calloutLineColor;
}
set
{
_calloutLineColor = value;
Invalidate();
}
}
/// <summary>
/// Label callout line style.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(ChartDashStyle.Solid),
SRDescription("DescriptionAttributeLineDashStyle"),
#if !Microsoft_CONTROL
PersistenceMode(PersistenceMode.Attribute)
#endif
]
virtual public ChartDashStyle CalloutLineDashStyle
{
get
{
return _calloutLineDashStyle;
}
set
{
_calloutLineDashStyle = value;
Invalidate();
}
}
/// <summary>
/// Label callout back color. Applies to the Box style only!
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(typeof(Color), "Transparent"),
SRDescription("DescriptionAttributeCalloutBackColor"),
TypeConverter(typeof(ColorConverter)),
Editor(Editors.ChartColorEditor.Editor, Editors.ChartColorEditor.Base),
]
virtual public Color CalloutBackColor
{
get
{
return _calloutBackColor;
}
set
{
_calloutBackColor = value;
Invalidate();
}
}
/// <summary>
/// Label callout line width.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(1),
SRDescription("DescriptionAttributeLineWidth"),
]
virtual public int CalloutLineWidth
{
get
{
return _calloutLineWidth;
}
set
{
_calloutLineWidth = value;
Invalidate();
}
}
/// <summary>
/// Label callout line anchor cap.
/// </summary>
[
SRCategory("CategoryAttributeAppearance"),
Bindable(true),
DefaultValue(LineAnchorCapStyle.Arrow),
SRDescription(SR.Keys.DescriptionAttributeCalloutLineAnchorCap),
]
virtual public LineAnchorCapStyle CalloutLineAnchorCapStyle
{
get
{
return _calloutLineAnchorCapStyle;
}
set
{
_calloutLineAnchorCapStyle = value;
Invalidate();
}
}
#endregion
#region Methods
/// <summary>
/// Invalidates ----osiated chart element.
/// </summary>
private void Invalidate()
{
if(chartElement != null)
{
if(chartElement is Series)
{
((Series)chartElement).Invalidate(false, false);
}
else if(chartElement is Annotation)
{
((Annotation)chartElement).Invalidate();
}
}
}
#endregion
}
/// <summary>
/// SmartLabelStyle class implements the SmartLabelStyle algorithm for the
/// data series points. It keeps track of all labels drawn and
/// detects their collisions. When labels collision is detected
/// the algorithm tries to resolve it by repositioning the labels.
/// If label can not be repositioned it maybe hidden depending on
/// the current settings.
/// </summary>
[
SRDescription("DescriptionAttributeSmartLabels_SmartLabels"),
]
internal class SmartLabel
{
#region Fields
// List of all SmartLabelStyle positions in the area
internal ArrayList smartLabelsPositions = null;
// Indicates that not a single collision is allowed
internal bool checkAllCollisions = false;
// Number of positions in array for the markers
internal int markersCount = 0;
#endregion
#region Constructors and initialization
/// <summary>
/// Default public constructor.
/// </summary>
public SmartLabel()
{
}
#endregion
#region Methods
/// <summary>
/// Reset SmartLabelStyle object.
/// </summary>
internal void Reset()
{
// Re-initialize list of labels position
smartLabelsPositions = new ArrayList();
}
/// <summary>
/// Process single SmartLabelStyle by adjusting it's position in case of collision.
/// </summary>
/// <param name="common">Reference to common elements.</param>
/// <param name="graph">Reference to chart graphics object.</param>
/// <param name="area">Chart area.</param>
/// <param name="smartLabelStyle">Smart labels style.</param>
/// <param name="labelPosition">Original label position.</param>
/// <param name="labelSize">Label text size.</param>
/// <param name="format">Label string format.</param>
/// <param name="markerPosition">Marker position.</param>
/// <param name="markerSize">Marker size.</param>
/// <param name="labelAlignment">Original label alignment.</param>
/// <returns>Adjusted position of the label.</returns>
internal PointF AdjustSmartLabelPosition(
CommonElements common,
ChartGraphics graph,
ChartArea area,
SmartLabelStyle smartLabelStyle,
PointF labelPosition,
SizeF labelSize,
StringFormat format,
PointF markerPosition,
SizeF markerSize,
LabelAlignmentStyles labelAlignment)
{
return AdjustSmartLabelPosition(
common,
graph,
area,
smartLabelStyle,
labelPosition,
labelSize,
format,
markerPosition,
markerSize,
labelAlignment,
false);
}
/// <summary>
/// Process single SmartLabelStyle by adjusting it's position in case of collision.
/// </summary>
/// <param name="common">Reference to common elements.</param>
/// <param name="graph">Reference to chart graphics object.</param>
/// <param name="area">Chart area.</param>
/// <param name="smartLabelStyle">Smart labels style.</param>
/// <param name="labelPosition">Original label position.</param>
/// <param name="labelSize">Label text size.</param>
/// <param name="format">Label string format.</param>
/// <param name="markerPosition">Marker position.</param>
/// <param name="markerSize">Marker size.</param>
/// <param name="labelAlignment">Original label alignment.</param>
/// <param name="checkCalloutLineOverlapping">Indicates that labels overlapping by callout line must be checked.</param>
/// <returns>Adjusted position of the label.</returns>
internal PointF AdjustSmartLabelPosition(
CommonElements common,
ChartGraphics graph,
ChartArea area,
SmartLabelStyle smartLabelStyle,
PointF labelPosition,
SizeF labelSize,
StringFormat format,
PointF markerPosition,
SizeF markerSize,
LabelAlignmentStyles labelAlignment,
bool checkCalloutLineOverlapping)
{
// Check if SmartLabelStyle are enabled
if(smartLabelStyle.Enabled)
{
bool labelMovedAway = false;
// Add series markers positions to avoid their overlapping
bool rememberMarkersCount = (this.smartLabelsPositions.Count == 0);
AddMarkersPosition(common, area);
if(rememberMarkersCount)
{
this.markersCount = this.smartLabelsPositions.Count;
}
// Check label collision
if(IsSmartLabelCollide(
common,
graph,
area,
smartLabelStyle,
labelPosition,
labelSize,
markerPosition,
format,
labelAlignment,
checkCalloutLineOverlapping))
{
// Try to find a new position for the SmartLabelStyle
labelMovedAway = FindNewPosition(
common,
graph,
area,
smartLabelStyle,
ref labelPosition,
labelSize,
format,
markerPosition,
ref markerSize,
ref labelAlignment,
checkCalloutLineOverlapping);
// Draw label callout if label was moved away or
// it's displayed in the corners of the marker
if(labelMovedAway ||
(labelAlignment == LabelAlignmentStyles.BottomLeft ||
labelAlignment == LabelAlignmentStyles.BottomRight ||
labelAlignment == LabelAlignmentStyles.TopLeft ||
labelAlignment == LabelAlignmentStyles.TopRight))
{
if(!labelPosition.IsEmpty)
{
DrawCallout(
common,
graph,
area,
smartLabelStyle,
labelPosition,
labelSize,
format,
markerPosition,
markerSize,
labelAlignment);
}
}
}
// Add label position into the list
AddSmartLabelPosition(graph, labelPosition, labelSize, format);
}
// Return label position
return labelPosition;
}
/// <summary>
/// Process single SmartLabelStyle by adjusting it's position in case of collision.
/// </summary>
/// <param name="common">Reference to common elements.</param>
/// <param name="graph">Reference to chart graphics object.</param>
/// <param name="area">Chart area.</param>
/// <param name="smartLabelStyle">Smart labels style.</param>
/// <param name="labelPosition">Original label position.</param>
/// <param name="labelSize">Label text size.</param>
/// <param name="format">Label string format.</param>
/// <param name="markerPosition">Marker position.</param>
/// <param name="markerSize">Marker size.</param>
/// <param name="labelAlignment">Label alignment.</param>
/// <param name="checkCalloutLineOverlapping">Indicates that labels overlapping by callout line must be checked.</param>
/// <returns>True if label was moved away from the marker.</returns>
private bool FindNewPosition(
CommonElements common,
ChartGraphics graph,
ChartArea area,
SmartLabelStyle smartLabelStyle,
ref PointF labelPosition,
SizeF labelSize,
StringFormat format,
PointF markerPosition,
ref SizeF markerSize,
ref LabelAlignmentStyles labelAlignment,
bool checkCalloutLineOverlapping)
{
SizeF newMarkerSize = SizeF.Empty;
PointF newLabelPosition = PointF.Empty;
int positionIndex = 0;
float labelMovement = 0f;
bool labelMovedAway = false;
LabelAlignmentStyles[] positions = new LabelAlignmentStyles[] {
LabelAlignmentStyles.Top,
LabelAlignmentStyles.Bottom,
LabelAlignmentStyles.Left,
LabelAlignmentStyles.Right,
LabelAlignmentStyles.TopLeft,
LabelAlignmentStyles.TopRight,
LabelAlignmentStyles.BottomLeft,
LabelAlignmentStyles.BottomRight,
LabelAlignmentStyles.Center
};
// Get relative size of single pixel
SizeF pixelSize = graph.GetRelativeSize(new SizeF(1f, 1f));
// Try to find a new position for the label
bool positionFound = false;
float movingStep = 2f;
float minMove = (float)Math.Min(smartLabelStyle.MinMovingDistance, smartLabelStyle.MaxMovingDistance);
float maxMove = (float)Math.Max(smartLabelStyle.MinMovingDistance, smartLabelStyle.MaxMovingDistance);
for(labelMovement = minMove; !positionFound && labelMovement <= maxMove; labelMovement += movingStep)
{
// Move label by increasing marker size by 4 pixels
newMarkerSize = new SizeF(
markerSize.Width + labelMovement*(pixelSize.Width * 2f),
markerSize.Height + labelMovement*(pixelSize.Height * 2f));
// Loop through different alignment types
for(positionIndex = 0; positionIndex < positions.Length; positionIndex++)
{
// Center label alignment should only be tried once!
if(positions[positionIndex] == LabelAlignmentStyles.Center && labelMovement != minMove)
{
continue;
}
// Check if this alignment is valid
if((smartLabelStyle.MovingDirection & positions[positionIndex]) == positions[positionIndex])
{
// Calculate new position of the label
newLabelPosition = CalculatePosition(
positions[positionIndex],
markerPosition,
newMarkerSize,
labelSize,
ref format);
// Check new position collision
if(!IsSmartLabelCollide(
common,
null,
area,
smartLabelStyle,
newLabelPosition,
labelSize,
markerPosition,
format,
positions[positionIndex],
checkCalloutLineOverlapping))
{
positionFound = true;
labelMovedAway = (labelMovement == 0f) ? false : true;
break;
}
}
}
}
// Set new data if new label position was found
if(positionFound)
{
markerSize = newMarkerSize;
labelPosition = newLabelPosition;
labelAlignment = positions[positionIndex];
}
// DEBUG code
#if DEBUG
if(common.Chart.ShowDebugMarkings)
{
RectangleF lp = GetLabelPosition(graph, labelPosition, labelSize, format, false);
if(positionFound)
{
graph.Graphics.DrawRectangle(Pens.Green, Rectangle.Round(graph.GetAbsoluteRectangle(lp)));
}
else
{
graph.Graphics.DrawRectangle(new Pen(Color.Magenta, 3), Rectangle.Round(graph.GetAbsoluteRectangle(lp)));
}
}
#endif
// Do not draw overlapped labels that can't be repositioned
if(!positionFound && smartLabelStyle.IsOverlappedHidden)
{
labelPosition = PointF.Empty;
}
return (labelMovedAway && positionFound) ? true : false;
}
/// <summary>
/// Process single SmartLabelStyle by adjusting it's position in case of collision.
/// </summary>
/// <param name="common">Reference to common elements.</param>
/// <param name="graph">Reference to chart graphics object.</param>
/// <param name="area">Chart area.</param>
/// <param name="smartLabelStyle">Smart labels style.</param>
/// <param name="labelPosition">Original label position.</param>
/// <param name="labelSize">Label text size.</param>
/// <param name="format">Label string format.</param>
/// <param name="markerPosition">Marker position.</param>
/// <param name="markerSize">Marker size.</param>
/// <param name="labelAlignment">Label alignment.</param>
/// <returns>Adjusted position of the label.</returns>
virtual internal void DrawCallout(
CommonElements common,
ChartGraphics graph,
ChartArea area,
SmartLabelStyle smartLabelStyle,
PointF labelPosition,
SizeF labelSize,
StringFormat format,
PointF markerPosition,
SizeF markerSize,
LabelAlignmentStyles labelAlignment)
{
// Calculate label position rectangle
RectangleF labelRectAbs = graph.GetAbsoluteRectangle(
GetLabelPosition(graph, labelPosition, labelSize, format, true));
// Create callout pen
Pen calloutPen = new Pen(smartLabelStyle.CalloutLineColor, smartLabelStyle.CalloutLineWidth);
calloutPen.DashStyle = graph.GetPenStyle(smartLabelStyle.CalloutLineDashStyle);
// Draw callout frame
if(smartLabelStyle.CalloutStyle == LabelCalloutStyle.Box)
{
// Fill callout box around the label
if(smartLabelStyle.CalloutBackColor != Color.Transparent)
{
using (Brush calloutBrush = new SolidBrush(smartLabelStyle.CalloutBackColor))
{
graph.FillRectangle(calloutBrush, labelRectAbs);
}
}
// Draw box border
graph.DrawRectangle(calloutPen, labelRectAbs.X, labelRectAbs.Y, labelRectAbs.Width, labelRectAbs.Height);
}
else if(smartLabelStyle.CalloutStyle == LabelCalloutStyle.Underlined)
{
if(labelAlignment == LabelAlignmentStyles.Right)
{
// Draw line to the left of label's text
graph.DrawLine(calloutPen, labelRectAbs.X, labelRectAbs.Top, labelRectAbs.X, labelRectAbs.Bottom);
}
else if(labelAlignment == LabelAlignmentStyles.Left)
{
// Draw line to the right of label's text
graph.DrawLine(calloutPen, labelRectAbs.Right, labelRectAbs.Top, labelRectAbs.Right, labelRectAbs.Bottom);
}
else if(labelAlignment == LabelAlignmentStyles.Bottom)
{
// Draw line on top of the label's text
graph.DrawLine(calloutPen, labelRectAbs.X, labelRectAbs.Top, labelRectAbs.Right, labelRectAbs.Top);
}
else
{
// Draw line under the label's text
graph.DrawLine(calloutPen, labelRectAbs.X, labelRectAbs.Bottom, labelRectAbs.Right, labelRectAbs.Bottom);
}
}
// Calculate connector line point on the label
PointF connectorPosition = graph.GetAbsolutePoint(labelPosition);
if(labelAlignment == LabelAlignmentStyles.Top)
{
connectorPosition.Y = labelRectAbs.Bottom;
}
else if(labelAlignment == LabelAlignmentStyles.Bottom)
{
connectorPosition.Y = labelRectAbs.Top;
}
if(smartLabelStyle.CalloutStyle == LabelCalloutStyle.Underlined)
{
if(labelAlignment == LabelAlignmentStyles.TopLeft ||
labelAlignment == LabelAlignmentStyles.TopRight ||
labelAlignment == LabelAlignmentStyles.BottomLeft ||
labelAlignment == LabelAlignmentStyles.BottomRight)
{
connectorPosition.Y = labelRectAbs.Bottom;
}
}
// Apply anchor cap settings
if(smartLabelStyle.CalloutLineAnchorCapStyle == LineAnchorCapStyle.Arrow)
{
//calloutPen.StartCap = LineCap.ArrowAnchor;
calloutPen.StartCap = LineCap.Custom;
calloutPen.CustomStartCap = new AdjustableArrowCap(calloutPen.Width + 2, calloutPen.Width + 3, true);
}
else if(smartLabelStyle.CalloutLineAnchorCapStyle == LineAnchorCapStyle.Diamond)
{
calloutPen.StartCap = LineCap.DiamondAnchor;
}
else if(smartLabelStyle.CalloutLineAnchorCapStyle == LineAnchorCapStyle.Round)
{
calloutPen.StartCap = LineCap.RoundAnchor;
}
else if(smartLabelStyle.CalloutLineAnchorCapStyle == LineAnchorCapStyle.Square)
{
calloutPen.StartCap = LineCap.SquareAnchor;
}
// Draw connection line between marker position and label
PointF markerPositionAbs = graph.GetAbsolutePoint(markerPosition);
graph.DrawLine(
calloutPen,
markerPositionAbs.X,
markerPositionAbs.Y,
connectorPosition.X,
connectorPosition.Y);
}
/// <summary>
/// Checks SmartLabelStyle collision.
/// </summary>
/// <param name="common">Reference to common elements.</param>
/// <param name="graph">Reference to chart graphics object.</param>
/// <param name="area">Chart area.</param>
/// <param name="smartLabelStyle">Smart labels style.</param>
/// <param name="position">Original label position.</param>
/// <param name="size">Label text size.</param>
/// <param name="markerPosition">Marker position.</param>
/// <param name="format">Label string format.</param>
/// <param name="labelAlignment">Label alignment.</param>
/// <param name="checkCalloutLineOverlapping">Indicates that labels overlapping by callout line must be checked.</param>
/// <returns>True if label collides.</returns>
virtual internal bool IsSmartLabelCollide(
CommonElements common,
ChartGraphics graph,
ChartArea area,
SmartLabelStyle smartLabelStyle,
PointF position,
SizeF size,
PointF markerPosition,
StringFormat format,
LabelAlignmentStyles labelAlignment,
bool checkCalloutLineOverlapping)
{
bool collisionDetected = false;
// Calculate label position rectangle
RectangleF labelPosition = GetLabelPosition(graph, position, size, format, false);
// Check if label goes outside of the chart picture
if(labelPosition.X < 0f || labelPosition.Y < 0f ||
labelPosition.Bottom > 100f || labelPosition.Right > 100f)
{
#if DEBUG
// DEBUG: Mark collided labels
if (graph != null && common!=null && common.Chart!=null && common.Chart.ShowDebugMarkings)
{
graph.Graphics.DrawRectangle(Pens.Cyan, Rectangle.Round(graph.GetAbsoluteRectangle(labelPosition)));
}
#endif
collisionDetected = true;
}
// Check if label is drawn outside of plotting area (collides with axis?).
if(!collisionDetected && area != null)
{
if(area.chartAreaIsCurcular)
{
using( GraphicsPath areaPath = new GraphicsPath() )
{
// Add circular shape of the area into the graphics path
areaPath.AddEllipse(area.PlotAreaPosition.ToRectangleF());
if(smartLabelStyle.AllowOutsidePlotArea == LabelOutsidePlotAreaStyle.Partial)
{
PointF centerPos = new PointF(
labelPosition.X + labelPosition.Width/2f,
labelPosition.Y + labelPosition.Height/2f);
if(!areaPath.IsVisible(centerPos))
{
// DEBUG: Mark collided labels
#if DEBUG
if(graph != null && common.Chart.ShowDebugMarkings)
{
graph.Graphics.DrawRectangle(Pens.Cyan, Rectangle.Round(graph.GetAbsoluteRectangle(labelPosition)));
}
#endif
collisionDetected = true;
}
}
else if(smartLabelStyle.AllowOutsidePlotArea == LabelOutsidePlotAreaStyle.No)
{
if(!areaPath.IsVisible(labelPosition.Location) ||
!areaPath.IsVisible(new PointF(labelPosition.Right, labelPosition.Y)) ||
!areaPath.IsVisible(new PointF(labelPosition.Right, labelPosition.Bottom)) ||
!areaPath.IsVisible(new PointF(labelPosition.X, labelPosition.Bottom)) )
{
// DEBUG: Mark collided labels
#if DEBUG
if(graph != null && common.Chart.ShowDebugMarkings)
{
graph.Graphics.DrawRectangle(Pens.Cyan, Rectangle.Round(graph.GetAbsoluteRectangle(labelPosition)));
}
#endif
collisionDetected = true;
}
}
}
}
else
{
if(smartLabelStyle.AllowOutsidePlotArea == LabelOutsidePlotAreaStyle.Partial)
{
PointF centerPos = new PointF(
labelPosition.X + labelPosition.Width/2f,
labelPosition.Y + labelPosition.Height/2f);
if(!area.PlotAreaPosition.ToRectangleF().Contains(centerPos))
{
// DEBUG: Mark collided labels
#if DEBUG
if(graph != null && common.Chart.ShowDebugMarkings)
{
graph.Graphics.DrawRectangle(Pens.Cyan, Rectangle.Round(graph.GetAbsoluteRectangle(labelPosition)));
}
#endif
collisionDetected = true;
}
}
else if(smartLabelStyle.AllowOutsidePlotArea == LabelOutsidePlotAreaStyle.No)
{
if(!area.PlotAreaPosition.ToRectangleF().Contains(labelPosition))
{
// DEBUG: Mark collided labels
#if DEBUG
if(graph != null && common.Chart.ShowDebugMarkings)
{
graph.Graphics.DrawRectangle(Pens.Cyan, Rectangle.Round(graph.GetAbsoluteRectangle(labelPosition)));
}
#endif
collisionDetected = true;
}
}
}
}
// Check if 1 collisuion is aceptable in case of cennter alignment
bool allowOneCollision =
(labelAlignment == LabelAlignmentStyles.Center && !smartLabelStyle.IsMarkerOverlappingAllowed) ? true : false;
if(this.checkAllCollisions)
{
allowOneCollision = false;
}
// Loop through all smart label positions
if(!collisionDetected && this.smartLabelsPositions != null)
{
int index = -1;
foreach(RectangleF pos in this.smartLabelsPositions)
{
// Increase index
++index;
// Check if label collide with other labels or markers.
bool collision = pos.IntersectsWith(labelPosition);
// Check if label callout line collide with other labels or markers.
// Line may overlap markers!
if(!collision &&
checkCalloutLineOverlapping &&
index >= markersCount)
{
PointF labelCenter = new PointF(
labelPosition.X + labelPosition.Width / 2f,
labelPosition.Y + labelPosition.Height / 2f);
if(LineIntersectRectangle(pos, markerPosition, labelCenter))
{
collision = true;
}
}
// Collision detected
if(collision)
{
// Check if 1 collision allowed
if(allowOneCollision)
{
allowOneCollision = false;
continue;
}
// DEBUG: Mark collided labels
#if DEBUG
if(graph != null &&
common.ChartPicture != null &&
common.ChartPicture.ChartGraph != null &&
common.Chart.ShowDebugMarkings)
{
common.ChartPicture.ChartGraph.Graphics.DrawRectangle(Pens.Blue, Rectangle.Round(common.ChartPicture.ChartGraph.GetAbsoluteRectangle(pos)));
common.ChartPicture.ChartGraph.Graphics.DrawRectangle(Pens.Red, Rectangle.Round(common.ChartPicture.ChartGraph.GetAbsoluteRectangle(labelPosition)));
}
#endif
collisionDetected = true;
break;
}
}
}
return collisionDetected;
}
/// <summary>
/// Checks if rectangle intersected by the line.
/// </summary>
/// <param name="rect">Rectangle to be tested.</param>
/// <param name="point1">First line point.</param>
/// <param name="point2">Second line point.</param>
/// <returns>True if line intersects rectangle.</returns>
private bool LineIntersectRectangle(RectangleF rect, PointF point1, PointF point2)
{
// Check for horizontal line
if(point1.X == point2.X)
{
if(point1.X >= rect.X && point1.X <= rect.Right)
{
if(point1.Y < rect.Y && point2.Y < rect.Y)
{
return false;
}
if(point1.Y > rect.Bottom && point2.Y > rect.Bottom)
{
return false;
}
return true;
}
return false;
}
// Check for vertical line
if(point1.Y == point2.Y)
{
if(point1.Y >= rect.Y && point1.Y <= rect.Bottom)
{
if(point1.X < rect.X && point2.X < rect.X)
{
return false;
}
if(point1.X > rect.Right && point2.X > rect.Right)
{
return false;
}
return true;
}
return false;
}
// Check if line completly outside rectangle
if(point1.X < rect.X && point2.X < rect.X)
{
return false;
}
else if(point1.X > rect.Right && point2.X > rect.Right)
{
return false;
}
else if(point1.Y < rect.Y && point2.Y < rect.Y)
{
return false;
}
else if(point1.Y > rect.Bottom && point2.Y > rect.Bottom)
{
return false;
}
// Check if one of the points inside rectangle
if( rect.Contains(point1) ||
rect.Contains(point2) )
{
return true;
}
// Calculate intersection point of the line with each side of the rectangle
PointF intersection = CalloutAnnotation.GetIntersectionY(point1, point2, rect.Y);
if( rect.Contains(intersection))
{
return true;
}
intersection = CalloutAnnotation.GetIntersectionY(point1, point2, rect.Bottom);
if( rect.Contains(intersection))
{
return true;
}
intersection = CalloutAnnotation.GetIntersectionX(point1, point2, rect.X);
if( rect.Contains(intersection))
{
return true;
}
intersection = CalloutAnnotation.GetIntersectionX(point1, point2, rect.Right);
if( rect.Contains(intersection))
{
return true;
}
return false;
}
/// <summary>
/// Adds positions of the series markers into the list.
/// </summary>
/// <param name="common">Reference to common elements.</param>
/// <param name="area">Chart area.</param>
virtual internal void AddMarkersPosition(
CommonElements common,
ChartArea area)
{
// Proceed only if there is no items in the list yet
if(this.smartLabelsPositions.Count == 0 && area != null)
{
// Get chart types registry
ChartTypeRegistry registry = common.ChartTypeRegistry;
// Loop through all the series from this chart area
foreach(Series series in common.DataManager.Series)
{
// Check if marker overapping is enabled for the series
if(series.ChartArea == area.Name &&
series.SmartLabelStyle.Enabled &&
!series.SmartLabelStyle.IsMarkerOverlappingAllowed)
{
// Get series chart type
IChartType chartType = registry.GetChartType(series.ChartTypeName);
// Add series markers positions into the list
chartType.AddSmartLabelMarkerPositions(common, area, series, this.smartLabelsPositions);
}
}
// Make sure labels do not intersect with scale breaks
foreach(Axis currentAxis in area.Axes)
{
// Check if scale breaks are defined and there are non zero spacing
if(currentAxis.ScaleBreakStyle.Spacing > 0.0
&& currentAxis.ScaleSegments.Count > 0)
{
for(int index = 0; index < (currentAxis.ScaleSegments.Count - 1); index++)
{
// Get break position in pixel coordinates
RectangleF breakPosition = currentAxis.ScaleSegments[index].GetBreakLinePosition(common.graph, currentAxis.ScaleSegments[index + 1]);
breakPosition = common.graph.GetRelativeRectangle(breakPosition);
// Create array list if needed
if(this.smartLabelsPositions == null)
{
this.smartLabelsPositions = new ArrayList();
}
// Add label position into the list
this.smartLabelsPositions.Add(breakPosition);
}
}
}
}
}
/// <summary>
/// Adds single Smart Label position into the list.
/// </summary>
/// <param name="graph">Chart graphics object.</param>
/// <param name="position">Original label position.</param>
/// <param name="size">Label text size.</param>
/// <param name="format">Label string format.</param>
internal void AddSmartLabelPosition(
ChartGraphics graph,
PointF position,
SizeF size,
StringFormat format)
{
// Calculate label position rectangle
RectangleF labelPosition = GetLabelPosition(graph, position, size, format, false);
if(this.smartLabelsPositions == null)
{
this.smartLabelsPositions = new ArrayList();
}
// Add label position into the list
this.smartLabelsPositions.Add(labelPosition);
}
/// <summary>
/// Gets rectangle position of the label.
/// </summary>
/// <param name="graph">Chart graphics object.</param>
/// <param name="position">Original label position.</param>
/// <param name="size">Label text size.</param>
/// <param name="format">Label string format.</param>
/// <param name="adjustForDrawing">Result position is adjusted for drawing.</param>
/// <returns>Label rectangle position.</returns>
internal RectangleF GetLabelPosition(
ChartGraphics graph,
PointF position,
SizeF size,
StringFormat format,
bool adjustForDrawing)
{
// Calculate label position rectangle
RectangleF labelPosition = RectangleF.Empty;
labelPosition.Width = size.Width;
labelPosition.Height = size.Height;
// Calculate pixel size in relative coordiantes
SizeF pixelSize = SizeF.Empty;
if(graph != null)
{
pixelSize = graph.GetRelativeSize(new SizeF(1f, 1f));
}
if(format.Alignment == StringAlignment.Far)
{
labelPosition.X = position.X - size.Width;
if(adjustForDrawing && !pixelSize.IsEmpty)
{
labelPosition.X -= 4f*pixelSize.Width;
labelPosition.Width += 4f*pixelSize.Width;
}
}
else if(format.Alignment == StringAlignment.Near)
{
labelPosition.X = position.X;
if(adjustForDrawing && !pixelSize.IsEmpty)
{
labelPosition.Width += 4f*pixelSize.Width;
}
}
else if(format.Alignment == StringAlignment.Center)
{
labelPosition.X = position.X - size.Width/2F;
if(adjustForDrawing && !pixelSize.IsEmpty)
{
labelPosition.X -= 2f*pixelSize.Width;
labelPosition.Width += 4f*pixelSize.Width;
}
}
if(format.LineAlignment == StringAlignment.Far)
{
labelPosition.Y = position.Y - size.Height;
}
else if(format.LineAlignment == StringAlignment.Near)
{
labelPosition.Y = position.Y;
}
else if(format.LineAlignment == StringAlignment.Center)
{
labelPosition.Y = position.Y - size.Height/2F;
}
return labelPosition;
}
/// <summary>
/// Gets point position of the label.
/// </summary>
/// <param name="labelAlignment">Label alignment.</param>
/// <param name="markerPosition">Marker position.</param>
/// <param name="sizeMarker">Marker size.</param>
/// <param name="sizeFont">Label size.</param>
/// <param name="format">String format.</param>
/// <returns>Label point position.</returns>
private PointF CalculatePosition(
LabelAlignmentStyles labelAlignment,
PointF markerPosition,
SizeF sizeMarker,
SizeF sizeFont,
ref StringFormat format)
{
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Center;
// Calculate label position
PointF position = new PointF(markerPosition.X, markerPosition.Y);
switch(labelAlignment)
{
case LabelAlignmentStyles.Center:
format.Alignment = StringAlignment.Center;
break;
case LabelAlignmentStyles.Bottom:
format.Alignment = StringAlignment.Center;
position.Y += sizeMarker.Height / 1.75F;
position.Y += sizeFont.Height/ 2F;
break;
case LabelAlignmentStyles.Top:
format.Alignment = StringAlignment.Center;
position.Y -= sizeMarker.Height / 1.75F;
position.Y -= sizeFont.Height/ 2F;
break;
case LabelAlignmentStyles.Left:
format.Alignment = StringAlignment.Far;
position.X -= sizeMarker.Height / 1.75F;
break;
case LabelAlignmentStyles.TopLeft:
format.Alignment = StringAlignment.Far;
position.X -= sizeMarker.Height / 1.75F;
position.Y -= sizeMarker.Height / 1.75F;
position.Y -= sizeFont.Height/ 2F;
break;
case LabelAlignmentStyles.BottomLeft:
format.Alignment = StringAlignment.Far;
position.X -= sizeMarker.Height / 1.75F;
position.Y += sizeMarker.Height / 1.75F;
position.Y += sizeFont.Height/ 2F;
break;
case LabelAlignmentStyles.Right:
position.X += sizeMarker.Height / 1.75F;
break;
case LabelAlignmentStyles.TopRight:
position.X += sizeMarker.Height / 1.75F;
position.Y -= sizeMarker.Height / 1.75F;
position.Y -= sizeFont.Height/ 2F;
break;
case LabelAlignmentStyles.BottomRight:
position.X += sizeMarker.Height / 1.75F;
position.Y += sizeMarker.Height / 1.75F;
position.Y += sizeFont.Height/ 2F;
break;
}
return position;
}
#endregion
}
/// <summary>
/// AnnotationSmartLabel class provides SmartLabelStyle functionality
/// specific to the annotation objects.
/// </summary>
[
SRDescription("DescriptionAttributeAnnotationSmartLabels_AnnotationSmartLabels"),
]
internal class AnnotationSmartLabel : SmartLabel
{
#region Constructors and initialization
/// <summary>
/// Default public constructor.
/// </summary>
public AnnotationSmartLabel()
{
}
#endregion
#region Methods
/// <summary>
/// Checks SmartLabelStyle collision.
/// </summary>
/// <param name="common">Reference to common elements.</param>
/// <param name="graph">Reference to chart graphics object.</param>
/// <param name="area">Chart area.</param>
/// <param name="smartLabelStyle">Smart labels style.</param>
/// <param name="position">Original label position.</param>
/// <param name="size">Label text size.</param>
/// <param name="markerPosition">Marker position.</param>
/// <param name="format">Label string format.</param>
/// <param name="labelAlignment">Label alignment.</param>
/// <param name="checkCalloutLineOverlapping">Indicates that labels overlapping by callout line must be checked.</param>
/// <returns>True if label collides.</returns>
override internal bool IsSmartLabelCollide(
CommonElements common,
ChartGraphics graph,
ChartArea area,
SmartLabelStyle smartLabelStyle,
PointF position,
SizeF size,
PointF markerPosition,
StringFormat format,
LabelAlignmentStyles labelAlignment,
bool checkCalloutLineOverlapping)
{
bool collisionDetected = false;
//*******************************************************************
//** Check collision with smatl labels of series in chart area
//*******************************************************************
if (area != null && area.Visible)
{
area.smartLabels.checkAllCollisions = true;
if (area.smartLabels.IsSmartLabelCollide(
common,
graph,
area,
smartLabelStyle,
position,
size,
markerPosition,
format,
labelAlignment,
checkCalloutLineOverlapping) )
{
area.smartLabels.checkAllCollisions = false;
return true;
}
area.smartLabels.checkAllCollisions = false;
}
//*******************************************************************
//** Check collision with other annotations.
//*******************************************************************
// Calculate label position rectangle
RectangleF labelPosition = GetLabelPosition(graph, position, size, format, false);
// Check if 1 collisuion is aceptable in case of cennter alignment
bool allowOneCollision =
(labelAlignment == LabelAlignmentStyles.Center && !smartLabelStyle.IsMarkerOverlappingAllowed) ? true : false;
if(this.checkAllCollisions)
{
allowOneCollision = false;
}
// Check if label collide with other labels or markers.
foreach(RectangleF pos in this.smartLabelsPositions)
{
if(pos.IntersectsWith(labelPosition))
{
// Check if 1 collision allowed
if(allowOneCollision)
{
allowOneCollision = false;
continue;
}
// DEBUG: Mark collided labels
#if DEBUG
if(graph != null && common.Chart.ShowDebugMarkings)
{
graph.Graphics.DrawRectangle(Pens.Blue, Rectangle.Round(graph.GetAbsoluteRectangle(pos)));
graph.Graphics.DrawRectangle(Pens.Red, Rectangle.Round(graph.GetAbsoluteRectangle(labelPosition)));
}
#endif
collisionDetected = true;
break;
}
}
return collisionDetected;
}
/// <summary>
/// Adds positions of the series markers into the list.
/// </summary>
/// <param name="common">Reference to common elements.</param>
/// <param name="area">Chart area.</param>
override internal void AddMarkersPosition(
CommonElements common,
ChartArea area)
{
// Proceed only if there is no items in the list yet
if(this.smartLabelsPositions.Count == 0 &&
common != null &&
common.Chart != null)
{
// Add annotations anchor points
foreach(Annotation annotation in common.Chart.Annotations)
{
annotation.AddSmartLabelMarkerPositions(this.smartLabelsPositions);
}
}
}
/// <summary>
/// Process single SmartLabelStyle by adjusting it's position in case of collision.
/// </summary>
/// <param name="common">Reference to common elements.</param>
/// <param name="graph">Reference to chart graphics object.</param>
/// <param name="area">Chart area.</param>
/// <param name="smartLabelStyle">Smart labels style.</param>
/// <param name="labelPosition">Original label position.</param>
/// <param name="labelSize">Label text size.</param>
/// <param name="format">Label string format.</param>
/// <param name="markerPosition">Marker position.</param>
/// <param name="markerSize">Marker size.</param>
/// <param name="labelAlignment">Label alignment.</param>
/// <returns>Adjusted position of the label.</returns>
override internal void DrawCallout(
CommonElements common,
ChartGraphics graph,
ChartArea area,
SmartLabelStyle smartLabelStyle,
PointF labelPosition,
SizeF labelSize,
StringFormat format,
PointF markerPosition,
SizeF markerSize,
LabelAlignmentStyles labelAlignment)
{
// No callout is drawn for the annotations
}
#endregion
}
}
|