/// <summary> /// Fix the ports for <see cref="RoutingMode.ShortestStraightPathToBorder"/> /// by enlarging the adjacent segment to the rotated layout. /// </summary> /// <param name="graph">The layout graph to work on.</param> /// <param name="edge">The edge to fix.</param> /// <param name="path">A <see cref="GeneralPath"/> which represents the rotated layout.</param> /// <param name="atSource">Whether to fix the source or target port of the edge.</param> private static void FixPorts(LayoutGraph graph, Edge edge, GeneralPath path, bool atSource) { var el = graph.GetLayout(edge); var pointCount = el.PointCount(); // find the opposite point of the port at the adjacent segment PointD firstBend = atSource ? (pointCount > 0 ? el.GetPoint(0) : graph.GetTargetPointAbs(edge)).ToPointD() : (pointCount > 0 ? el.GetPoint(pointCount - 1) : graph.GetSourcePointAbs(edge)).ToPointD(); // The port itself PointD port = (atSource ? graph.GetSourcePointAbs(edge) : graph.GetTargetPointAbs(edge)).ToPointD(); // The adjacent segment as vector pointing from the opposite point to the port var direction = port - firstBend; // find the intersection (there is always one) var intersection = path.FindRayIntersection(firstBend.ToPointD(), direction); PointD point = port; if (intersection < Double.PositiveInfinity) { // found an intersection: extend the adjacent segment point = firstBend + (direction * intersection); } else { // no intersection: connect to the original port's nearest point var cursor = path.CreateCursor(); double minDistance = Double.PositiveInfinity; while (cursor.MoveNext()) { var distance = port.DistanceTo(cursor.CurrentEndPoint); if (distance < minDistance) { minDistance = distance; point = cursor.CurrentEndPoint; } } } // set the port position if (atSource) { graph.SetSourcePointAbs(edge, point.ToYPoint()); } else { graph.SetTargetPointAbs(edge, point.ToYPoint()); } }
protected void PaintEdge(Graphics g, LayoutGraph graph, Edge e) { IEdgeLayout el = graph.GetLayout(e); YPoint sp = graph.GetSourcePointAbs(e); YPoint tp = graph.GetTargetPointAbs(e); PointF[] points = new PointF[el.PointCount() + 2]; points[0] = new PointF((float)sp.X, (float)sp.Y); points[el.PointCount() + 1] = new PointF((float)tp.X, (float)tp.Y); for (int i = 0; i < el.PointCount(); i++) { YPoint p = el.GetPoint(i); points[i + 1] = new PointF((float)p.X, (float)p.Y); } g.DrawLines(edgePen, points); }
/// <summary> /// Called by the various edge creation callbacks to create an edge in the resulting graph view /// that corresponds to the provided <paramref name="layoutEdge"/>. /// </summary> /// <remarks> /// If a model edge is provided, the edge will be created between the copies of the corresponding /// source/target ports. /// </remarks> ///<param name="pageLayoutGraph">The layout graph representing the current page.</param> ///<param name="pageView">The <see cref="IGraph"/> that is built to show the multi-page layout in a graph canvas.</param> ///<param name="layoutEdge">The edge of the layout graph that should be copied.</param> ///<param name="modelEdge">The edge of the original input graph that corresponds to the <paramref name="layoutEdge"/> (may be <see langword="null"/>).</param> ///<param name="edgeDefaults"></param> ///<returns>The created edge</returns> /// <seealso cref="CreateConnectorEdge"/> /// <seealso cref="CreateNormalEdge"/> /// <seealso cref="CreateProxyEdge"/> /// <seealso cref="CreateProxyReferenceEdge"/> protected IEdge CreateEdgeCore(LayoutGraph pageLayoutGraph, IGraph pageView, Edge layoutEdge, IEdge modelEdge, IEdgeDefaults edgeDefaults) { IEdge viewEdge; if (modelEdge != null) { // if the edge has a model edge: create the copied edge between // the copies of its source and target ports IPort modelSourcePort = modelEdge.SourcePort; IPort modelTargetPort = modelEdge.TargetPort; IPort viewSourcePort = GetViewPort(modelSourcePort); IPort viewTargetPort = GetViewPort(modelTargetPort); IEdgeStyle style = (IEdgeStyle)(edgeDefaults.Style != NullEdgeStyle ? edgeDefaults.GetStyleInstance() : modelEdge.Style.Clone()); viewEdge = pageView.CreateEdge(viewSourcePort, viewTargetPort, style, modelEdge.Tag); } else { // otherwise create it between the copies of its source and target nodes INode viewSource = GetViewNode(layoutEdge.Source); INode viewTarget = GetViewNode(layoutEdge.Target); viewEdge = pageView.CreateEdge(viewSource, viewTarget); } // adjust the port location YPoint newSourcePortLocation = pageLayoutGraph.GetSourcePointAbs(layoutEdge); YPoint newTargetPortLocation = pageLayoutGraph.GetTargetPointAbs(layoutEdge); pageView.SetPortLocation(viewEdge.SourcePort, newSourcePortLocation.ToPointD()); pageView.SetPortLocation(viewEdge.TargetPort, newTargetPortLocation.ToPointD()); // and copy the bends IEdgeLayout edgeLayout = pageLayoutGraph.GetLayout(layoutEdge); for (int i = 0; i < edgeLayout.PointCount(); i++) { YPoint bendLocation = edgeLayout.GetPoint(i); pageView.AddBend(viewEdge, new PointD(bendLocation.X, bendLocation.Y), i); } return(viewEdge); }
public GraphCanvas(LayoutGraph graph) { this.RenderTransform = new TranslateTransform(_padding, _padding); var grouping = new GroupingSupport(graph); // Add all edges foreach (var edge in graph.Edges) { IEdgeLayout el = graph.GetLayout(edge); var l = new Polyline(); l.Stroke = Brushes.Black; l.Points.Add(new Point(graph.GetSourcePointAbs(edge).X, graph.GetSourcePointAbs(edge).Y)); for (int i = 0; i < el.PointCount(); i++) { Point p = new Point(el.GetPoint(i).X, el.GetPoint(i).Y); l.Points.Add(p); } l.Points.Add(new Point(graph.GetTargetPointAbs(edge).X, graph.GetTargetPointAbs(edge).Y)); this.Children.Add(l); // edge labels var edgeLabelLayout = graph.GetLabelLayout(edge); foreach (var labelLayout in edgeLabelLayout) { var orientedRectangle = labelLayout.LabelModel.GetLabelPlacement( labelLayout.BoundingBox, graph.GetLayout(edge), graph.GetLayout(edge.Source), graph.GetLayout(edge.Target), labelLayout.ModelParameter); this.Children.Add(GetPolygon(orientedRectangle)); } } // add all nodes foreach (var node in graph.Nodes) { INodeLayout nl = graph.GetLayout(node); Color color = grouping.IsGroupNode(node) ? Color.FromArgb(60, 255, 60, 0) : Color.FromArgb(255, 255, 255, 0); var rect = new Rectangle(); this.Children.Add(rect); rect.Stroke = new SolidColorBrush() { Color = Colors.Black }; rect.Fill = new SolidColorBrush() { Color = color }; rect.Width = nl.Width; rect.Height = nl.Height; Canvas.SetTop(rect, nl.Y); Canvas.SetLeft(rect, nl.X); // display the node index var text = new TextBlock() { Text = String.Empty + node.Index, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; this.Children.Add(text); text.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity)); Canvas.SetTop(text, nl.Y + nl.Height / 2 - text.DesiredSize.Height / 2); Canvas.SetLeft(text, nl.X + nl.Width / 2 - text.DesiredSize.Width / 2); } }
/// <summary> /// Executes the layout algorithm. /// </summary> /// <remarks> /// <para> /// Enlarges the node layout to fully encompass the rotated layout (the rotated layout's bounding box). /// If the <see cref="EdgeRoutingMode"/> is set to <see cref="RoutingMode.FixedPort"/> /// port constraints are created to keep the ports at their current location. /// Existing port constraints are adjusted to the rotation. /// </para> /// <para> /// Then, the <see cref="LayoutStageBase.CoreLayout"/> is executed. /// </para> /// <para> /// After the core layout the original node sizes are restored. /// If the <see cref="EdgeRoutingMode"/> is set to <see cref="RoutingMode.ShortestStraightPathToBorder"/> /// the last edge segment is extended from the bounding box to the rotated layout. /// </para> /// </remarks> public override void ApplyLayout(LayoutGraph graph) { if (CoreLayout == null) { return; } var boundsProvider = graph.GetDataProvider(RotatedNodeLayoutDpKey); if (boundsProvider == null) { // no provider: this stage adds nothing to the core layout CoreLayout.ApplyLayout(graph); return; } bool addedSourcePortConstraints = false; bool addedTargetPortContstraints = false; IDataMap sourcePortConstraints = (IDataMap)graph.GetDataProvider(PortConstraintKeys.SourcePortConstraintDpKey); IDataMap targetPortConstraints = (IDataMap)graph.GetDataProvider(PortConstraintKeys.TargetPortConstraintDpKey); if (EdgeRoutingMode == RoutingMode.FixedPort) { // Fixed port: create port constraints to keep the ports at position // in this case: create data providers if there are none yet if (sourcePortConstraints == null) { sourcePortConstraints = graph.CreateEdgeMap(); graph.AddDataProvider(PortConstraintKeys.SourcePortConstraintDpKey, sourcePortConstraints); addedSourcePortConstraints = true; } if (targetPortConstraints == null) { targetPortConstraints = graph.CreateEdgeMap(); graph.AddDataProvider(PortConstraintKeys.TargetPortConstraintDpKey, targetPortConstraints); addedTargetPortContstraints = true; } } try { var originalDimensions = new Dictionary <Node, OldDimensions>(); foreach (var node in graph.Nodes) { var nodeShape = (RotatedNodeShape)boundsProvider.Get(node); var orientedLayout = nodeShape != null ? nodeShape.OrientedLayout : null; var outline = nodeShape != null ? nodeShape.Outline : null; if (orientedLayout != null) { // if the current node is rotated: apply fixes // remember old layout and size var oldLayout = graph.GetLayout(node); var newLayout = orientedLayout.GetBounds().ToYRectangle(); var offset = new PointD(newLayout.X - oldLayout.X, newLayout.Y - oldLayout.Y); var originalSize = new SizeD(oldLayout.Width, oldLayout.Height); var oldDimensions = new OldDimensions { offset = offset, size = originalSize, outline = outline }; if (EdgeRoutingMode == RoutingMode.FixedPort) { // EdgeRoutingMode: FixedPort: keep the ports at their current location // The oriented layout's corners to find the best PortSide var tl = new PointD(orientedLayout.AnchorX + orientedLayout.UpX * orientedLayout.Height, orientedLayout.AnchorY + orientedLayout.UpY * orientedLayout.Height); var tr = new PointD(orientedLayout.AnchorX + orientedLayout.UpX * orientedLayout.Height - orientedLayout.UpY * orientedLayout.Width, orientedLayout.AnchorY + orientedLayout.UpY * orientedLayout.Height + orientedLayout.UpX * orientedLayout.Width); var bl = new PointD(orientedLayout.AnchorX, orientedLayout.AnchorY); var br = new PointD(orientedLayout.AnchorX - orientedLayout.UpY * orientedLayout.Width, orientedLayout.AnchorY + orientedLayout.UpX * orientedLayout.Width); // for each out edge foreach (var edge in node.OutEdges) { // create a strong port constraint for the side which is closest to the port location (without rotation) var constraint = sourcePortConstraints.Get(edge); if (constraint == null) { var point = graph.GetSourcePointAbs(edge).ToPointD(); var side = FindBestSide(point, bl, br, tl, tr); sourcePortConstraints.Set(edge, PortConstraint.Create(side, true)); } } foreach (var edge in node.InEdges) { // create a strong port constraint for the side which is closest to the port location (without rotation) var constraint = targetPortConstraints.Get(edge); if (constraint == null) { var point = graph.GetTargetPointAbs(edge).ToPointD(); var side = FindBestSide(point, bl, br, tl, tr); targetPortConstraints.Set(edge, PortConstraint.Create(side, true)); } } } // For source and target port constraints: fix the PortSide according to the rotation var angle = Math.Atan2(orientedLayout.UpY, orientedLayout.UpX); if (sourcePortConstraints != null) { foreach (var edge in node.OutEdges) { FixPortConstraintSide(sourcePortConstraints, edge, angle); } } if (targetPortConstraints != null) { foreach (var edge in node.InEdges) { FixPortConstraintSide(targetPortConstraints, edge, angle); } } // enlarge the node layout var position = new YPoint(newLayout.X, newLayout.Y); oldDimensions.location = position; originalDimensions.Add(node, oldDimensions); graph.SetLocation(node, position); graph.SetSize(node, newLayout); } } // =============================================================== CoreLayout.ApplyLayout(graph); // =============================================================== var groups = graph.GetDataProvider(GroupingKeys.GroupDpKey); foreach (var node in graph.Nodes) { if (groups != null && groups.GetBool(node)) { // groups don't need to be adjusted to their former size and location because their bounds are entirely // calculated by the layout algorithm and they are not rotated continue; } // for each node which has been corrected: undo the correction var oldDimensions = originalDimensions[node]; var offset = oldDimensions.offset; var originalSize = oldDimensions.size; var newLayout = graph.GetLayout(node); // create a general path representing the new roated layout var path = oldDimensions.outline; var transform = new Matrix2D(); transform.Translate(new PointD(newLayout.X - oldDimensions.location.X, newLayout.Y - oldDimensions.location.Y)); path.Transform(transform); // restore the original size graph.SetLocation(node, new YPoint(newLayout.X - offset.X, newLayout.Y - offset.Y)); graph.SetSize(node, originalSize.ToYDimension()); if (EdgeRoutingMode == RoutingMode.NoRouting) { // NoRouting still needs fix for self-loops foreach (var edge in node.Edges) { if (edge.SelfLoop) { FixPorts(graph, edge, path, false); FixPorts(graph, edge, path, true); } } continue; } if (EdgeRoutingMode != RoutingMode.ShortestStraightPathToBorder) { continue; } // enlarge the adjacent segment to the oriented rectangle (represented by the path) // handling in and out edges separately will automatically cause selfloops to be handled correctly foreach (var edge in node.InEdges) { FixPorts(graph, edge, path, false); } foreach (var edge in node.OutEdges) { FixPorts(graph, edge, path, true); } } } finally { // if data provider for the port constraints have been added // remove and dispose them if (addedSourcePortConstraints) { graph.RemoveDataProvider(PortConstraintKeys.SourcePortConstraintDpKey); graph.DisposeEdgeMap((IEdgeMap)sourcePortConstraints); } if (addedTargetPortContstraints) { graph.RemoveDataProvider(PortConstraintKeys.TargetPortConstraintDpKey); graph.DisposeEdgeMap((IEdgeMap)targetPortConstraints); } } }