/// <summary> /// Creates the initial sample graph. /// </summary> private void CreateSampleGraph() { IGraph graph = graphControl.Graph; INode node0 = graph.CreateNode(new RectD(180, 40, 30, 30)); INode node1 = graph.CreateNode(new RectD(260, 50, 30, 30)); INode node2 = graph.CreateNode(new RectD(284, 200, 30, 30)); INode node3 = graph.CreateNode(new RectD(350, 40, 30, 30)); IEdge edge0 = graph.CreateEdge(node1, node2); // Add some bends graph.AddBend(edge0, new PointD(350, 130)); graph.AddBend(edge0, new PointD(230, 170)); graph.CreateEdge(node1, node0); graph.CreateEdge(node1, node3); ILabel label0 = graph.AddLabel(edge0, "Edge Label"); ILabel label1 = graph.AddLabel(node1, "Node Label"); //////////////////////////////////////////////////// //////////////// New in this sample //////////////// //////////////////////////////////////////////////// CreateLabelTags(label0, label1); //////////////////////////////////////////////////// }
/// <summary> /// Creates the initial sample graph. /// </summary> private void CreateSampleGraph() { IGraph graph = graphControl.Graph; //////////////////////////////////////////////////// //////////////// New in this sample //////////////// //////////////////////////////////////////////////// INode node0 = graph.CreateNode(new RectD(180, 40, 30, 30)); node0.Tag = Colors.Green; INode node1 = graph.CreateNode(new RectD(260, 50, 30, 30)); INode node2 = graph.CreateNode(new RectD(284, 200, 30, 30)); INode node3 = graph.CreateNode(new RectD(350, 40, 30, 30), new MySimpleNodeStyle() { NodeColor = Colors.Yellow }); //////////////////////////////////////////////////// IEdge edge0 = graph.CreateEdge(node1, node2); // Add some bends graph.AddBend(edge0, new PointD(350, 130)); graph.AddBend(edge0, new PointD(230, 170)); graph.CreateEdge(node1, node0); graph.CreateEdge(node1, node3); ILabel label0 = graph.AddLabel(edge0, "Edge Label"); ILabel label1 = graph.AddLabel(node1, "Node Label"); }
public EdgeStylePanel() { IGraph graph = graphControl.Graph; graph.NodeDefaults.Style = VoidNodeStyle.Instance; this.dummyEdge = graph.CreateEdge( graph.CreateNode(new RectD(10, 10, 0, 0)), graph.CreateNode(new RectD(50, 30, 0, 0))); graph.AddBend(dummyEdge, new PointD(30, 10), 0); graph.AddBend(dummyEdge, new PointD(30, 30), 1); graphControl.ContentRect = new RectD(5, 5, 50, 30); }
/// <summary> /// Creates the sample graph of this demo. /// </summary> private void CreateSampleGraph(IGraph graph) { CreateSubgraph(graph, Colors.Firebrick, 0, false); CreateSubgraph(graph, Colors.Green, 110, false); CreateSubgraph(graph, Colors.Purple, 220, true); CreateSubgraph(graph, Colors.Orange, 330, false); // The blue edge has more bends than the other edges var blueEdge = CreateSubgraph(graph, Colors.RoyalBlue, 440, false); var blueBends = blueEdge.Bends.ToArray(); graph.Remove(blueBends[1]); graph.Remove(blueBends[0]); var sourcePortLocation = blueEdge.SourcePort.GetLocation(); var targetPortLocation = blueEdge.TargetPort.GetLocation(); graph.AddBend(blueEdge, new PointD(220, sourcePortLocation.Y - 30)); graph.AddBend(blueEdge, new PointD(300, sourcePortLocation.Y - 30)); graph.AddBend(blueEdge, new PointD(300, targetPortLocation.Y + 30)); graph.AddBend(blueEdge, new PointD(380, targetPortLocation.Y + 30)); }
/// <summary> /// Creates a new bend at the given location. If this bend is on the first or last segment, /// a second bend is created and placed at a location that ensures that the newly create /// inner segment is orthogonal. /// </summary> public int CreateBend(IInputModeContext context, IGraph graph, IEdge edge, PointD location) { var edgePoints = GetEdgePoints(edge); var closestSegment = DetermineBendSegmentIndex(edgePoints, location); int firstSegment = 0; int lastSegment = edge.Bends.Count; // if bend wasn't created in first or last segment, call default action if (closestSegment != firstSegment && closestSegment != lastSegment) { return((new DefaultBendCreator()).CreateBend(context, graph, edge, location)); } // add created bend and another one to make the edge stay orthogonal if (closestSegment == -1 || context == null || !(context.ParentInputMode is CreateBendInputMode)) { return(-1); } var editingContext = context.Lookup <OrthogonalEdgeEditingContext>(); if (editingContext == null) { return(-1); } if (closestSegment == firstSegment) { IPoint nextPoint = edgePoints[1]; // get orientation of next edge segment to determine second bend location SegmentOrientation orientation = editingContext.GetSegmentOrientation(edge, 1); graph.AddBend(edge, location, 0); if (orientation == SegmentOrientation.Horizontal) { graph.AddBend(edge, new PointD(nextPoint.X, location.Y), 1); } else if (orientation == SegmentOrientation.Vertical) { graph.AddBend(edge, new PointD(location.X, nextPoint.Y), 1); } return(0); } if (closestSegment == lastSegment) { IPoint prevPoint = edgePoints[edge.Bends.Count]; // get orientation of next edge segment to determine second bend location SegmentOrientation orientation = editingContext.GetSegmentOrientation(edge, edge.Bends.Count - 1); graph.AddBend(edge, location, edge.Bends.Count); if (orientation == SegmentOrientation.Horizontal) { graph.AddBend(edge, new PointD(prevPoint.X, location.Y), edge.Bends.Count - 1); } else if (orientation == SegmentOrientation.Vertical) { graph.AddBend(edge, new PointD(location.X, prevPoint.Y), edge.Bends.Count - 1); } return(edge.Bends.Count - 1); } return(-1); }
/// <summary> /// Creates the sample graph of the given color with two nodes and a single edge. /// </summary> private static IEdge CreateSubgraph(IGraph graph, Color color, double yOffset, bool createPorts) { var brush = new SolidColorBrush(color); // Create two nodes var nodeStyle = new ShinyPlateNodeStyle { Brush = brush }; var n1 = graph.CreateNode(new RectD(110, 100 + yOffset, 40, 40), nodeStyle, color); var n2 = graph.CreateNode(new RectD(450, 130 + yOffset, 40, 40), nodeStyle, color); // Create an edge, either between the two nodes or between the nodes's ports IEdge edge; if (!createPorts) { edge = graph.CreateEdge(n1, n2, new PolylineEdgeStyle { Pen = new Pen(brush, 1) }, color); } else { var p1 = CreateSamplePorts(graph, n1, true); var p2 = CreateSamplePorts(graph, n2, false); edge = graph.CreateEdge(p1[1], p2[2], new PolylineEdgeStyle { Pen = new Pen(brush, 1) }, color); } // Add bends that create a veredge.SourcePort.Locationtical segment in the middle of the edge var sourcePortLocation = edge.SourcePort.GetLocation(); var targetPortLocation = edge.TargetPort.GetLocation(); var x = (sourcePortLocation.X + targetPortLocation.X) / 2; graph.AddBend(edge, new PointD(x, sourcePortLocation.Y)); graph.AddBend(edge, new PointD(x, targetPortLocation.Y)); return(edge); }
/// <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); }
/// <summary> /// If the existing number of bends is 2 mod 3 (i.e. the bends are consistent with /// what the bezier style expects), /// this implementation creates a triple of collinear bends and adjust the neighboring bends /// in a way that the shape of the curve is not changed initially and returns the middle bend. /// If there are no bends at all, it creates a triple plus two initial and final control bends, all of them collinear. /// Otherwise, the fallback bend creator is used to create a bend with its default strategy. /// </summary> /// <returns>The index of middle bend of a control point triple if such a triple was created, /// or the index of the newly created single bend.</returns> public int CreateBend(IInputModeContext context, IGraph graph, IEdge edge, PointD location) { switch (edge.Bends.Count) { case 0: var spl = edge.SourcePort.GetLocation(); var tpl = edge.TargetPort.GetLocation(); //a single linear segment... we just insert 5 collinear bends adjusted to the angle of the linear segment, //approximately evenly spaced graph.AddBend(edge, (location - spl) / 4 + spl, 0); graph.AddBend(edge, -(location - spl) / 4 + location, 1); graph.AddBend(edge, location, 2); graph.AddBend(edge, (location - spl) / 4 + location, 3); graph.AddBend(edge, location + (tpl - location) * 3 / 4, 4); return(2); case 1: //Use the default strategy to insert a single bend at the correct index return(fallBackCreator.CreateBend(context, graph, edge, location)); default: var pathPoints = edge.GetPathPoints(); if (pathPoints.Count % 3 == 1) { //Consistent number of existing points //Try to insert a smooth bend //I.e. a triple of three collinear bends and adjust the neighbor bends //Various quality measures and counters var segmentIndex = 0; var pathCounter = 0; var bestDistanceSqr = Double.PositiveInfinity; double bestRatio = Double.NaN; //The index of the segment where we want to create the bend in the end int bestIndex = -1; //Find the best segment while (pathCounter + 3 < pathPoints.Count) { //Get the control points defining the current segment var cp0 = pathPoints[pathCounter++]; var cp1 = pathPoints[pathCounter++]; var cp2 = pathPoints[pathCounter++]; //Consecutive segments share the last/first control point! So we may not advance the counter here var cp3 = pathPoints[pathCounter]; //Shift a cubic segment // //Here we assume that the path is actually composed of cubic segments, only. //Alternatively, we could inspect the actual path created by the edge renderer - this would also //allow to deal with intermediate non cubic segments, but we'd have to associate those //path segments somehow with the correct bends again, so again this would be tied to the actual //renderer implementation. var fragment = new GeneralPath(2); fragment.MoveTo(cp0); fragment.CubicTo(cp1, cp2, cp3); //Try to find the projection onto the fragment var ratio = fragment.GetProjection(location, 0); if (ratio.HasValue) { //Actually found a projection ratio //Determine the point on the curve - the tangent provides this var tangent = fragment.GetTangent(0, ratio.Value); if (tangent.HasValue) { //There actually is a tangent var d = (location - tangent.Value.Point).SquaredVectorLength; //Is this the best distance? if (d < bestDistanceSqr) { bestDistanceSqr = d; //Remember ratio (needed to split the curve) bestRatio = ratio.Value; //and the index, of course bestIndex = segmentIndex; } } } ++segmentIndex; } if (bestIndex != -1) { //Actually found a segment //For the drag, we want to move the middle bend return(CreateBends(graph, edge, bestIndex, bestRatio, pathPoints).GetIndex()); } //No best segment found (for whatever reason) - we don't want to create a bend so that we don't mess up anything return(-1); } else { //No consistent number of bends - just insert a single bend //We could also see whether we actually would have a cubic segment on the path, and treat that differently //However, why bother - just create the edge with a correct number of points instead return(fallBackCreator.CreateBend(context, graph, edge, location)); } } }
private static IBend CreateBends([NotNull] IGraph graph, [NotNull] IEdge edge, int segmentIndex, double ratio, [NotNull] IListEnumerable <IPoint> pathPoints) { //Create 3 bends and adjust the neighbors //The first bend we need to touch is at startIndex var startIndex = segmentIndex * 3; //This holds the new coordinates left and right of the split point //We don't actually need all of them, but this keeps the algorithm more straightforward. var left = new PointD[4]; var right = new PointD[4]; //Determine the new control points to cleanly split the curve GetCubicSplitPoints(ratio, new[] { pathPoints[startIndex].ToPointD(), pathPoints[startIndex + 1].ToPointD(), pathPoints[startIndex + 2].ToPointD(), pathPoints[startIndex + 3].ToPointD() }, left, right); //Previous control point - does always exist as a bend, given our precondition var previousBend = edge.Bends[startIndex]; //Next control point - also always exists given the precondition for bend counts (i.e. there have to be at least two) var nextBend = edge.Bends[startIndex + 1]; //We create the three new bends between previous bend and next bend and adjust these two. //We don't have to adjust more bends, since we just have a cubic curve. IBend bendToMove; var engine = graph.GetUndoEngine(); //Wrap everything into a single compound edit, so that everything can be undone in a single unit using (var edit = graph.BeginEdit("Create Bezier Bend", "Create Bezier Bend")) { try { //Adjust the previous bend - given the split algorithm, its coordinate is in left[1] //(left[0] is actually kept unchanged from the initial value) var oldPrevLocation = previousBend.Location.ToPointD(); var newPrevLocation = left[1]; graph.SetBendLocation(previousBend, newPrevLocation); // Add unit to engine graph.AddUndoUnit("Set bend location", "Set bend location", () => graph.SetBendLocation(previousBend, oldPrevLocation), () => graph.SetBendLocation(previousBend, newPrevLocation)); //Insert the new triple, using the values from left and right in order graph.AddBend(edge, left[2], startIndex + 1); bendToMove = graph.AddBend(edge, left[3], startIndex + 2); //right[0] == left[3], so right[1] is the next new control point graph.AddBend(edge, right[1], startIndex + 3); //Adjust the next bend var oldNextLocation = nextBend.Location.ToPointD(); var newNextLocation = right[2]; graph.SetBendLocation(nextBend, newNextLocation); // Add unit to engine graph.AddUndoUnit("Set bend location", "Set bend location", () => graph.SetBendLocation(nextBend, oldNextLocation), () => graph.SetBendLocation(nextBend, newNextLocation)); } catch { //Cancel the edit in case anything goes wrong. edit.Cancel(); throw; } } return(bendToMove); }
public static IBend AddBend(this IGraph graph, IEdge edge, int index, PointD location) { return(graph.AddBend(edge, location, index)); }
public static IBend AppendBend(this IGraph graph, IEdge edge, PointD location) { return(graph.AddBend(edge, location)); }