using System; using System.Collections.Generic; using System.Globalization; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Geometry.Curves; using Microsoft.Msagl.Core.Layout; namespace Microsoft.Msagl.Layout.Layered { /// /// Defines the anchors for a node; anchors can be not symmetrical in general /// /// |TopAnchor ///Left anchor| /// ======Origin==================RightAnchor /// | /// | /// |BottomAnchor /// #if TEST_MSAGL public #else internal #endif class Anchor { /// /// ToString /// /// public override string ToString() { return "la:ra " + la.ToString("#.##", CultureInfo.InvariantCulture) + " " + ra.ToString("#.##", CultureInfo.InvariantCulture) + " ta:ba " + ta.ToString("#.##", CultureInfo.InvariantCulture) + " " + ba.ToString("#.##", CultureInfo.InvariantCulture) + " x:y " + x.ToString("#.##", CultureInfo.InvariantCulture) + " " + y.ToString("#.##", CultureInfo.InvariantCulture); } double la; double ra; double ta; double ba; double labelCornersPreserveCoefficient; /// /// distance for the center of the node to its left boundary /// public double LeftAnchor { get { return la; } set { //the absence of this check allows a situation when an edge crosses its label or // a label which does not belong to the edge // if(value<-Curve.DistEps) // throw new Exception("assigning negative value to a anchor"); la = Math.Max(value, 0); ; } } /// /// distance from the center of the node to its right boundary /// public double RightAnchor { get { return ra; } set { // if(value<-Curve.DistEps) // throw new Exception("assigning negative value to a anchor: "+value ); ra = Math.Max(value, 0); } } /// /// distance from the center of the node to its top boundary /// public double TopAnchor { get { return ta; } set { //if(value<-Curve.DistEps) //throw new Exception("assigning negative value to a anchor"); ta = Math.Max(value, 0); } } /// /// distance from the center of the node to it bottom boundary /// public double BottomAnchor { get { return ba; } set { //if(value<-Curve.DistEps) //throw new InvalidOperationException();//"assigning negative value to a anchor"); ba = Math.Max(value, 0); } } /// /// Left boundary of the node /// public double Left { get { return x - la; } } /// /// right boundary of the node /// public double Right { get { return x + ra; } } /// /// top boundary of the node /// public double Top { get { return y + ta; } set { y += value - Top; } } /// /// bottom of the node /// public double Bottom { get { return y - ba; } set { y += value - Bottom; } } /// /// Left top corner /// public Point LeftTop { get { return new Point(Left, Top); } } /// /// Left bottom of the node /// public Point LeftBottom { get { return new Point(Left, Bottom); } } /// /// Right bottom of the node /// public Point RightBottom { get { return new Point(Right, Bottom); } } Node node; internal Node Node { get { return node; } set { node = value; this.polygonalBoundary = null; } } /// /// Right top of the node /// public Point RightTop { get { return new Point(Right, Top); } } /// /// an empty constructor /// public Anchor(double labelCornersPreserveCoefficient) { this.labelCornersPreserveCoefficient = labelCornersPreserveCoefficient; } /// /// constructor /// public Anchor(double leftAnchor, double rightAnchor, double topAnchor, double bottomAnchor, Node node, double labelCornersPreserveCoefficient) { la = leftAnchor; ra = rightAnchor; ta = topAnchor; ba = bottomAnchor; Node = node; this.labelCornersPreserveCoefficient = labelCornersPreserveCoefficient; } double x; /// /// the x position /// internal double X { get { return x; } set { polygonalBoundary = null; x = value; } } double y; /// /// the y position /// internal double Y { get { return y; } set { polygonalBoundary = null; y = value; } } /// /// Center of the node /// public Point Origin { get { return new Point(x, y); } } bool alreadySitsOnASpline; /// /// signals if the spline has been routed already through the node /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811")] public bool AlreadySitsOnASpline { get { return alreadySitsOnASpline; } set { alreadySitsOnASpline = value; } } /// /// node widths /// public double Width { get { return this.la + this.ra; } } /// /// node height /// public double Height { get { return this.ta + this.ba; } } /// /// set to true if the anchor has been introduced for a label /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811")] public bool RepresentsLabel { get { return LabelToTheRightOfAnchorCenter || LabelToTheLeftOfAnchorCenter; } } bool labelIsToTheLeftOfTheSpline; /// /// An anchor for an edge label with the label to the right of the spline has its height equal to the one of the label /// Its leftAnchor is a reserved space for the spline and the rightAnchor is equal to the label width. /// internal bool LabelToTheLeftOfAnchorCenter { get { return labelIsToTheLeftOfTheSpline; } set { labelIsToTheLeftOfTheSpline = value; } } bool labelIsToTheRightOfTheSpline; /// /// An anchor for an edge label with the label to the left of the spline has its height equal to the one of the label /// Its rightAnchor is a reserved space for the spline and the leftAnchor is equal to the label width. /// internal bool LabelToTheRightOfAnchorCenter { get { return labelIsToTheRightOfTheSpline; } set { labelIsToTheRightOfTheSpline = value; } } internal bool HasLabel { get { return LabelToTheRightOfAnchorCenter || LabelToTheLeftOfAnchorCenter; } } internal double LabelWidth { get { if (LabelToTheLeftOfAnchorCenter) return LeftAnchor; if (LabelToTheRightOfAnchorCenter) return RightAnchor; throw new InvalidOperationException(); } } Polyline polygonalBoundary; /// /// the polygon representing the boundary of a node /// #if TEST_MSAGL public #else internal #endif Polyline PolygonalBoundary { get { if (polygonalBoundary != null) return polygonalBoundary; return polygonalBoundary = Pad(CreatPolygonalBoundaryWithoutPadding(),Padding); } } static Polyline Pad(Polyline curve, double padding) { if (padding == 0) return curve; if (CurveIsConvex(curve)) { return PadConvexCurve(curve, padding); } else return PadConvexCurve(Curve.StandardRectBoundary(curve), padding); } static void PadCorner(Polyline poly, PolylinePoint p0, PolylinePoint p1, PolylinePoint p2, double padding) { Point a, b; int numberOfPoints=GetPaddedCorner(p0, p1, p2, out a, out b, padding); poly.AddPoint(a); if (numberOfPoints==2) poly.AddPoint(b); } static Polyline PadConvexCurve(Polyline poly, double padding) { Polyline ret = new Polyline(); PadCorner(ret, poly.EndPoint.Prev, poly.EndPoint, poly.StartPoint, padding); PadCorner(ret, poly.EndPoint, poly.StartPoint, poly.StartPoint.Next, padding); for (PolylinePoint pp = poly.StartPoint; pp.Next.Next != null; pp = pp.Next) PadCorner(ret, pp, pp.Next, pp.Next.Next, padding); ret.Closed = true; return ret; } /// /// /// /// /// /// /// /// /// /// number of new points static int GetPaddedCorner(PolylinePoint first, PolylinePoint second, PolylinePoint third, out Point a, out Point b, double padding) { Point u = first.Point; Point v = second.Point; Point w = third.Point; // System.Diagnostics.Debug.Assert(Point.GetTriangleOrientation(u, v, w) == TriangleOrientation.Clockwise); Point uvPerp = (v - u).Rotate(Math.PI / 2).Normalize(); ////uvPerp has to look outside of the curve //if (uvPerp * (v - Origin) < 0) // uvPerp *= -1; Point l = (v - u).Normalize() + (v - w).Normalize(); if (l.Length < ApproximateComparer.IntersectionEpsilon) { a = b = v + padding * uvPerp; return 1; } Point d = l.Normalize() * padding; Point dp = d.Rotate(Math.PI / 2); //look for a in the form d+x*dp //we have: Padding=(d+x*dp)*uvPerp double xp = (padding - d * uvPerp) / (dp * uvPerp); a = d + xp * dp + v; b = d - xp * dp + v; return 2; //number of points to add } static bool CurveIsConvex(Polyline poly) { TriangleOrientation orientation = Point.GetTriangleOrientation(poly.EndPoint.Point, poly.StartPoint.Point, poly.StartPoint.Next.Point); if (Point.GetTriangleOrientation(poly.EndPoint.Prev.Point, poly.EndPoint.Point, poly.StartPoint.Point) != orientation) return false; PolylinePoint pp = poly.StartPoint; while (pp.Next.Next != null) { if (Point.GetTriangleOrientation(pp.Point, pp.Next.Point, pp.Next.Next.Point) != orientation) return false; pp = pp.Next; } return true; } //private static double TurnAfterSeg(Curve curve, int i) { // return Point.SignedDoubledTriangleArea(curve.Segments[i].Start, curve.Segments[i].End, curve.Segments[(i + 1) / curve.Segments.Count].End); //} private Polyline CreatPolygonalBoundaryWithoutPadding() { Polyline ret; if (this.HasLabel) ret = LabelToTheLeftOfAnchorCenter ? PolygonOnLeftLabel() : PolygonOnRightLabel(); else if (this.NodeBoundary == null) ret = StandardRectBoundary(); else ret = Curve.PolylineAroundClosedCurve(this.NodeBoundary); return ret; } private Polyline StandardRectBoundary() { Polyline poly = new Polyline(); poly.AddPoint(LeftTop); poly.AddPoint(RightTop); poly.AddPoint(RightBottom); poly.AddPoint(LeftBottom); poly.Closed = true; return poly; } private Polyline PolygonOnLeftLabel() { Polyline poly = new Polyline(); double t = Left + (1 - this.labelCornersPreserveCoefficient) * LabelWidth; poly.AddPoint(new Point(t, Top)); poly.AddPoint(RightTop); poly.AddPoint(RightBottom); poly.AddPoint(new Point(t, Bottom)); poly.AddPoint(new Point(Left, Y)); poly.Closed = true; return poly; } private Polyline PolygonOnRightLabel() { Polyline poly = new Polyline(); double t = Right - (1 - this.labelCornersPreserveCoefficient) * LabelWidth; poly.AddPoint(t, Top); poly.AddPoint(Right, Y); poly.AddPoint(t, Bottom); poly.AddPoint(Left, Bottom); poly.AddPoint(Left, Top); poly.Closed = true; return poly; } internal ICurve NodeBoundary { get { return Node!=null?Node.BoundaryCurve:null; } } double padding; /// /// node padding /// public double Padding { get { return padding; } set { padding = value; } } internal void Move(Point p){ this.X += p.X; this.Y += p.Y; } #if TEST_MSAGL /// /// UserData /// public object UserData { get; set; } #endif } }