Orthogonal link reshaping

I have set the routing to Orthogonal. The link reshaping does not work as I would expect it to. It seems to be missing the vertices nearest the ends of the links and only allows moving the other central vertices either horizontally or vertically but not both.

I want vertices at the ends of the links that only allow horizontal moves and central vertices that move both horizontally and vertically.

I attempted to override the UpdateAdornments method in the LinkReshapingTool. I was able to get the vertices at the ends of the links acting like I want. However, the vertices between these now do not move at all even though the ReshapeBehavior is set to ReshapeBehavior.All.

       public override void UpdateAdornments(Part part)
        {
            Link link = part as Link;
            if (link == null) return;  // no Nodes
 
            Adornment adornment = null;
            if (link.IsSelected)
            {
                FrameworkElement selelt = link.Path;
                if (selelt != null && link.CanReshape() && Part.IsVisibleElement(selelt))
                {
                    adornment = link.GetAdornment(ToolCategory);
                    if (adornment == null)
                    {
                        Route route = link.Route;
                        IEnumerable<Point> pts = route.Points;
                        int numpts = route.PointsCount;
                        if (numpts < 2) return;
 
                        // this Adornment consists of a LinkPanel holding a bunch of ToolHandles
                        LinkPanel panel = new LinkPanel();
                        int firstindex = route.FirstPickIndex;
                        int lastindex = route.LastPickIndex;
                        
                        for (int i = firstindex; i <= lastindex; i++)
                        {
                            //if (i > firstindex)
                            //{
                                // the handle at each vertex point:
                                FrameworkElement h = new ToolHandle()
                                {
                                    Width = 6,
                                    Height = 6,
                                    Fill = Brushes.Yellow,
                                    Stroke = Brushes.Black,
                                    StrokeThickness = 1
                                };
                                NodePanel.SetFigure(h, NodeFigure.Rectangle);
                                // identify the segment for this particular handle
                                LinkPanel.SetIndex(h, i);
                                // allow reshape behavior if not at either end
                                if (i == firstindex || i == lastindex)
                                {
                                    SetReshapeBehavior(h, ReshapeBehavior.Horizontal);
                                    h.Cursor = Cursors.SizeWE;
                                }
                                else
                                {
                                    SetReshapeBehavior(h, ReshapeBehavior.All);
                                    h.Cursor = Cursors.SizeAll;
                                }
                                
                                panel.Children.Add(h);
                            
                        }
 
                        adornment = new Adornment();
                        adornment.AdornedElement = selelt;
                        adornment.Category = ToolCategory;
                        adornment.Content = panel;  // just provide the FrameworkElement as the Content and as the Visual Child
                        adornment.LocationSpot = Spot.TopLeft;
                    }
                }
                if (adornment != null)
                {
                    Point loc = link.GetElementPoint(selelt, Spot.TopLeft);
                    adornment.Position = loc;
                    //adornment.RotationAngle = link.GetAngle(selelt);
                    adornment.Remeasure();
                }
            }
            link.SetAdornment(ToolCategory, adornment);
        }
    }
Do you have any suggestions?

I’m not sure why you needed to customize the LinkReshapingTool, because I think the default behavior is what you want. Take a look at the reshape handles on links in the FlowChart sample.

So I am curious why you are not getting the same reshape handles as in the FlowChart sample.

In the FlowChart example the routing is set to AvoidsNodes. I am using Orthogonal routing. If I set my routing to AvoidsNodes I get the same behavior as in the FlowChart example. We decided to go with Orthogonal routing because the AvoidsNodes routing caused too many issues with overlapping links.

Usually when you override UpdateAdornments and change what ToolHandles there are or what they do, you also have to override the method that handles dragging those ToolHandles. In the case of the LinkReshapingTool, that is the DoReshape method.

I believe the standard definition of DoReshape is treating those “end” handles specially. Here’s the code, which you can adapt:

