public static Desmos ToDesmos(this FrontChain frontChain) { // roughly estimate the size of the graph var top = 0; var bottom = 0; var right = 0; var left = 0; var desmos = new Desmos(); var id = 0; var current = frontChain.Head; do { var layout = current.Value; var expression = layout.ToString(); id++; desmos.Add(id.ToString(), expression); desmos.AddXY(layout.Center); left = (int)Math.Min(left, layout.Center.X - layout.Radius); right = (int)Math.Max(right, layout.Center.X + layout.Radius); top = (int)Math.Max(top, layout.Center.Y + layout.Radius); bottom = (int)Math.Min(bottom, layout.Center.Y - layout.Radius); current = current.Next; } while (current != frontChain.Head); desmos.SetBounds(top, left, bottom, right); return(desmos); }
private CircularLayoutInfo CalculateEnclosingCircle(IHierarchicalData root, FrontChain frontChain) { // Get extreme points by extending the vector from origin to center by the radius var layouts = frontChain.ToList(); var points = layouts.Select(x => Geometry.MovePointAlongLine(new Point(), x.Center, x.Radius)).ToList(); var layout = GetLayout(root); Debug.Assert(layout.Center == new Point()); // Since we process bottom up this node was not considered yet. double radius; Point center; if (frontChain.Head.Next == frontChain.Head) // Single element { center = frontChain.Head.Value.Center; radius = frontChain.Head.Value.Radius; } else { // 2 and 3 points seem to be handled correctly. Geometry.FindMinimalBoundingCircle(points, out center, out radius); } layout.Center = center; Debug.Assert(Math.Abs(radius) > 0.00001); layout.Radius = radius * 1.03; // Just a little larger 3%. return(layout); // Note that this is not exact. It is a difficult problem. // We may have a little overlap. The larger the increment in radius // the smaller this problem gets. }
/// <summary> /// Layouting of items starts with arranging the first three items around the center 0,0 /// Note that we do not set the radius. The radius was determined for the leaf nodes before. /// The non leaf nodes don't reflect the sum of the children (area metric!). /// </summary> private FrontChain InitializeFrontChain(List <IHierarchicalData> children) { var frontChain = new FrontChain(); var left = new CircularLayoutInfo(); var right = new CircularLayoutInfo(); var top = new CircularLayoutInfo(); IHierarchicalData child; if (children.Count >= 1) { // Left child = children[0]; left = GetLayout(child); // If child has children its origin is not (0,0) any more. So move this node to origin first. var displacement = -(Vector)left.Center + new Vector(-left.Radius, 0); left.Move(displacement); } if (children.Count >= 2) { // Right child = children[1]; right = GetLayout(child); // If child has children its origin is not (0,0) any more. So move this node to origin first. var displacement = -(Vector)right.Center + new Vector(right.Radius, 0); right.Move(displacement); } if (children.Count >= 3) { // Top child = children[2]; top = GetLayout(child); var solutions = Geometry.FindCircleCircleIntersections( left.Center, left.Radius + top.Radius, right.Center, right.Radius + top.Radius, out var solution1, out var solution2); // If not maybe you did not remove zero weights? Debug.Assert(solutions == 2); var solution = solution1.Y > solution2.Y ? solution1 : solution2; var displacement = -(Vector)top.Center + (Vector)solution; top.Move(displacement); } // This order makes a huge difference. // left -> right -> top did not work and leads to overlapping circles! frontChain.Add(left); frontChain.Add(top); frontChain.Add(right); return(frontChain); }
public static void WriteDebugOutput(FrontChain frontChain, string extra, params CircularLayoutInfo[] proposedSolutions) { var id = 1; var d = frontChain.ToDesmos(); foreach (var circle in proposedSolutions) { d.Add("solution" + id++, circle.ToString()); } if (extra == null) { d.Write($"d:\\circles_{_dbg:D3}.html"); } else { d.Write($"d:\\circles_{_dbg:D3}_{extra}.html"); } }
private Node FindOverlappingCircleOnFrontChain(FrontChain frontChain, CircularLayoutInfo tmpCircle) { return(frontChain.Find(node => IsOverlapping(node.Value, tmpCircle))); }
/// <summary> /// Usually we have two solutions for tangent circles. We have to decide which to use. /// Theoretically that is the one that is outside the front chain polygon. /// However there are many edge cases. So I do not have an exact mathematical solution to this problem. /// Therefore I use different heuristics. /// </summary> private CircularLayoutInfo SelectCircle(FrontChain frontChain, CircularLayoutInfo circle1, CircularLayoutInfo circle2) { var poly = frontChain.ToList().Select(x => x.Center).ToList(); Debug.Assert(IsPointValid(circle1.Center) || IsPointValid(circle2.Center)); // ---------------------------------------------------------------------------- // If exactly one of the two points is valid that is the solution. // ---------------------------------------------------------------------------- if (IsPointValid(circle1.Center) && !IsPointValid(circle2.Center)) { return(circle1); } if (!IsPointValid(circle1.Center) && IsPointValid(circle2.Center)) { return(circle2); } // We have two solutions. Which point to chose? // ---------------------------------------------------------------------------- // If one center is inside and one outside the polygon take the outside. // ---------------------------------------------------------------------------- var center1Inside = MathHelper.PointInPolygon(poly, circle1.Center.X, circle1.Center.Y); var center2Inside = MathHelper.PointInPolygon(poly, circle2.Center.X, circle2.Center.Y); if (center1Inside && !center2Inside) { return(circle2); } if (!center1Inside && center2Inside) { return(circle1); } // Both centers outside: Examples\Both_centers_outside_polygon.html. // Note that the purple circle (center) may also be inside the polygon if it was a little bit smaller. // Debug.Assert(center2Inside && center2Inside); // Both centers inside: Examples\Both_centers_inside_polygon.html // Happens when centers are on the polygon edges. // Debug.Assert(!center2Inside && !center2Inside); // ---------------------------------------------------------------------------- // If one circle is crossed by an polygon edge and the other is not, // take the other. // ---------------------------------------------------------------------------- var circle1HitByEdge = false; var circle2HitByEdge = false; var iter = frontChain.Head; while (iter != null) { var first = iter.Value; var second = iter.Next.Value; Point intersect1; Point intersect2; // Note we consider a line here, not a line segment var solutions = MathHelper.FindLineCircleIntersections(circle1.Center.X, circle1.Center.Y, circle1.Radius, first.Center, second.Center, out intersect1, out intersect2); if (solutions > 0) { circle1HitByEdge |= MathHelper.PointOnLineSegment(first.Center, second.Center, intersect1); } if (solutions > 1) { circle1HitByEdge |= MathHelper.PointOnLineSegment(first.Center, second.Center, intersect2); } solutions = MathHelper.FindLineCircleIntersections(circle2.Center.X, circle2.Center.Y, circle2.Radius, first.Center, second.Center, out intersect1, out intersect2); if (solutions > 0) { circle2HitByEdge |= MathHelper.PointOnLineSegment(first.Center, second.Center, intersect1); } if (solutions > 1) { circle2HitByEdge |= MathHelper.PointOnLineSegment(first.Center, second.Center, intersect2); } iter = iter.Next; if (iter == frontChain.Head) { // Ensure that the segment from tail to head is also processed. iter = null; } } if (circle1HitByEdge && !circle2HitByEdge) { return(circle2); } if (!circle1HitByEdge && circle2HitByEdge) { return(circle1); } if (DebugEnabled) { DebugHelper.WriteDebugOutput(frontChain, "not_sure_which_circle", circle1, circle2); } // Still inconclusive which solution to take. // I choose the one with the largst distance from the origin. // Background is following example: Inconclusive_solution.html // I need to get rid of the inner (green) circle If I want to grow outwards. // In my understanding this inner node is always m. We chose it because it had the smallest distance to the origin. // My hope is that the selected solution leads to removal of m. Cannot prove it, but I think so. return(SelectCircleWithLargerDistanceFromOrigin(circle1, circle2)); }