using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Shapes; using System.Collections.Generic; using System.Linq; namespace Visifire.Commons { public class LabelPlacementHelper { #region "Vertical LabelPlacement Helper" /// /// Arrange the labels vertically /// /// /// /// /// /// internal static Rect[] VerticalLabelPlacement(Rect area, ref Rect[] labelsInfo) { Int32 index; for (index = 0; index < labelsInfo.Length; index++) { if (index == labelsInfo.Length - 1) break; if (CheckOverlap(labelsInfo[index], labelsInfo[index + 1])) { // Shift the top label Double shiftValue = CalculateShiftValue(labelsInfo[index], labelsInfo[index + 1]); Int32 findSpaceAtIndex = index; Double spaceFoundAtIndex = FindSpaceTowardsTopAtIndex(ref findSpaceAtIndex, shiftValue * 2, area, labelsInfo); if (spaceFoundAtIndex != 0 || findSpaceAtIndex != -1) // if space found to shift towards top { // if(index != findSpaceAtIndex) ShiftTowardsTop(shiftValue * 2, index, findSpaceAtIndex, ref labelsInfo); } else { findSpaceAtIndex = index; spaceFoundAtIndex = FindSpaceTowardsBottomAtIndex(ref findSpaceAtIndex, shiftValue * 2, area, labelsInfo); if (spaceFoundAtIndex != 0 || findSpaceAtIndex != -1) // if space found to shift towards top { if (index == findSpaceAtIndex) ShiftTowardsBottom(shiftValue * 2, index, findSpaceAtIndex, ref labelsInfo); else ShiftTowardsBottom(shiftValue * 2, index + 1, findSpaceAtIndex, ref labelsInfo); } } } } for (index = 0; index < labelsInfo.Length; index++) { if (index == labelsInfo.Length - 1) break; if (CheckOverlap(labelsInfo[index], labelsInfo[index + 1])) { // Shift the top label Double shiftValue = CalculateShiftValue(labelsInfo[index], labelsInfo[index + 1]) * 2; for (Int32 i = index; i > 0 && shiftValue > 0; i--) { Double posibleShiftValue = CheckSpaceAtTop(i, area, labelsInfo); if (posibleShiftValue > 0 && shiftValue > posibleShiftValue) { ShiftTowardsTop(posibleShiftValue, index, i, ref labelsInfo); shiftValue -= posibleShiftValue; } } } } for (index = 0; index < labelsInfo.Length; index++) { if (index == labelsInfo.Length - 1) break; if (CheckOverlap(labelsInfo[index], labelsInfo[index + 1])) { // Shift the top label Double shiftValue = CalculateShiftValue(labelsInfo[index], labelsInfo[index + 1]) * 2; for (Int32 i = index + 1; i < labelsInfo.Length && shiftValue > 0; i++) { Double posibleShiftValue; if (i == labelsInfo.Length - 1) posibleShiftValue = shiftValue; else posibleShiftValue = CheckSpaceAtBottom(i, area, labelsInfo); if (posibleShiftValue > 0 && shiftValue >= posibleShiftValue) { ShiftTowardsBottom(posibleShiftValue, index + 1, i, ref labelsInfo); shiftValue -= posibleShiftValue; } } } } return labelsInfo; } private static Double FindSpaceTowardsTopAtIndex(ref Int32 index, Double spaceRequired, Rect area, Rect[] labelsInfo) { Double amountOfSpaceFound = 0; for (; index >= 0; index--) { Double space = CheckSpaceAtTop(index, area, labelsInfo); if (spaceRequired <= space) { amountOfSpaceFound = space; break; } } if (amountOfSpaceFound == 0) index = -1; return amountOfSpaceFound; } private static Double FindSpaceTowardsBottomAtIndex(ref Int32 index, Double spaceRequired, Rect area, Rect[] labelsInfo) { Double amountOfSpaceFound = 0; for (; index < labelsInfo.Length - 1; index++) { Double space = CheckSpaceAtBottom(index, area, labelsInfo); if (spaceRequired <= space) { amountOfSpaceFound = space; break; } } if (amountOfSpaceFound == 0) index = -1; return amountOfSpaceFound; } /// /// Check available space at top /// /// Index of target area /// labelsInfo /// Double private static Double CheckSpaceAtTop(Int32 index, Rect baseArea, Rect[] labelsInfo) { if (index == 0) { if (labelsInfo[index].Top < 0) return 0; else return labelsInfo[index].Top; } else { if (labelsInfo.Length > 1) { if (labelsInfo[index].Top > (labelsInfo[index - 1].Top + labelsInfo[index - 1].Height)) return labelsInfo[index].Top - (labelsInfo[index - 1].Top + labelsInfo[index - 1].Height); else return 0; } else return 0; } } /// /// Shift labels towards top /// /// Amount to shift /// Start index of labels /// Array of label information private static void ShiftTowardsTop(Double value, Int32 startIndex, ref Rect[] labelsInfos) { for (Int32 index = startIndex; index >= 0; index--) { labelsInfos[index].Y -= value; } } /// /// Shift labels towards top /// /// Amount to shift /// Start index of labels /// Array of label information private static void ShiftTowardsTop(Double value, Int32 fromIndex, Int32 toIndex, ref Rect[] labelsInfos) { for (Int32 index = fromIndex; index >= toIndex; index--) { labelsInfos[index].Y -= value; } } /// /// Shift labels towards top /// /// Amount to shift /// Start index of labels /// Array of label information private static void ShiftTowardsTop(Double value, ref Rect labelsInfo) { labelsInfo.Y -= value; } /// /// Shift labels towards bottom /// /// Amount to shift /// Start index of labels /// Array of label information private static void ShiftTowardsBottom(Double value, Int32 fromIndex, Int32 toIndex, ref Rect[] labelsInfos) { for (Int32 index = fromIndex; index <= toIndex && toIndex < labelsInfos.Length; index++) { labelsInfos[index].Y += value; } } /// /// Shift labels towards bottom /// /// Amount to shift /// Start index of labels /// Array of label information private static void ShiftTowardsBottom(Double value, Int32 startIndex, ref Rect[] labelsInfos) { for (Int32 index = startIndex; index < labelsInfos.Length; index++) { labelsInfos[index].Y += value; } } /// /// Shift labels towards bottom /// /// Amount to shift /// Start index of labels /// Array of label information private static void ShiftTowardsBottom(Double value, ref Rect labelsInfo) { labelsInfo.Y += value; } /// /// Check available space at bottom /// /// Index of target area /// labelsInfo /// Double private static Double CheckSpaceAtBottom(Int32 index, Rect baseArea, Rect[] labelsInfo) { if (index == labelsInfo.Length - 1) { Double gap = baseArea.Height - (labelsInfo[index].Top + labelsInfo[index].Height); return (gap > 0) ? gap : 0; } else { if (labelsInfo.Length > 1) { if ((labelsInfo[index + 1].Top) > (labelsInfo[index].Top + labelsInfo[index].Height)) { Double gap = (labelsInfo[index + 1].Top) - (labelsInfo[index].Top + labelsInfo[index].Height); return gap; } else return 0; } else return 0; } } /// /// Check whether two areas overlap on each other /// /// 1st area information /// 2nd area information /// True/False private static Boolean CheckOverlap(Rect areaInfo1, Rect areaInfo2) { if (areaInfo1.Top + areaInfo1.Height > areaInfo2.Top) return true; else return false; } /// /// Calculate amount of shift value towards top or bottom to overcome overlap. /// /// 1st area information /// 2nd area information /// Double private static Double CalculateShiftValue(Rect areaInfo1, Rect areaInfo2) { if (areaInfo1.Top + areaInfo1.Height > areaInfo2.Top) return Math.Abs(((areaInfo1.Top + areaInfo1.Height) - areaInfo2.Top) / 2); else return 0; } #endregion /// /// Get the list of labels at the right and left side of the pie of a circular region /// /// List /// labels at left /// labels at right private static void GetLeftAndRightLabels(List labels, out List labelsAtLeft, out List labelsAtRight) { // Labels at angle >= 3 * Math.PI / 2 && angle < 0 List labelsR1 = (from cl in labels where cl.CurrentMeanAngle >= 3 * Math.PI / 2 orderby cl.CurrentMeanAngle ascending select cl).ToList(); // Labels at angle >= 0 && angle < <= Math.PI / 2 List labelsR2 = (from cl in labels where cl.CurrentMeanAngle >= 0 && cl.CurrentMeanAngle <= Math.PI / 2 orderby cl.CurrentMeanAngle ascending select cl).ToList(); // Combine labels at right present at the right side of the circular region labelsAtRight = new List(); labelsAtRight.AddRange(labelsR1); labelsAtRight.AddRange(labelsR2); // All labels present at the right side of the circular region labelsAtLeft = (from cl in labels where cl.CurrentMeanAngle > Math.PI / 2 && cl.CurrentMeanAngle < 3 * Math.PI / 2 orderby cl.CurrentMeanAngle ascending select cl).ToList(); } /// /// Rearrange the labels vertically /// /// List of CircularLabels /// Left of the bounding area /// Top of the bounding area /// Height of the bounding area /// Width of the bounding area private static void RearrangeLabelsVertically(List labels, Double leftOfArea, Double topOfArea, Double areaHeight, Double areaWidth) { Rect[] labelInfo = new Rect[labels.Count]; int index = 0; // Prepare label information into an array foreach (CircularLabel label in labels) { Double left = (Double)label.LabelVisual.GetValue(Canvas.LeftProperty); Double top = (Double)label.LabelVisual.GetValue(Canvas.TopProperty); labelInfo[index++] = new Rect(left, top, label.LabelVisual.Width, label.LabelVisual.Height); } // Arrange the labels vertically LabelPlacementHelper.VerticalLabelPlacement(new Rect(leftOfArea, topOfArea, areaWidth, areaHeight), ref labelInfo); index = 0; // Update position of the labels foreach (CircularLabel label in labels) { Double top = labelInfo[index].Top + topOfArea; Double left = labelInfo[index].Left; Double currentMeanAngle = label.CalculateAngleByYCoordinate(top); Double offset = 0; if (!Double.IsNaN(currentMeanAngle)) { currentMeanAngle = CircularLabel.ResetMeanAngle(currentMeanAngle); label.Position = label.GetCartesianCoordinates4Labels(currentMeanAngle); if (left < label.Center.X) { left = label.Center.X - (label.Position.X - label.Center.X); //left = areaWidth - label.Position.X - label.LabelVisual.Width; //left = areaWidth - label.Position.X - (label.Center.X - left) + label.LabelVisual.Width; //left = label.Center.X - (label.Position.X - label.Center.X) - label.LabelVisual.Width; } else left = label.Position.X; label.CurrentMeanAngle = currentMeanAngle; } // Move the labels towards left or right if space availlable //if ((label.BaseMeanAngle > 7 * Math.PI / 4 && label.BaseMeanAngle < Math.PI / 4 // || label.BaseMeanAngle > 3 * Math.PI / 2 && label.BaseMeanAngle < 5 * Math.PI / 4) if (top > (label.YRadiusLabel * 2) / 6 && top < 5 * (label.YRadiusLabel * 2) / 6) { if (left < label.Center.X) { Double x = left - label.LabelVisual.Width; if (x > 0) offset = - x / 3; } else { Double x = areaWidth - (left + label.LabelVisual.Width); if (x > 0) offset = + x / 3; } } label.Position = new Point(left + offset, top); label.UpdateLabelVisualPosition(); //label.LabelVisual.SetValue(Canvas.LeftProperty, left); // ); //label.LabelVisual.SetValue(Canvas.TopProperty, top); index++; } } /// /// Arrange labels over a circular path /// /// /// public static void CircularLabelPlacment(Rect boundingArea, List labels, Boolean skippingEnabled) { //return; List labelsAtLeft; List labelsAtRight; List allLabels = new List(); GetLeftAndRightLabels(labels, out labelsAtLeft, out labelsAtRight); // Combine left and right labels allLabels.AddRange(labelsAtRight); allLabels.AddRange(labelsAtLeft); allLabels[0].IsFirst = true; allLabels[allLabels.Count - 1].IsLast = true; // Try to place labels and asign Skip value foreach (CircularLabel label in labels) { label.PlaceLabel(false); } // Try to place labels and asign Skip value if(skippingEnabled) foreach (CircularLabel label in labels) { label.PlaceLabel(true); } // Update label visual position foreach (CircularLabel label in labels) { label.UpdateLabelVisualPosition(); } // Labels that are not slipped yet List labelsNotSkipped = (from lb in labels where !lb.IsSkiped select lb).ToList(); GetLeftAndRightLabels(labelsNotSkipped, out labelsAtLeft, out labelsAtRight); labelsAtLeft.Reverse(); // Arrange right labels vertically RearrangeLabelsVertically(labelsAtRight, Double.NaN, 0, boundingArea.Height, boundingArea.Width); RearrangeLabelsVertically(labelsAtLeft, Double.NaN, 0, boundingArea.Height, boundingArea.Width); // Skip labels if any label goes out of PlotArea foreach (CircularLabel label in labelsNotSkipped) { Double left = (Double)label.LabelVisual.GetValue(Canvas.LeftProperty); Double top = (Double)label.LabelVisual.GetValue(Canvas.TopProperty); //if (top + label.Height > boundingArea.Height || label.CheckOutOfBounds()) // label.SkipLabel(); if (top + label.Height > boundingArea.Height || label.CheckOutOfBounds()) { if (skippingEnabled) label.SkipLabel(); else { label.Position = label.GetCartesianCoordinates4Labels(label.BaseMeanAngle); label.UpdateLabelVisualPosition(); } } } } public static Double LABEL_LINE_GAP = 3; } /// /// Types of overlap /// public enum OverlapTypes { Vertical, Horizontal, Both, None } /// /// Visifire.Commons.CircularLabel class /// public class CircularLabel { /// /// CircularLabel class is used to /// /// /// Center of the circular region /// Mean angle of the label /// XRadius of the ellipse for label placement /// YRadius of the ellipse for label placement /// XRadius of the chart /// YRadius of the chart /// Parent panel of the label. Used for debugging purpose public CircularLabel(FrameworkElement labelVisual, Point center, Double meanAngle, Double xRadiusLabel, Double yRadiusLabel, Double xRadiusChart, Double yRadiusChart, Canvas canvas) { LabelVisual = labelVisual; BaseMeanAngle = ResetMeanAngle(meanAngle); CurrentMeanAngle = BaseMeanAngle; XRadiusLabel = xRadiusLabel; YRadiusLabel = yRadiusLabel; XRadiusChart = xRadiusChart; YRadiusChart = yRadiusChart; Visual = canvas; Center = center; Position = GetCartesianCoordinates4Labels(meanAngle); UpdateLabelVisualPosition(); // CalculateMaxAndMinPosition(); } /// /// Check if CircularLabel overlaps with nearest CircularLabel /// /// Clockwise/Counterclockwise /// Vertical offset required to overcome overlap /// Horizontal offset required to overcome overlap /// OverlapTypes public OverlapTypes CheckOverLap(SweepDirection direction, out Double verticalOffset, out Double horizontalOffset) { verticalOffset = 0; horizontalOffset = 0; if(direction == SweepDirection.Clockwise) return (NextLabel == null) ? OverlapTypes.None : CheckOverlap(NextLabel, out verticalOffset, out horizontalOffset); else return (PreviusLabel == null) ? OverlapTypes.None : CheckOverlap(PreviusLabel, out verticalOffset, out horizontalOffset); } /// /// Ask for space to NextLabel if NextLabel can leave the space. /// NextLabel B asks for space to the next label C.. and so on. /// So this CircularLabel is looking for space. /// public void PlaceLabel(Boolean skip) { Double verticalOffset, horizontalOffset; Boolean placedTowardsTop; Boolean placedTowardsBottom; Boolean isPlaced = false; Int32 noOfIteration = 2; OverlapTypes overlapTypesBottom = CheckOverLap(SweepDirection.Clockwise, out verticalOffset, out horizontalOffset); if (overlapTypesBottom == OverlapTypes.Both) { placedTowardsTop = AskSpaceToPreviusLabel(this, verticalOffset / 2, noOfIteration); placedTowardsBottom = AskSpaceToNextLabel(this, verticalOffset / 2, noOfIteration); if (placedTowardsTop && placedTowardsBottom) isPlaced = true; else if (placedTowardsTop && !placedTowardsBottom) { placedTowardsTop = AskSpaceToPreviusLabel(this, verticalOffset / 2, noOfIteration); isPlaced = placedTowardsTop; } else if (!placedTowardsTop && placedTowardsBottom) { placedTowardsBottom = AskSpaceToNextLabel(this, verticalOffset / 2, noOfIteration); isPlaced = placedTowardsTop; } else isPlaced = false; } else { OverlapTypes overlapTypesTop = CheckOverLap(SweepDirection.Counterclockwise, out verticalOffset, out horizontalOffset); if (overlapTypesTop == OverlapTypes.Both) { placedTowardsBottom = AskSpaceToNextLabel(this, verticalOffset / 2, noOfIteration); placedTowardsTop = AskSpaceToPreviusLabel(this, verticalOffset / 2, noOfIteration); if (placedTowardsTop && placedTowardsBottom) isPlaced = true; else if (placedTowardsTop && !placedTowardsBottom) { placedTowardsTop = AskSpaceToNextLabel(this, verticalOffset / 2, noOfIteration); isPlaced = placedTowardsTop; } else if (!placedTowardsTop && placedTowardsBottom) { placedTowardsBottom = AskSpaceToPreviusLabel(this, verticalOffset / 2, noOfIteration); isPlaced = placedTowardsBottom; } else isPlaced = false; } else isPlaced = true; } if (isPlaced) { //ColorIt(Colors.Green); } else { //ColorIt(Colors.Red); if (skip) SkipLabel(); } } /// /// Ask for space to NextLabel if NextLabel can leave the space. /// NextLabel B asks for space to the next label C.. and so on /// /// CircularLabel looking for space to overcome the overlap private static Boolean AskSpaceToNextLabel(CircularLabel label, Double requiredVerticalOffset, Int32 noOfIteration) { Boolean retValue = true; Double gap = 0; if (label.IsLast) { if (label.Position.Y + label.Height < label.Boundary.Height && label.Boundary.Height - (label.Position.Y + label.Height) > requiredVerticalOffset) { retValue = label.RotateByVerticalOffset(requiredVerticalOffset, SweepDirection.Clockwise); return retValue; } else return false; } gap = VerticalSpcaeBetweenLabels(label, label.NextLabel); if (gap > requiredVerticalOffset) { retValue = label.RotateByVerticalOffset(requiredVerticalOffset, SweepDirection.Clockwise); return retValue; } else { retValue = AskSpaceToNextLabel(label.NextLabel, requiredVerticalOffset, noOfIteration); if (retValue) { retValue = label.RotateByVerticalOffset(requiredVerticalOffset, SweepDirection.Clockwise); return retValue; } else retValue = false; } return retValue; } /// /// Ask for space to PreviusLabel B if it can leave the space inorder to overcome the overlap. /// PreviusLabel B asks for space to the previous label C.. and so on /// /// CircularLabel looking for space to overcome the overlap private static Boolean AskSpaceToPreviusLabel(CircularLabel labelA, Double requiredVerticalOffset, Int32 noOfIteration) { Boolean retValue = true; Double gap = 0; if (labelA.IsFirst) { if (labelA.Position.Y > requiredVerticalOffset) { retValue = labelA.RotateByVerticalOffset(requiredVerticalOffset, SweepDirection.Counterclockwise); return retValue; } else return false; } gap = VerticalSpcaeBetweenLabels(labelA, labelA.PreviusLabel); if (gap > requiredVerticalOffset) { retValue =labelA.RotateByVerticalOffset(requiredVerticalOffset, SweepDirection.Counterclockwise); return retValue; } else { retValue = AskSpaceToPreviusLabel(labelA.PreviusLabel, requiredVerticalOffset, noOfIteration); if (retValue) { retValue = labelA.RotateByVerticalOffset(requiredVerticalOffset, SweepDirection.Counterclockwise); return retValue; } else retValue = false; } return retValue; } /// /// Calculate vertical space available between two CircularLabels /// /// CircularLabel A /// CircularLabel B /// vertical space available between two CircularLabels private static Double VerticalSpcaeBetweenLabels(CircularLabel labelA, CircularLabel labelB) { Double gap = 0; if (labelA.Position.Y < labelB.Position.Y) gap = labelB.Position.Y - (labelA.Position.Y + labelA.Height); else gap = labelA.Position.Y - (labelB.Position.Y + labelB.Height); if (gap < 0) return 0; else return gap; } /// /// Skip a CircularLabel /// Note: If a CircularLabel is skipped then it will be removed from the circular link-list. /// public void SkipLabel() { this.LabelVisual.Visibility = Visibility.Collapsed; IsSkiped = true; // Removed from the circular link-list. this.PreviusLabel.NextLabel = NextLabel; this.NextLabel.PreviusLabel = PreviusLabel; if (IsFirst) NextLabel.IsFirst = true; if (IsLast) PreviusLabel.IsLast = true; } /// /// Calculate angle by Y Coordinate /// /// /// public Double CalculateAngleByYCoordinate(Double y) { return Math.Asin((y - Center.Y) / YRadiusLabel); } /// /// Calculate angle by X Coordinate /// /// /// public Double CalculateAngleByXCoordinate(Double x) { return Math.Acos((x - Center.X) / XRadiusLabel); } /// /// Rotate the label towards certain degree clockwise or anticlockwise /// /// Angle in Radian /// SweepDirection public Boolean RotateByVerticalOffset(Double verticalOffset, System.Windows.Media.SweepDirection direction) { Double offsetAngle = Math.Atan(verticalOffset / YRadiusLabel); if (direction == SweepDirection.Counterclockwise) offsetAngle *= -1; Double currentMeanAngle = CurrentMeanAngle + offsetAngle; currentMeanAngle = ResetMeanAngle(currentMeanAngle); Point tempPosition = GetCartesianCoordinates4Labels(currentMeanAngle); if (Math.Abs(BaseMeanAngle - ResetMeanAngle(CurrentMeanAngle + offsetAngle)) > Math.PI / 5) return false; CurrentMeanAngle = currentMeanAngle; Position = tempPosition; return true; } /// /// Returns Cartation coordinates over circle /// /// Angle /// Cartation coordinates public Point GetCartesianCoordinates4Labels(Double meanAngle) { meanAngle = ResetMeanAngle(meanAngle); Double x = Center.X + XRadiusLabel * Math.Cos(meanAngle); Double y = Center.Y + YRadiusLabel * Math.Sin(meanAngle); return new Point(x, y); } /// /// Returns Cartation coordinates over circle /// /// Angle /// Cartation coordinates public Point GetCartesianCoordinates4Chart(Double meanAngle) { meanAngle = ResetMeanAngle(meanAngle); Double x = Center.X + XRadiusChart * Math.Cos(meanAngle); Double y = Center.Y + YRadiusChart * Math.Sin(meanAngle); return new Point(x, y); } /// /// Check whether two labels overlap. Also calculate the horizontal and vertical offsets /// to overcome the overlap. /// /// Information of the label /// How much need to move vertically to overcome overlap /// How much need to move horizontally to overcome overlap /// OverlapTypes private OverlapTypes CheckOverlap(CircularLabel label, out Double verticalOffset, out Double horizontalOffset) { OverlapTypes retValue = OverlapTypes.None; // Type of overlap verticalOffset = 0; // How much need to move vertically to overcome overlap horizontalOffset = 0; // How much need to move horizontally to overcome overlap CircularLabel A = this; CircularLabel B = label; if (B.Position.Y > A.Position.Y) { if (B.Position.Y < A.Position.Y + A.Height) { // Calculate how much need to move vertically to overcome overlap verticalOffset = A.Position.Y + A.Height - B.Position.Y; retValue = OverlapTypes.Vertical; } } else if (A.Position.Y > B.Position.Y) { if (A.Position.Y < B.Position.Y + B.Height) { // Calculate how much need to move vertically to overcome overlap verticalOffset = B.Position.Y + B.Height - A.Position.Y; retValue = OverlapTypes.Vertical; } } if (B.Position.X > A.Position.X) { if (B.Position.X < A.Position.X + A.Width) { // Calculate how much need to move horizontally to overcome overlap horizontalOffset = A.Position.X + A.Width - B.Position.X; retValue = (retValue == OverlapTypes.Vertical) ? OverlapTypes.Both : OverlapTypes.Horizontal; } } else if (A.Position.X > B.Position.X) { if (A.Position.X < B.Position.X + B.Width) { // Calculate how much need to move horizontally to overcome overlap horizontalOffset = B.Position.X + B.Width - A.Position.X; retValue = (retValue == OverlapTypes.Vertical) ? OverlapTypes.Both : OverlapTypes.Horizontal; } } return retValue; } private void CalculateMaxAndMinPosition() { // Calculate MaxPosition Double x1 = Center.X + XRadiusLabel * Math.Cos(ResetMeanAngle(BaseMeanAngle + Math.PI / 2)); Double y1 = Center.Y + YRadiusLabel * Math.Sin(ResetMeanAngle(BaseMeanAngle + Math.PI / 2)); Line l = new Line(); l.X1 = Center.X; l.Y1 = Center.Y; l.X2 = x1; l.Y2 = y1; Brush color = Graphics.GetRandonColor(); color.Opacity = 0.7; l.Stroke = color; l.StrokeThickness = 2; Visual.Children.Add(l); (LabelVisual as Canvas).Background = color; // Calculate MaxPosition Double x2 = Center.X + XRadiusLabel * Math.Cos(ResetMeanAngle(BaseMeanAngle - Math.PI / 2)); Double y2 = Center.Y + YRadiusLabel * Math.Sin(ResetMeanAngle(BaseMeanAngle - Math.PI / 2)); l = new Line(); l.X1 = Center.X; l.Y1 = Center.Y; l.X2 = x2; l.Y2 = y2; //l.Stroke = color; l.StrokeThickness = 1; Visual.Children.Add(l); MaxXPosition = Math.Max(x1, x2); MaxYPosition = Math.Max(y1, y2); MinXPosition = Math.Min(x1, x2); MinYPosition = Math.Min(y1, y2); } /// /// Reset the mean angle to 0 to 360 /// /// Mean angle /// New mean angle public static Double ResetMeanAngle(Double meanAngle) { if (meanAngle > Math.PI * 2) meanAngle -= Math.PI * 2; if (meanAngle < 0) meanAngle = Math.PI * 2 + meanAngle; return meanAngle; } public Boolean CheckOutOfBounds() { //Point position = GetCartesianCoordinates4Labels(BaseMeanAngle); //if (BaseMeanAngle >= 7 * Math.PI / 4 && BaseMeanAngle <= 0 || BaseMeanAngle >= 0 && BaseMeanAngle <= Math.PI / 4 // || BaseMeanAngle >= 3 * Math.PI / 4 && BaseMeanAngle <= 5 * Math.PI / 4) //{ // if (Math.Abs(position.X - Position.X) > XRadiusLabel) // return true; // if (Math.Abs(position.Y - Position.Y) > YRadiusLabel) // return true; //} //else //{ // if (Math.Abs(BaseMeanAngle - ResetMeanAngle(CurrentMeanAngle)) > Math.PI / 4) // return true; //} Double left = (Double)LabelVisual.GetValue(Canvas.LeftProperty); Double top = (Double)LabelVisual.GetValue(Canvas.TopProperty); Double labelLinePointX, labelLinePointY; if (left < Center.X) labelLinePointX = left + Width + LabelPlacementHelper.LABEL_LINE_GAP; else labelLinePointX = left - LabelPlacementHelper.LABEL_LINE_GAP; labelLinePointY = top + Height / 2; Point pointOverPie = GetCartesianCoordinates4Chart(BaseMeanAngle); Point labelLinePoint = new Point(labelLinePointX, labelLinePointY); Point intersectionPoint = Graphics.IntersectingPointOfTwoLines(Center, labelLinePoint, pointOverPie, new Point(labelLinePoint.X, Center.Y)); Double b = Graphics.DistanceBetweenTwoPoints(Center, intersectionPoint); Double angle = Math.Atan(b / XRadiusChart); angle = Math.PI / 2 - angle; if (angle > Math.PI / 2) return true; // Graphics.DrawPointAt(labelLinePoint, Visual, Colors.Red); return false; } /// /// Place the Label at x, y. /// /// X position /// Y position public void UpdateLabelVisualPosition() { Double x = Position.X, y = Position.Y; // For left side labels line should target to the right handside of the label if (x < Center.X) { if (x - LabelVisual.Width < 0) x = 0; else x = x - LabelVisual.Width; } // if (y + LabelVisual.Height > YRadiusLabel * 2) // y = YRadiusLabel * 2 - LabelVisual.Height; // if (y < 0) // y = 0; // Set the position LabelVisual.SetValue(Canvas.LeftProperty, x); LabelVisual.SetValue(Canvas.TopProperty, y); } /// /// /// /// internal void ColorIt(Color color) { (LabelVisual as Canvas).Background = new SolidColorBrush(color); } public Point Position { get; set; } public Double Height { get { return LabelVisual.Height; } } public Double Width { get { return LabelVisual.Width; } } public Double BaseMeanAngle; /// /// Current mean angle. /// Note: Current mean angle can go -Infinite to +Infinite /// public Double CurrentMeanAngle; public Point Center; public Double XRadiusLabel; public Double YRadiusLabel; public Double XRadiusChart; public Double YRadiusChart; private Canvas Visual; public FrameworkElement LabelVisual; public CircularLabel PreviusLabel; public CircularLabel NextLabel; public Boolean IsLast; public Boolean IsFirst; public Boolean IsSkiped; public Rect Boundary; public Double MaxXPosition; public Double MaxYPosition; public Double MinXPosition; public Double MinYPosition; } }