//------------------------------------------------------------- // // Copyright © Microsoft Corporation. All Rights Reserved. // //------------------------------------------------------------- // @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 /// /// Line anchor cap style. /// [ SRDescription("DescriptionAttributeLineAnchorCapStyle_LineAnchorCapStyle") ] public enum LineAnchorCapStyle { /// /// No line anchor cap. /// None, /// /// Arrow line anchor cap. /// Arrow, /// /// Diamond line anchor cap. /// Diamond, /// /// Square line anchor cap. /// Square, /// /// Round line anchor cap. /// Round } /// /// Data point label callout style. /// [ SRDescription("DescriptionAttributeLabelCalloutStyle_LabelCalloutStyle") ] public enum LabelCalloutStyle { /// /// Label connected with the marker using just a line. /// None, /// /// Label is undelined and connected with the marker using a line. /// Underlined, /// /// Box is drawn around the label and it's connected with the marker using a line. /// Box } /// /// Data point label outside of the plotting area style. /// [ SRDescription("DescriptionAttributeLabelOutsidePlotAreaStyle_LabelOutsidePlotAreaStyle") ] public enum LabelOutsidePlotAreaStyle { /// /// Labels can be positioned outside of the plotting area. /// Yes, /// /// Labels can not be positioned outside of the plotting area. /// No, /// /// Labels can be partially outside of the plotting area. /// Partial } #endregion /// /// 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. /// [ 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 /// /// Default public constructor. /// public SmartLabelStyle() { this.chartElement = null; } /// /// Constructor. /// /// Chart element this style belongs to. internal SmartLabelStyle(Object chartElement) { this.chartElement = chartElement; } #endregion #region Properties /// /// SmartLabelStyle algorithm enabled flag. /// [ SRCategory("CategoryAttributeMisc"), Bindable(true), DefaultValue(true), SRDescription("DescriptionAttributeEnabled13"), ParenthesizePropertyNameAttribute(true), ] virtual public bool Enabled { get { return _enabled; } set { _enabled = value; Invalidate(); } } /// /// Indicates that marker overlapping by label is allowed. /// [ SRCategory("CategoryAttributeMisc"), Bindable(true), DefaultValue(false), SRDescription("DescriptionAttributeMarkerOverlapping"), ] virtual public bool IsMarkerOverlappingAllowed { get { return _isMarkerOverlappingAllowed; } set { _isMarkerOverlappingAllowed = value; Invalidate(); } } /// /// Indicates that overlapped labels that can't be repositioned will be hidden. /// [ SRCategory("CategoryAttributeMisc"), Bindable(true), DefaultValue(true), SRDescription("DescriptionAttributeHideOverlapped"), ] virtual public bool IsOverlappedHidden { get { return _isOverlappedHidden; } set { _isOverlappedHidden = value; Invalidate(); } } /// /// Possible moving directions for the overlapped SmartLabelStyle. /// [ 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(); } } /// /// Minimum distance the overlapped SmartLabelStyle can be moved from the marker. Distance is measured in pixels. /// [ 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(); } } /// /// Maximum distance the overlapped SmartLabelStyle can be moved from the marker. Distance is measured in pixels. /// [ 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(); } } /// /// Defines if SmartLabelStyle are allowed to be drawn outside of the plotting area. /// [ SRCategory("CategoryAttributeMisc"), Bindable(true), DefaultValue(LabelOutsidePlotAreaStyle.Partial), SRDescription("DescriptionAttributeAllowOutsidePlotArea"), ] virtual public LabelOutsidePlotAreaStyle AllowOutsidePlotArea { get { return _allowOutsidePlotArea; } set { _allowOutsidePlotArea = value; Invalidate(); } } /// /// Callout style of the repositioned SmartLabelStyle. /// [ SRCategory("CategoryAttributeMisc"), Bindable(true), DefaultValue(LabelCalloutStyle.Underlined), SRDescription("DescriptionAttributeCalloutStyle3"), ] virtual public LabelCalloutStyle CalloutStyle { get { return _calloutStyle; } set { _calloutStyle = value; Invalidate(); } } /// /// Label callout line color. /// [ 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(); } } /// /// Label callout line style. /// [ 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(); } } /// /// Label callout back color. Applies to the Box style only! /// [ 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(); } } /// /// Label callout line width. /// [ SRCategory("CategoryAttributeAppearance"), Bindable(true), DefaultValue(1), SRDescription("DescriptionAttributeLineWidth"), ] virtual public int CalloutLineWidth { get { return _calloutLineWidth; } set { _calloutLineWidth = value; Invalidate(); } } /// /// Label callout line anchor cap. /// [ 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 /// /// Invalidates ----osiated chart element. /// private void Invalidate() { if(chartElement != null) { if(chartElement is Series) { ((Series)chartElement).Invalidate(false, false); } else if(chartElement is Annotation) { ((Annotation)chartElement).Invalidate(); } } } #endregion } /// /// 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. /// [ 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 /// /// Default public constructor. /// public SmartLabel() { } #endregion #region Methods /// /// Reset SmartLabelStyle object. /// internal void Reset() { // Re-initialize list of labels position smartLabelsPositions = new ArrayList(); } /// /// Process single SmartLabelStyle by adjusting it's position in case of collision. /// /// Reference to common elements. /// Reference to chart graphics object. /// Chart area. /// Smart labels style. /// Original label position. /// Label text size. /// Label string format. /// Marker position. /// Marker size. /// Original label alignment. /// Adjusted position of the label. 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); } /// /// Process single SmartLabelStyle by adjusting it's position in case of collision. /// /// Reference to common elements. /// Reference to chart graphics object. /// Chart area. /// Smart labels style. /// Original label position. /// Label text size. /// Label string format. /// Marker position. /// Marker size. /// Original label alignment. /// Indicates that labels overlapping by callout line must be checked. /// Adjusted position of the label. 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; } /// /// Process single SmartLabelStyle by adjusting it's position in case of collision. /// /// Reference to common elements. /// Reference to chart graphics object. /// Chart area. /// Smart labels style. /// Original label position. /// Label text size. /// Label string format. /// Marker position. /// Marker size. /// Label alignment. /// Indicates that labels overlapping by callout line must be checked. /// True if label was moved away from the marker. 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; } /// /// Process single SmartLabelStyle by adjusting it's position in case of collision. /// /// Reference to common elements. /// Reference to chart graphics object. /// Chart area. /// Smart labels style. /// Original label position. /// Label text size. /// Label string format. /// Marker position. /// Marker size. /// Label alignment. /// Adjusted position of the label. 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); } /// /// Checks SmartLabelStyle collision. /// /// Reference to common elements. /// Reference to chart graphics object. /// Chart area. /// Smart labels style. /// Original label position. /// Label text size. /// Marker position. /// Label string format. /// Label alignment. /// Indicates that labels overlapping by callout line must be checked. /// True if label collides. 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; } /// /// Checks if rectangle intersected by the line. /// /// Rectangle to be tested. /// First line point. /// Second line point. /// True if line intersects rectangle. 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; } /// /// Adds positions of the series markers into the list. /// /// Reference to common elements. /// Chart area. 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); } } } } } /// /// Adds single Smart Label position into the list. /// /// Chart graphics object. /// Original label position. /// Label text size. /// Label string format. 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); } /// /// Gets rectangle position of the label. /// /// Chart graphics object. /// Original label position. /// Label text size. /// Label string format. /// Result position is adjusted for drawing. /// Label rectangle position. 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; } /// /// Gets point position of the label. /// /// Label alignment. /// Marker position. /// Marker size. /// Label size. /// String format. /// Label point position. 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 } /// /// AnnotationSmartLabel class provides SmartLabelStyle functionality /// specific to the annotation objects. /// [ SRDescription("DescriptionAttributeAnnotationSmartLabels_AnnotationSmartLabels"), ] internal class AnnotationSmartLabel : SmartLabel { #region Constructors and initialization /// /// Default public constructor. /// public AnnotationSmartLabel() { } #endregion #region Methods /// /// Checks SmartLabelStyle collision. /// /// Reference to common elements. /// Reference to chart graphics object. /// Chart area. /// Smart labels style. /// Original label position. /// Label text size. /// Marker position. /// Label string format. /// Label alignment. /// Indicates that labels overlapping by callout line must be checked. /// True if label collides. 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; } /// /// Adds positions of the series markers into the list. /// /// Reference to common elements. /// Chart area. 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); } } } /// /// Process single SmartLabelStyle by adjusting it's position in case of collision. /// /// Reference to common elements. /// Reference to chart graphics object. /// Chart area. /// Smart labels style. /// Original label position. /// Label text size. /// Label string format. /// Marker position. /// Marker size. /// Label alignment. /// Adjusted position of the label. 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 } }