using System; using System.Collections.Generic; using System.Drawing; using DevComponents.DotNetBar.Charts.Style; namespace DevComponents.DotNetBar.Charts { class FlockAlign { #region Constants private const long RemovePenalty = 1; private const int MovesPerIteration = 500; #endregion #region Private variables private List _LabelGroups; #endregion public FlockAlign(List labelGroups) { _LabelGroups = labelGroups; foreach (PointLabelGroup lg in _LabelGroups) { foreach (PointLabel pl in lg.PointLabels) pl.Bounds = Rectangle.Empty; } } #region Iterate public bool Iterate(Rectangle bounds, DataLabelOverlapMode ovlMode) { if (ovlMode == DataLabelOverlapMode.NotSet) ovlMode = DataLabelOverlapMode.RotateAroundPoint; for (int i = 0; i < _LabelGroups.Count; i++) { PointLabelGroup lg = _LabelGroups[i]; List lps = lg.PointLabels; ChartSeries series = lg.ChartSeries; Point lp = Point.Empty; for (int j = 0; j < lps.Count; j++) { PointLabel pl = lps[j]; if (pl.Visible == true ) { DataLabelVisualStyle dstyle = pl.DataLabelVisualStyle ?? series.EffectiveDataLabelStyle; if (series.IsBarSeries == true) { if (series.IsRotated == true) IterateHBar(series, pl, bounds, ovlMode, dstyle); else IterateVBar(series, pl, bounds, ovlMode, dstyle); } else { IteratePoint(series, pl, bounds, lp, ovlMode, dstyle); } } lp = pl.Point; } } return (true); } #region IterateHBar private void IterateHBar(ChartSeries series, PointLabel pl, Rectangle bounds, DataLabelOverlapMode ovlMode, DataLabelVisualStyle dstyle) { ChartXy chartXy = series.Parent as ChartXy; int start = (pl.IsDataLabel == true) ? pl.Point.X - 1 : series.GetHBarStart(chartXy, pl.SeriesPoint); int end = pl.Point.X; BarLabelPosition labelPos = pl.BarLabelPosition; if (labelPos == BarLabelPosition.NotSet) labelPos = series.GetBarLabelPosition(chartXy); switch (labelPos) { case BarLabelPosition.Near: SetHBarLabelNear(series, pl, bounds, ovlMode, dstyle, start, end, false); break; case BarLabelPosition.NearInside: SetHBarLabelNear(series, pl, bounds, ovlMode, dstyle, start, end, true); break; case BarLabelPosition.Far: SetHBarLabelFar(series, pl, bounds, ovlMode, dstyle, start, end, false); break; case BarLabelPosition.FarInside: SetHBarLabelFar(series, pl, bounds, ovlMode, dstyle, start, end, true); break; default: SetHBarLabelCenter(series, pl, bounds, ovlMode, dstyle, start, end); break; } } #region SetHBarLabelNear private void SetHBarLabelNear(ChartSeries series, PointLabel pl, Rectangle bounds, DataLabelOverlapMode ovlMode, DataLabelVisualStyle dstyle, int start, int end, bool inside) { ConnectorLineVisualStyle cstyle = dstyle.ConnectorLineStyle; Point pt = new Point(start, pl.Point.Y + series.BarOffset); Size size = GetBoundsSize(series, pl, dstyle); Rectangle t = Rectangle.Empty; int minLength = Dpi.Width(cstyle.MinLength); for (int i = 0; i < 2; i++) { int clen = (inside == false && minLength > 0) ? minLength + Dpi.Width2 : Dpi.Width4; int step = cstyle.LengthStep; int offset = (size.Width / 2 + clen); if (inside ? end < start : end > start) { step *= -1; offset *= -1; } Point ptc = pt; ptc.X += offset; t = GetCenteredRectangle(series, ptc, size); while (step < 0 ? t.X > bounds.X : t.Right < bounds.Right) { if (SetHBarLabelPos(pl, ovlMode, bounds, ptc, t, start, end) == true) return; if (ovlMode != DataLabelOverlapMode.RotateAroundPoint) break; t.X += step; if (cstyle.MaxLength > 0) { if (Math.Abs(t.X - ptc.X) > cstyle.MaxLength) break; } } inside = !inside; } SetHBarDefaultLabelPos(series, ref pl, pt, t, ovlMode, start, end); } #endregion #region SetHBarLabelFar private void SetHBarLabelFar(ChartSeries series, PointLabel pl, Rectangle bounds, DataLabelOverlapMode ovlMode, DataLabelVisualStyle dstyle, int start, int end, bool inside) { ConnectorLineVisualStyle cstyle = dstyle.ConnectorLineStyle; Point pt = new Point(end, pl.Point.Y + series.BarOffset); Size size = GetBoundsSize(series, pl, dstyle); Rectangle t = Rectangle.Empty; int minLength = Dpi.Width(cstyle.MinLength); for (int i = 0; i < 2; i++) { int clen = (inside == false && minLength >= 0) ? minLength + Dpi.Width2 : Dpi.Width4; int step = cstyle.LengthStep; int offset = (size.Width / 2 + clen); if (inside ? end > start : end < start) { step *= -1; offset *= -1; } Point ptc = pt; ptc.X += offset; t = GetCenteredRectangle(series, ptc, size); while (step < 0 ? t.X > bounds.X : t.Right < bounds.Right) { if (SetHBarLabelPos(pl, ovlMode, bounds, ptc, t, start, end) == true) return; if (ovlMode != DataLabelOverlapMode.RotateAroundPoint) break; t.X += step; if (cstyle.MaxLength > 0) { if (Math.Abs(t.X - ptc.X) > cstyle.MaxLength) break; } } inside = !inside; } SetHBarDefaultLabelPos(series, ref pl, pt, t, ovlMode, start, end); } #endregion #region SetHBarLabelCenter private void SetHBarLabelCenter(ChartSeries series, PointLabel pl, Rectangle bounds, DataLabelOverlapMode ovlMode, DataLabelVisualStyle dstyle, int start, int end) { Point pt = new Point((end + start) / 2, pl.Point.Y + series.BarOffset); int pass = 0; int step = 2; int startx = pt.X; Size size = GetBoundsSize(series, pl, dstyle); Rectangle t = GetCenteredRectangle(series, pt, size); int len = (end + start) / 2; while (pass <= len) { if (SetHBarLabelPos(pl, ovlMode, bounds, pt, t, start, end) == true) return; if (ovlMode != DataLabelOverlapMode.RotateAroundPoint) break; int pval = pass / 2 + 1; if (pass % 2 > 0) pval = -pval; t.X = (startx + (pval * step)); pass++; } SetHBarDefaultLabelPos(series, ref pl, pt, t, ovlMode, start, end); } #endregion #region SetHBarLabelPos private bool SetHBarLabelPos(PointLabel pl, DataLabelOverlapMode ovlMode, Rectangle bounds, Point pt, Rectangle t, int start, int end) { if (IsFreeArea(t, bounds, ovlMode, false, true) == true) { pl.Bounds = t; SetHBarEdgePoint(pl, pt, t, start, end); return (true); } if (ovlMode == DataLabelOverlapMode.HideOverlapping) return (true); return (false); } #endregion #region SetHBarDefaultLabelPos private void SetHBarDefaultLabelPos(ChartSeries series, ref PointLabel pl, Point pt, Rectangle t, DataLabelOverlapMode ovlMode, int start, int end) { if (ovlMode == DataLabelOverlapMode.RotateAroundPoint) { if (t.X < start) t.X = start + Dpi.Width4; else t.X = end - (t.Width + Dpi.Width4); ChartXy chartXy = series.Parent as ChartXy; Rectangle bounds = chartXy.ContentBoundsEx; if (t.Right > bounds.Right) t.X -= (t.Right - bounds.Right + Dpi.Width4); if (t.X < bounds.X) t.X = bounds.X + Dpi.Width4; pl.Bounds = t; SetHBarEdgePoint(pl, pt, t, start, end); } else { pl.Bounds = Rectangle.Empty; pl.EdgePoint = Point.Empty; } } #endregion #region SetHBarEdgePoint private void SetHBarEdgePoint(PointLabel pl, Point pt, Rectangle t, int start, int end) { if (t.X > start && t.X > end) { pt.X = Math.Max(start, end); pl.EdgePoint = new Point(t.X, pt.Y); } else if (t.X < start && t.X < end) { pt.X = Math.Min(start, end); pl.EdgePoint = new Point(t.Right, pt.Y); } else { pl.EdgePoint = Point.Empty; } pl.Point = pt; } #endregion #endregion #region IterateVBar private void IterateVBar(ChartSeries series, PointLabel pl, Rectangle bounds, DataLabelOverlapMode ovlMode, DataLabelVisualStyle dstyle) { ChartXy chartXy = series.Parent as ChartXy; int start = series.GetVBarStart(chartXy, pl.SeriesPoint); int end = pl.Point.Y; BarLabelPosition labelPos = series.GetBarLabelPosition(chartXy); switch (labelPos) { case BarLabelPosition.Near: SetVBarLabelNear(series, pl, bounds, ovlMode, dstyle, start, end, false); break; case BarLabelPosition.NearInside: SetVBarLabelNear(series, pl, bounds, ovlMode, dstyle, start, end, true); break; case BarLabelPosition.Far: SetVBarLabelFar(series, pl, bounds, ovlMode, dstyle, start, end, false); break; case BarLabelPosition.FarInside: SetVBarLabelFar(series, pl, bounds, ovlMode, dstyle, start, end, true); break; default: SetVBarLabelCenter(series, pl, bounds, ovlMode, dstyle, start, end); break; } } #region SetVBarLabelNear private void SetVBarLabelNear(ChartSeries series, PointLabel pl, Rectangle bounds, DataLabelOverlapMode ovlMode, DataLabelVisualStyle dstyle, int start, int end, bool inside) { ConnectorLineVisualStyle cstyle = dstyle.ConnectorLineStyle; Point pt = new Point(pl.Point.X + series.BarOffset, start); Size size = GetBoundsSize(series, pl, dstyle); int minLength = Dpi.Width(cstyle.MinLength); int clen = (inside == false && minLength >= 0) ? minLength + Dpi.Width2 : Dpi.Width4; int step = cstyle.LengthStep; int offset = (size.Height / 2 + clen); Rectangle t = Rectangle.Empty; for (int i = 0; i < 2; i++) { if (inside ? end < start : end > start) { step *= -1; offset *= -1; } Point ptc = pt; ptc.Y += offset; t = GetCenteredRectangle(series, ptc, size); while (t.Y < bounds.Bottom) { if (SetVBarLabelPos(pl, ovlMode, bounds, ptc, t, start, end) == true) return; if (ovlMode != DataLabelOverlapMode.RotateAroundPoint) break; t.Y += step; if (cstyle.MaxLength > 0) { if (Math.Abs(t.Y - ptc.Y) > cstyle.MaxLength) break; } } inside = !inside; } SetVBarDefaultLabelPos(series, ref pl, pt, t, ovlMode, start, end); } #endregion #region SetVBarLabelFar private void SetVBarLabelFar(ChartSeries series, PointLabel pl, Rectangle bounds, DataLabelOverlapMode ovlMode, DataLabelVisualStyle dstyle, int start, int end, bool inside) { ConnectorLineVisualStyle cstyle = dstyle.ConnectorLineStyle; Point pt = new Point(pl.Point.X + series.BarOffset, end); Size size = GetBoundsSize(series, pl, dstyle); int minLength = Dpi.Width(cstyle.MinLength); Rectangle t = Rectangle.Empty; for (int i = 0; i < 2; i++) { int clen = (inside == false && minLength >= 0) ? minLength + Dpi.Width2 : Dpi.Width4; int step = cstyle.LengthStep; int offset = (size.Height / 2 + clen); if (inside ? end > start : end < start) { step *= -1; offset *= -1; } Point ptc = pt; ptc.Y += offset; t = GetCenteredRectangle(series, ptc, size); while (step < 0 ? t.Y > bounds.Y : t.Y < bounds.Bottom) { if (SetVBarLabelPos(pl, ovlMode, bounds, ptc, t, start, end) == true) return; if (ovlMode != DataLabelOverlapMode.RotateAroundPoint) break; t.Y += step; if (cstyle.MaxLength > 0) { if (Math.Abs(t.Y - ptc.Y) > cstyle.MaxLength) break; } } inside = !inside; } SetVBarDefaultLabelPos(series, ref pl, pt, t, ovlMode, start, end); } #endregion #region SetVBarLabelCenter private void SetVBarLabelCenter(ChartSeries series, PointLabel pl, Rectangle bounds, DataLabelOverlapMode ovlMode, DataLabelVisualStyle dstyle, int origin, int end) { Point pt = new Point(pl.Point.X + series.BarOffset, (end + origin) / 2); int pass = 0; int step = 2; int starty = pt.Y; Size size = GetBoundsSize(series, pl, dstyle); Rectangle t = GetCenteredRectangle(series, pt, size); int len = (end + origin) / 2; while (pass <= len) { if (SetVBarLabelPos(pl, ovlMode, bounds, pt, t, origin, end) == true) return; if (ovlMode != DataLabelOverlapMode.RotateAroundPoint) break; int pval = pass / 2 + 1; if (pass % 2 > 0) pval = -pval; t.Y = (starty + (pval * step)); pass++; } SetVBarDefaultLabelPos(series, ref pl, pt, t, ovlMode, origin, end); } #endregion #region SetVBarLabelPos private bool SetVBarLabelPos(PointLabel pl, DataLabelOverlapMode ovlMode, Rectangle bounds, Point pt, Rectangle t, int start, int end) { if (IsFreeArea(t, bounds, ovlMode, false, true) == true) { pl.Bounds = t; SetVBarEdgePoint(pl, pt, t, start, end); return (true); } if (ovlMode == DataLabelOverlapMode.HideOverlapping) return (true); return (false); } #endregion #region SetVBarDefaultLabelPos private void SetVBarDefaultLabelPos(ChartSeries series, ref PointLabel pl, Point pt, Rectangle t, DataLabelOverlapMode ovlMode, int start, int end) { if (ovlMode == DataLabelOverlapMode.RotateAroundPoint) { if (t.Y > start) t.Y = start - (t.Height + Dpi.Height4); else t.Y = end + Dpi.Height4; ChartXy chartXy = series.Parent as ChartXy; Rectangle bounds = chartXy.ContentBoundsEx; if (t.Y > bounds.Bottom) t.Y -= (t.Bottom - bounds.Bottom + Dpi.Height4); if (t.Y < bounds.Y) t.Y = bounds.Y + Dpi.Height4; pl.Bounds = t; SetVBarEdgePoint(pl, pt, t, start, end); } else { pl.Bounds = Rectangle.Empty; pl.EdgePoint = Point.Empty; } } #endregion #region SetVBarEdgePoint private void SetVBarEdgePoint(PointLabel pl, Point pt, Rectangle t, int start, int end) { if (t.Y > start && t.Y > end) { pt.Y = Math.Max(start, end); pl.EdgePoint = new Point(pt.X, t.Y); } else if (t.Y < start && t.Y < end) { pt.Y = Math.Min(start, end); pl.EdgePoint = new Point(pt.X, t.Bottom); } else { pl.EdgePoint = Point.Empty; } pl.Point = pt; } #endregion #endregion #region IteratePoint private void IteratePoint(ChartSeries series, PointLabel pl, Rectangle bounds, Point lp, DataLabelOverlapMode ovlMode, DataLabelVisualStyle dstyle) { if ((dstyle.CenterLabel != Tbool.True) || SetLabelCenter(series, pl, bounds, ovlMode, dstyle) == false) { ConnectorLineVisualStyle cstyle = dstyle.ConnectorLineStyle; int step; int startAngle = GetStartAngle(lp, pl, cstyle, out step); int offset = 0; if (cstyle.Origin == ConnectorOrigin.Edge) { offset = Math.Max( pl.SeriesPoint.PointSize.Width, pl.SeriesPoint.PointSize.Height) / 2; } SetLabel(series, pl, startAngle, step, cstyle.MinLength + offset, cstyle.MaxLength + offset, ovlMode, bounds, dstyle); } } #endregion #region GetBoundsSize private Size GetBoundsSize( ChartSeries series, PointLabel pl, DataLabelVisualStyle dstyle) { RotateDegrees rotate = series.GetRotateDegrees(dstyle); Size size = (rotate == RotateDegrees.Rotate90 || rotate == RotateDegrees.Rotate270) ? new Size(pl.LabelSize.Height, pl.LabelSize.Width) : pl.LabelSize; if (dstyle.HasBorder == true) { size.Width += (dstyle.BorderThickness << 1); size.Height += (dstyle.BorderThickness << 1); } size.Width += dstyle.Padding.Horizontal; size.Height += dstyle.Padding.Vertical; return (size); } #endregion #region SetLabelCenter private bool SetLabelCenter(ChartSeries series, PointLabel pl, Rectangle bounds, DataLabelOverlapMode ovlMode, DataLabelVisualStyle dstyle) { Rectangle t = GetCenteredRectangle(series, pl.Point, pl.LabelSize); if (IsFreeArea(t, bounds, ovlMode, true, true) == true) { pl.Bounds = t; pl.EdgePoint = new Point(t.Right, t.Bottom); return (true); } if (ovlMode == DataLabelOverlapMode.HideOverlapping) return (true); return (false); } #endregion #region SetLabel private bool SetLabel(ChartSeries series, PointLabel pl, int startAngle, int step, int radius, int maxRadius, DataLabelOverlapMode ovlMode, Rectangle bounds, DataLabelVisualStyle dstyle) { int angle = startAngle; int pass = 0; radius = Dpi.Width(radius); maxRadius = Dpi.Width(maxRadius); while (radius <= maxRadius) { Point calcPoint; Rectangle r = GetAreaRectangle(series, pl, angle, radius, out calcPoint, dstyle); if (IsFreeArea(r, bounds, ovlMode, true, true) == true) { pl.Angle = angle; pl.Bounds = r; pl.EdgePoint = calcPoint; return (true); } if (ovlMode != DataLabelOverlapMode.RotateAroundPoint) break; int pval = pass / 2 + 1; if (pass % 2 > 0) pval = -pval; angle = (startAngle + (pval * step)) % 360; pass++; if (pass * step >= 360) { radius += Dpi.Width15; pass = 0; } } pl.Bounds = Rectangle.Empty; pl.EdgePoint = Point.Empty; return (false); } #endregion #region GetStartAngle private int GetStartAngle(Point lp, PointLabel pl, ConnectorLineVisualStyle cstyle, out int step) { step = cstyle.AngleStep; if (cstyle.DefaultAngle >= 0) return (cstyle.DefaultAngle); Point pt = pl.Point; int rise = lp.Y - pt.Y; int run = lp.X - pt.X; double slope = (run == 0) ? 0 : (double)rise / run; return ((slope < 1) ? 270 : (slope > 0) ? 315 : 225); } #endregion #region GetCenteredRectangle private Rectangle GetCenteredRectangle( ChartSeries series, Point pt, Size labelSize) { Rectangle r = new Rectangle(pt, labelSize); r.X -= (r.Width / 2); r.Y -= (r.Height / 2); return (r); } #endregion #region GetAreaRectangle private Rectangle GetAreaRectangle(ChartSeries series, PointLabel pl, int angle, int radius, out Point calcPoint, DataLabelVisualStyle dstyle) { Rectangle r = new Rectangle(pl.Point, pl.LabelSize); RotateDegrees rotate = series.GetRotateDegrees(dstyle); if (rotate == RotateDegrees.Rotate90 || rotate == RotateDegrees.Rotate270) { r.Width = pl.LabelSize.Height; r.Height = pl.LabelSize.Width; } if (dstyle.HasBorder == true) { int n = Dpi.Width(dstyle.BorderThickness) << 1; r.Width += n; r.Height += n; } r.Width += Dpi.Width(dstyle.Padding.Horizontal); r.Height += Dpi.Height(dstyle.Padding.Vertical); r.X += (int)(radius * Math.Cos(MathHelper.ToRadians(angle))); r.Y += (int)(radius * Math.Sin(MathHelper.ToRadians(angle))); calcPoint = r.Location; return (OffsetAreaRectangle(r, angle, ref calcPoint, dstyle)); } #region OffsetAreaRectangle private Rectangle OffsetAreaRectangle(Rectangle r, int angle, ref Point calcPoint, DataLabelVisualStyle dstyle) { if (angle == 0) { r.Y -= (r.Height / 2); } else if (angle == 90) { r.X -= (r.Width / 2); } else if (angle == 180) { r.X -= r.Width; r.Y -= (r.Height / 2); } else if (angle == 270) { r.X -= (r.Width / 2); r.Y -= r.Height; } else if (angle < 90) { } else if (angle < 180) { r.X -= r.Width; } else if (angle < 270) { r.X -= r.Width; r.Y -= r.Height; } else { r.Y -= r.Height; } return (r); } #endregion #endregion #region IsFreeArea private bool IsFreeArea( Rectangle r, Rectangle bounds, DataLabelOverlapMode ovlMode, bool xos, bool yos) { if (xos == true) { if (r.X < bounds.X || r.Right > bounds.Right) return (false); } if (yos == true) { if (r.Y < bounds.Y || r.Bottom > bounds.Bottom) return (false); } if (ovlMode == DataLabelOverlapMode.ShowOverlapping) return (true); r.Inflate(3, 3); for (int i = 0; i < _LabelGroups.Count; i++) { List lps = _LabelGroups[i].PointLabels; for (int j = 0; j < lps.Count; j++) { PointLabel pl = lps[j]; if (pl.Bounds.IsEmpty == false) { if (pl.Bounds.IntersectsWith(r) == true) return (false); } } } return (true); } #endregion #endregion } }