protected virtual void DoReshape(Point newPoint) { Link link = this.AdornedLink; Route route = link.Route; ReshapeBehavior behavior = GetReshapeBehavior(this.Handle); if (route.Orthogonal) { // need to adjust adjacent points as well if (this.HandleIndex == route.FirstPickIndex+1) { int midfirst = route.FirstPickIndex+1; if (behavior == ReshapeBehavior.Vertical) { // move segment vertically route.SetPoint(midfirst, new Point(route.GetPoint(midfirst-1).X, newPoint.Y)); route.SetPoint(midfirst+1, new Point(route.GetPoint(midfirst+2).X, newPoint.Y)); } else if (behavior == ReshapeBehavior.Horizontal) { // move segment horizontally route.SetPoint(midfirst, new Point(newPoint.X, route.GetPoint(midfirst-1).Y)); route.SetPoint(midfirst+1, new Point(newPoint.X, route.GetPoint(midfirst+2).Y)); } } else if (this.HandleIndex == route.LastPickIndex-1) { int midlast = route.LastPickIndex-1; if (behavior == ReshapeBehavior.Vertical) { // move segment vertically route.SetPoint(midlast-1, new Point(route.GetPoint(midlast-2).X, newPoint.Y)); route.SetPoint(midlast, new Point(route.GetPoint(midlast+1).X, newPoint.Y)); } else if (behavior == ReshapeBehavior.Horizontal) { // move segment horizontally route.SetPoint(midlast-1, new Point(newPoint.X, route.GetPoint(midlast-2).Y)); route.SetPoint(midlast, new Point(newPoint.X, route.GetPoint(midlast+1).Y)); } } else { // can move anywhere, but need to keep adjacent segments orthogonal int i = this.HandleIndex; Point oldpt = route.GetPoint(i); Point before = route.GetPoint(i-1); Point after = route.GetPoint(i+1); if (Geo.IsApprox(before.X, oldpt.X) && Geo.IsApprox(oldpt.Y, after.Y)) { route.SetPoint(i-1, new Point(newPoint.X, before.Y)); route.SetPoint(i+1, new Point(after.X, newPoint.Y)); } else if (Geo.IsApprox(before.Y, oldpt.Y) && Geo.IsApprox(oldpt.X, after.X)) { route.SetPoint(i-1, new Point(before.X, newPoint.Y)); route.SetPoint(i+1, new Point(newPoint.X, after.Y)); } else if (Geo.IsApprox(before.X, oldpt.X) && Geo.IsApprox(oldpt.X, after.X)) { route.SetPoint(i-1, new Point(newPoint.X, before.Y)); route.SetPoint(i+1, new Point(newPoint.X, after.Y)); } else if (Geo.IsApprox(before.Y, oldpt.Y) && Geo.IsApprox(oldpt.Y, after.Y)) { route.SetPoint(i-1, new Point(before.X, newPoint.Y)); route.SetPoint(i+1, new Point(after.X, newPoint.Y)); } route.SetPoint(this.HandleIndex, newPoint); } } else { // no Orthogonal constraints, just set the new point route.SetPoint(this.HandleIndex, newPoint); } }

I put that code in my custom link reshaping tool but you are using a method Geo.IsApprox that does not exist in the current context. What or where is that method?

Back to your first response, I think you are right that the behavior I want should be the default behavior. It is the default behavior when routing is set the AvoidsNodes but not when routing is set to Orthononal. Maybe this is a bug? I did notice that when the routing is set to AvoidsNodes there is one more point on each end of the route than there is when routing is set to Orthogonal.

Sorry about that – Geo.IsApprox is an internal method that sees if two values or Points are close to each other.

The default routing behavior for Orthogonal links should be different than for AvoidsNodes links. I can understand how you would like there to sometimes be additional points in the route, but that’s why you can override these two methods (UpdateAdornments and DoReshape) and Route.AddOrthoPoints.

Geo.IsApprox - can you share please :) or do I have to do this myself?

I don’t understand why you would want the default reshape behavior to be different for AvoidsNodes and Orthogonal. I don’t want to add any points to the route, I just want to be able to appropriately reshape the points that are already there. I would expect the first point on each end (the one right at the port) to be unmovable. The next point from each end to be movable in only one dimension. And all points in between should be movable in all directions changing adjacent points as needed. The current default behavior on Orthogonal has both the end point and first point from each end unmovable and the second point in from each end movable in only one dimension.

If you look at the images in the first post, when routing is set to Orthogonal there are only two drag handles and Route.Points contains 6 points. When routing is set to AvoidsNodes there are four drag handles and Route.Points contains 8 points. The reshaping code looks like it ignores the first two points at each end and makes the third point move only in one dimension. That works for AvoidsNodes because there seems to be an extra point at each end. However, it does not work for Orthogonal, it should only ignore the first point at each end and make the second point move only in one dimension.

public static bool IsApprox(double x, double y) { double d = x - y; return d < 0.5 && d > -0.5; }</p><p> public static bool IsApprox(Point a, Point b) { return IsApprox(a.X, b.X) && IsApprox(a.Y, b.Y); }


In general the extra points are necessary for the constrained reshaping to be possible. In the particular case that you have shown, it isn’t the case, so what you are saying is perfectly reasonable. And you can implement that behavior if you want, by overriding those two methods to do what you want.

I’m sorry that the default behavior isn’t quite what you expected, but that’s how it is right now. Perhaps in the future we should extend the functionality along the lines of your suggestion.

OK, thanks!

The link reshaping is now working as I want it too. However, it seemed to cause another problem. In another post http://www.nwoods.com/forum/forum_posts.asp?TID=4780&title=avoid-link-overlap-and-keep-manual-route-changes you helped me to make it so that manual rerouting of a link was not undone when a connected node was moved by setting Route.Adjusting=LinkAdjusting.End. That no longer seems to do the trick. When I move a node the link seems to completely reroute itself and loses the manual changes.

I don’t know. You haven’t overridden Route methods, have you? Or somehow cleared the Route’s sequence of Points? I can’t think of any other reason that might happen.