Adding adornment to a link


#1

Hi,

As you can see in the below screenshot, I have a connection where it has only two adornments. Using this I’m only able to re-route the link in one direction. How do I add more adornments to the link ?

image

Thanks.


#2

Assuming you want the user to maintain orthogonality, you’ll need to add more points to the route. That can be done by overriding Route.AddOrthoPoints:

  public class ExtraRoute : Route {
    protected override void AddOrthoPoints(Point startFrom, double fromDir, Point endTo, double toDir, Node fromnode, Node tonode) {
      AddPoint(startFrom);
      base.AddOrthoPoints(startFrom, fromDir, endTo, toDir, fromnode, tonode);
      AddPoint(endTo);
    }
  }

Use it in your Link template:

    <DataTemplate x:Key="LinkTemplate">
      <go:LinkPanel . . .>
        <go:Link.Route>
          <local:ExtraRoute Routing="Orthogonal" . . . />
        </go:Link.Route>
  . . .

#3

Thanks. This works fine.
But , the two points nearest to the ports(StartFrom and endTo points) can only be moved along one direction.
image
Is it possible to make all points more flexible in the sense that one can move them in both directions?


#4

So you have added Points to the route (i.e. Link.Points). Are there in fact two Points with the same value for points 1 and 2, and similarly two same points at points n-2 and n-3?

The default LinkReshapingTool behavior is to maintain orthogonality by only allowing one direction freedom of movement to the first and last reshape handles. When there are only 6 points, that means there is only one direction allowed for the middle 2 reshape handles. But if you have 8 points now in the route, there should be full freedom to the middle 2 reshape handles, at points 3 and 4, limited freedom to the reshape handles at points 2 and 5, and no reshape handle at all at points 1 and 6.

If you want full freedom of movement for the reshape handles at points 2 and 5, I suppose you could customize the LinkReshapingTool even further by having it automatically insert points when the user starts to drag the reshape handle at points 2 and 5. But only the first time when the drag starts – not on each mouse movement!

If I have time I can see if I can come with a sample this afternoon.


#5

I haven’t tested this thoroughly, but this custom LinkReshapingTool only inserts a point at the time of the first call to DoReshape.

  public class CustomLinkReshapingTool : LinkReshapingTool {
    public const String ToolCategory = "ReshapeLink";

    private bool _AlreadyAddedPoint = false;

    public override void DoActivate() {
      base.DoActivate();
      _AlreadyAddedPoint = false;
    }

    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;
            bool ortho = route.Orthogonal;

            // this Adornment consists of a LinkPanel holding a bunch of ToolHandles
            LinkPanel panel = new LinkPanel();
            int firstindex = route.FirstPickIndex;
            int lastindex = route.LastPickIndex;
            int orthadjust = (ortho ? 1 : 0);
            if (/*route.Resegmentable &&*/ route.Curve != LinkCurve.Bezier) {
              for (int i = firstindex+orthadjust; i < lastindex-orthadjust; i++) {
                Point a = route.GetPoint(i);
                Point b = route.GetPoint(i + 1);
                if (IsApprox(a, b)) continue;
                FrameworkElement mh = null;
                // now the handle at the middle of each segment:
                if (IsApprox(a.X, b.X)) {
                  mh = new ToolHandle() {
                    Width = 6,
                    Height = 16,
                    Fill = Brushes.Yellow,
                    Stroke = Brushes.Black,
                    StrokeThickness = 1
                  };
                }
                else {
                  mh = new ToolHandle() {
                    Width = 16,
                    Height = 6,
                    Fill = Brushes.Yellow,
                    Stroke = Brushes.Black,
                    StrokeThickness = 1
                  };
                }
                NodePanel.SetFigure(mh, NodeFigure.Rectangle);
                // identify this particular handle within the LinkPanel
                LinkPanel.SetIndex(mh, i);  // the segment
                LinkPanel.SetFraction(mh, 0.5);  // how far along the segment
                if (IsApprox(a.X, b.X)) {
                  SetReshapeBehavior(mh, ReshapeBehavior.Horizontal);
                  mh.Cursor = Cursors.SizeWE;
                } else {
                  SetReshapeBehavior(mh, ReshapeBehavior.Vertical);
                  mh.Cursor = Cursors.SizeNS;
                }
                panel.Children.Add(mh);
              }
            }
            // don't bother creating handles for firstindex or lastindex
            for (int i = firstindex + 1; i < lastindex; i++) {
              // 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);
              // now determines its reshape behavior and cursor, depending on whether Orthogonal et al.
              //if (i == firstindex + 1 && ortho) {
              //  Point a = route.GetPoint(firstindex);
              //  Point b = route.GetPoint(firstindex + 1);
              //  if (IsApprox(a, b)) {
              //    b = route.GetPoint(firstindex - 1);
              //    if (IsApprox(a.X, b.X)) {
              //      SetReshapeBehavior(h, ReshapeBehavior.Vertical);
              //      h.Cursor = Cursors.SizeNS;
              //    } else if (IsApprox(a.Y, b.Y)) {
              //      SetReshapeBehavior(h, ReshapeBehavior.Horizontal);
              //      h.Cursor = Cursors.SizeWE;
              //    }
              //  } else if (IsApprox(a.X, b.X)) {
              //    SetReshapeBehavior(h, ReshapeBehavior.Vertical);
              //    h.Cursor = Cursors.SizeNS;
              //  } else if (IsApprox(a.Y, b.Y)) {
              //    SetReshapeBehavior(h, ReshapeBehavior.Horizontal);
              //    h.Cursor = Cursors.SizeWE;
              //  }
              //} else if (i == lastindex - 1 && ortho) {
              //  Point a = route.GetPoint(lastindex - 1);
              //  Point b = route.GetPoint(lastindex);
              //  if (IsApprox(a, b)) {
              //    a = route.GetPoint(lastindex + 1);
              //    if (IsApprox(a.X, b.X)) {
              //      SetReshapeBehavior(h, ReshapeBehavior.Vertical);
              //      h.Cursor = Cursors.SizeNS;
              //    } else if (IsApprox(a.Y, b.Y)) {
              //      SetReshapeBehavior(h, ReshapeBehavior.Horizontal);
              //      h.Cursor = Cursors.SizeWE;
              //    }
              //  } else if (IsApprox(a.X, b.X)) {
              //    SetReshapeBehavior(h, ReshapeBehavior.Vertical);
              //    h.Cursor = Cursors.SizeNS;
              //  } else if (IsApprox(a.Y, b.Y)) {
              //    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.Remeasure();
        }
      }
      link.SetAdornment(ToolCategory, adornment);
    }

    protected override void DoReshape(Point newPoint) {
      Link link = this.AdornedLink;
      Route route = link.Route;
      int index = this.HandleIndex;
      ReshapeBehavior behavior = GetReshapeBehavior(this.Handle);
      if (behavior == ReshapeBehavior.None) return;
      if (route.Orthogonal) {  // need to adjust adjacent points as well
        if (!_AlreadyAddedPoint && LinkPanel.GetFraction(this.Handle) == 0.0) {
          _AlreadyAddedPoint = true;
          if (index == route.FirstPickIndex + 1) {
            System.Diagnostics.Debug.WriteLine("first " + index.ToString());
            route.InsertPoint(index, route.GetPoint(index));
            link.SetAdornment(ToolCategory, null);
            UpdateAdornments(link);

            return;
          } else if (index == route.LastPickIndex - 1) {
            System.Diagnostics.Debug.WriteLine("last " + index.ToString());
            route.InsertPoint(index, route.GetPoint(index));
            link.SetAdornment(ToolCategory, null);
            UpdateAdornments(link);
            return;
          }
        }
        if (index == 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 (index == 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 = index;
          Point oldpt = route.GetPoint(i);
          Point before = route.GetPoint(i-1);
          Point after = route.GetPoint(i+1);
          if (IsApprox(before.X, oldpt.X) && IsApprox(oldpt.Y, after.Y)) {
            if (IsApprox(before.X, route.GetPoint(i-2).X) && !IsApprox(before.Y, route.GetPoint(i-2).Y)) {
              route.InsertPoint(i, new Point(newPoint.X, before.Y));
              index++;
              i++;
            } else route.SetPoint(i-1, new Point(newPoint.X, before.Y));
            if (IsApprox(after.Y, route.GetPoint(i+2).Y) && !IsApprox(after.X, route.GetPoint(i+2).X)) {
              route.InsertPoint(i+1, new Point(after.X, newPoint.Y));
            } else route.SetPoint(i+1, new Point(after.X, newPoint.Y));
          } else if (IsApprox(before.Y, oldpt.Y) && IsApprox(oldpt.X, after.X)) {
            if (IsApprox(before.Y, route.GetPoint(i-2).Y) && !IsApprox(before.X, route.GetPoint(i-2).X)) {
              route.InsertPoint(i, new Point(before.X, newPoint.Y));
              index++;
              i++;
            } else route.SetPoint(i-1, new Point(before.X, newPoint.Y));
            if (IsApprox(after.X, route.GetPoint(i+2).X) && !IsApprox(after.Y, route.GetPoint(i+2).Y)) {
              route.InsertPoint(i+1, new Point(newPoint.X, after.Y));
            } else route.SetPoint(i+1, new Point(newPoint.X, after.Y));
          } else if (IsApprox(before.X, oldpt.X) && IsApprox(oldpt.X, after.X)) {
            if (IsApprox(before.X, route.GetPoint(i-2).X) && !IsApprox(before.Y, route.GetPoint(i-2).Y)) {
              route.InsertPoint(i, new Point(newPoint.X, before.Y));
              index++;
              i++;
            } else route.SetPoint(i-1, new Point(newPoint.X, before.Y));
            if (IsApprox(after.X, route.GetPoint(i+2).X) && !IsApprox(after.Y, route.GetPoint(i+2).Y)) {
              route.InsertPoint(i+1, new Point(newPoint.X, after.Y));
            } else route.SetPoint(i+1, new Point(newPoint.X, after.Y));
          } else if (IsApprox(before.Y, oldpt.Y) && IsApprox(oldpt.Y, after.Y)) {
            if (IsApprox(before.Y, route.GetPoint(i-2).Y) && !IsApprox(before.X, route.GetPoint(i-2).X)) {
              route.InsertPoint(i, new Point(before.X, newPoint.Y));
              index++;
              i++;
            } else route.SetPoint(i-1, new Point(before.X, newPoint.Y));
            if (IsApprox(after.Y, route.GetPoint(i+2).Y) && !IsApprox(after.X, route.GetPoint(i+2).X)) {
              route.InsertPoint(i+1, new Point(after.X, newPoint.Y));
            } else route.SetPoint(i+1, new Point(after.X, newPoint.Y));
          }
          route.SetPoint(index, newPoint);
        }
      } else {  // no Orthogonal constraints, just set the new point
        route.SetPoint(this.HandleIndex, newPoint);
        // also adjust the end point if there is no spot for the port
        var fromNode = link.FromNode;
        var fromPort = link.FromPort;
        var fromSpot = Node.GetFromSpot(fromPort);
        var toNode = link.ToNode;
        var toPort = link.ToPort;
        var toSpot = Node.GetToSpot(toPort);
        if (this.HandleIndex == 1 && fromSpot.IsNoSpot) {
          var endpt = route.GetLinkPoint(fromNode, fromPort, Spot.None, true, false, toNode, toPort);
          route.SetPoint(0, endpt);
        }
        if (this.HandleIndex == route.PointsCount - 2 && toSpot.IsNoSpot) {
          var endpt = route.GetLinkPoint(toNode, toPort, Spot.None, false, false, fromNode, fromPort);
          route.SetPoint(route.PointsCount - 1, endpt);
        }
      }
    }

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

#6

I am facing this behavior with the adornments on the link.

In the below gif, I am adding a connection between the nodes. I am then not altering the connection but I am directly flipping the shape. Now I am able to move the first adornment point to right and left .
com-video-to-gif%20(3)

In the below gif, I am adding a connection between the nodes. This time, I am altering the connection by moving it . As you can see, it moves up and down. Now I am flipping the node. Now, the adornment point can only be moved up and down and not to the right or left.

com-video-to-gif%20(4)

Is there a reason to why this behavior occurs?


#7

I don’t know – you’d have to figure out exactly what UpdateAdornments is doing to control the cursors on the reshape handles, and what DoReshape is doing to control what movement each of the reshape handles may have.


#8

Hi,
I faced one more issue with the link.

com-video-to-gif%20(5)
From the above gif, you can see that when the second point is moved, the orthogonality of the link is lost.

As I move the last point , the orthogonality of the link is restored as shown in the below gif.
com-video-to-gif%20(6)

Is there a reason to why this behavior occurs?


#9

I’m suspicious that the FromSpot and ToSpot are not correct in your app. Note how the end segments are going up-and-down, rather than left-to-right or right-to-left. The latter should be more appropriate given the positions of the ports on the left and right sides of the nodes.

I can see if I can reproduce the problem. Have you overridden any methods of either the Link or the Route classes?


#10

Yes, I have overridden the AddOrthoPoints method of Route class to add two more points.

 public class ExtraRoute : Route {
    protected override void AddOrthoPoints(Point startFrom, double fromDir, Point endTo, double toDir, Node fromnode, Node tonode) {
      AddPoint(startFrom);
      base.AddOrthoPoints(startFrom, fromDir, endTo, toDir, fromnode, tonode);
      AddPoint(endTo);
    }
  }

Used it on the Link template:

    <DataTemplate x:Key="LinkTemplate">
      <go:LinkPanel . . .>
        <go:Link.Route>
          <local:ExtraRoute Routing="Orthogonal" . . . />
        </go:Link.Route>
  . . .

I tried the same in the GoWpf Sample and I found the same issue.
com-video-to-gif%20(7)


#11

Hmmm, I think the custom LinkReshapingTool I gave you was not assuming that override of Route.AddOrthoPoints.

Instead of using that custom Route class, just use this custom LinkReshapingTool:

  public class CustomLinkReshapingTool : LinkReshapingTool {
    public const String ToolCategory = "ReshapeLink";

    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;
            bool ortho = route.Orthogonal;

            // this Adornment consists of a LinkPanel holding a bunch of ToolHandles
            LinkPanel panel = new LinkPanel();
            int firstindex = route.FirstPickIndex;
            int lastindex = route.LastPickIndex;
            // don't bother creating handles for firstindex or lastindex
            for (int i = firstindex + 1; i < lastindex; i++) {
              // 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);
              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.Remeasure();
        }
      }
      link.SetAdornment(ToolCategory, adornment);
    }

    protected override void DoReshape(Point newPoint) {
      Link link = this.AdornedLink;
      Route route = link.Route;
      int index = this.HandleIndex;
      ReshapeBehavior behavior = GetReshapeBehavior(this.Handle);
      if (behavior == ReshapeBehavior.None) return;
      if (route.Orthogonal) {  // need to adjust adjacent points as well
        if (index == route.FirstPickIndex + 1) {
          //System.Diagnostics.Debug.WriteLine("first " + index.ToString());
          route.InsertPoint(index, route.GetPoint(index));
          link.SetAdornment(ToolCategory, null);
          UpdateAdornments(link);

          return;
        } else if (index == route.LastPickIndex - 1) {
          //System.Diagnostics.Debug.WriteLine("last " + index.ToString());
          route.InsertPoint(index, route.GetPoint(index));
          link.SetAdornment(ToolCategory, null);
          UpdateAdornments(link);
          return;
        }
        // can move anywhere, but need to keep adjacent segments orthogonal
        int i = index;
        Point oldpt = route.GetPoint(i);
        Point before = route.GetPoint(i-1);
        Point after = route.GetPoint(i+1);
        if (IsApprox(before.X, oldpt.X) && IsApprox(oldpt.Y, after.Y)) {
          if (IsApprox(before.X, route.GetPoint(i-2).X) && !IsApprox(before.Y, route.GetPoint(i-2).Y)) {
            route.InsertPoint(i, new Point(newPoint.X, before.Y));
            index++;
            i++;
          } else route.SetPoint(i-1, new Point(newPoint.X, before.Y));
          if (IsApprox(after.Y, route.GetPoint(i+2).Y) && !IsApprox(after.X, route.GetPoint(i+2).X)) {
            route.InsertPoint(i+1, new Point(after.X, newPoint.Y));
          } else route.SetPoint(i+1, new Point(after.X, newPoint.Y));
        } else if (IsApprox(before.Y, oldpt.Y) && IsApprox(oldpt.X, after.X)) {
          if (IsApprox(before.Y, route.GetPoint(i-2).Y) && !IsApprox(before.X, route.GetPoint(i-2).X)) {
            route.InsertPoint(i, new Point(before.X, newPoint.Y));
            index++;
            i++;
          } else route.SetPoint(i-1, new Point(before.X, newPoint.Y));
          if (IsApprox(after.X, route.GetPoint(i+2).X) && !IsApprox(after.Y, route.GetPoint(i+2).Y)) {
            route.InsertPoint(i+1, new Point(newPoint.X, after.Y));
          } else route.SetPoint(i+1, new Point(newPoint.X, after.Y));
        } else if (IsApprox(before.X, oldpt.X) && IsApprox(oldpt.X, after.X)) {
          if (IsApprox(before.X, route.GetPoint(i-2).X) && !IsApprox(before.Y, route.GetPoint(i-2).Y)) {
            route.InsertPoint(i, new Point(newPoint.X, before.Y));
            index++;
            i++;
          } else route.SetPoint(i-1, new Point(newPoint.X, before.Y));
          if (IsApprox(after.X, route.GetPoint(i+2).X) && !IsApprox(after.Y, route.GetPoint(i+2).Y)) {
            route.InsertPoint(i+1, new Point(newPoint.X, after.Y));
          } else route.SetPoint(i+1, new Point(newPoint.X, after.Y));
        } else if (IsApprox(before.Y, oldpt.Y) && IsApprox(oldpt.Y, after.Y)) {
          if (IsApprox(before.Y, route.GetPoint(i-2).Y) && !IsApprox(before.X, route.GetPoint(i-2).X)) {
            route.InsertPoint(i, new Point(before.X, newPoint.Y));
            index++;
            i++;
          } else route.SetPoint(i-1, new Point(before.X, newPoint.Y));
          if (IsApprox(after.Y, route.GetPoint(i+2).Y) && !IsApprox(after.X, route.GetPoint(i+2).X)) {
            route.InsertPoint(i+1, new Point(after.X, newPoint.Y));
          } else route.SetPoint(i+1, new Point(after.X, newPoint.Y));
        }
        route.SetPoint(index, newPoint);
      } else {  // no Orthogonal constraints, just set the new point
        route.SetPoint(this.HandleIndex, newPoint);
        // also adjust the end point if there is no spot for the port
        var fromNode = link.FromNode;
        var fromPort = link.FromPort;
        var fromSpot = Node.GetFromSpot(fromPort);
        var toNode = link.ToNode;
        var toPort = link.ToPort;
        var toSpot = Node.GetToSpot(toPort);
        if (this.HandleIndex == 1 && fromSpot.IsNoSpot) {
          var endpt = route.GetLinkPoint(fromNode, fromPort, Spot.None, true, false, toNode, toPort);
          route.SetPoint(0, endpt);
        }
        if (this.HandleIndex == route.PointsCount - 2 && toSpot.IsNoSpot) {
          var endpt = route.GetLinkPoint(toNode, toPort, Spot.None, false, false, fromNode, fromPort);
          route.SetPoint(route.PointsCount - 1, endpt);
        }
      }
    }

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

Install with:

      myDiagram.LinkReshapingTool = new CustomLinkReshapingTool();

or by doing the same in XAML.

You now have the complete implementation of DoReshape, so you can adapt the code if you find it’s not doing what you want.


#12

The above code works with adding new points as we move the link.
com-video-to-gif%20(8)
But in the above gif, there are two issues.

  1. The point on the right cannot be moved, that is, more link routes points are created rather than the point moving.

com-video-to-gif%20(9)

  1. If I move the node, the link is not getting shorter like the above gif.
    I tried using RecomputePoints . But it does not seem to work.

#13

I took out the flag _AlreadyAddedPoint that was in the code I had pasted above. Perhaps you do want that behavior after all.

Have you set Route.Adjusting?


#14

Route.Adjusting was set to End which I have removed. The path now becomes shorter. But I do not want the link to get shorter every time I move the node.
com-video-to-gif%20(10)
From the above gif,you can see that when I move one node towards another node, when distance between two nodes are less, the link gets shorter.

Is there any way to make the link shorter (Adjusting=“None”) when there is a shorter distance between two nodes and when the distance is more make the Adjusting to behave as “End”?

Also, flag _AlreadyAddedPoint does not seem to work in moving the point but adds more points.


#15

Hmmm, I’m not confident that your desires are sufficiently well specified to be implementable.

I suppose you could customize the DraggingTool to dynamically set Link.Route.Adjusting on all of the Links connected with all selected Nodes based on whether the Node is moving closer or farther away from the connected Node.

But instead I think it would be better to override http://goxam.com/2.2/helpWPF/webframe.html#Northwoods.GoWPF~Northwoods.GoXam.Route~AdjustPoints.html to do what you want. The question is whether in that method you have enough information to decide whether the connected nodes are moving closer together or farther away. I believe it could get that information from the DraggingTool: http://goxam.com/2.2/helpWPF/webframe.html#Northwoods.GoWPF~Northwoods.GoXam.Tool.DraggingTool~DraggedParts.html


#16

I do not understand the use of flag _AlreadyAddedPoint in the code.
When I include the flag _AlreadyAddedPoint , the following behaviors are seen,

  1. The first point can now be moved but only Horizontally on both the sides.

com-video-to-gif%20(11)

  1. The last point still behaves the same way i.e , when the point is dragged, the link extends with more points but the last point does not seem to move at all.

com-video-to-gif%20(12)

Is there a way to make both the points (first and last) to move freely?


#17

Maybe there’s an off-by-one error in the sample code I gave you above. I’ll take a look at it.


#18

Thank you.


#19

One problem with what I gave you above is that it’s confusing simple reshaping (i.e. moving points) and adding points. It would be cleaner if they were separate operations with separate handles.

Try this code that adds a circular yellow handle at the middle of orthogonal link segments. Dragging those circular middle handles automatically inserts two points to maintain orthogonality. Dragging the square yellow handles is unchanged, I think. This custom LinkReshapingTool replaces the standard one and does not assume a custom Route.

  public class CustomLinkReshapingTool : LinkReshapingTool {
    public const String ToolCategory = "ReshapeLink";

    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;
            bool ortho = route.Orthogonal;

            // this Adornment consists of a LinkPanel holding a bunch of ToolHandles
            LinkPanel panel = new LinkPanel();
            int firstindex = route.FirstPickIndex;
            int lastindex = route.LastPickIndex;
            int orthadjust = (ortho ? 1 : 0);
            if (/*route.Resegmentable &&*/ route.Curve != LinkCurve.Bezier) {
              for (int i = firstindex+orthadjust; i < lastindex-orthadjust; i++) {
                // now the handle at the middle of each segment:
                FrameworkElement mh = new ToolHandle() {
                  Width = 8,
                  Height = 8,
                  Fill = Brushes.Yellow,
                  Stroke = Brushes.Black,
                  StrokeThickness = 1
                };
                NodePanel.SetFigure(mh, NodeFigure.Ellipse);
                // identify this particular handle within the LinkPanel
                LinkPanel.SetIndex(mh, i);  // the segment
                LinkPanel.SetFraction(mh, 0.5);  // how far along the segment
                SetReshapeBehavior(mh, ReshapeBehavior.All);
                mh.Cursor = Cursors.Hand;
                panel.Children.Add(mh);
              }
            }
            // don't bother creating handles for firstindex or lastindex
            for (int i = firstindex + 1; i < lastindex; i++) {
              // the handle at each vertex point:
              FrameworkElement h = new ToolHandle() {
                Width = 8,
                Height = 8,
                Fill = Brushes.Yellow,
                Stroke = Brushes.Black,
                StrokeThickness = 1
              };
              NodePanel.SetFigure(h, NodeFigure.Rectangle);
              // identify the segment for this particular handle
              LinkPanel.SetIndex(h, i);
              // now determines its reshape behavior and cursor, depending on whether Orthogonal et al.
              if (i == firstindex + 1 && ortho) {
                Point a = route.GetPoint(firstindex);
                Point b = route.GetPoint(firstindex + 1);
                if (IsApprox(a, b)) {
                  b = route.GetPoint(firstindex - 1);
                  if (IsApprox(a.X, b.X)) {
                    SetReshapeBehavior(h, ReshapeBehavior.Vertical);
                    h.Cursor = Cursors.SizeNS;
                  } else if (IsApprox(a.Y, b.Y)) {
                    SetReshapeBehavior(h, ReshapeBehavior.Horizontal);
                    h.Cursor = Cursors.SizeWE;
                  }
                } else if (IsApprox(a.X, b.X)) {
                  SetReshapeBehavior(h, ReshapeBehavior.Vertical);
                  h.Cursor = Cursors.SizeNS;
                } else if (IsApprox(a.Y, b.Y)) {
                  SetReshapeBehavior(h, ReshapeBehavior.Horizontal);
                  h.Cursor = Cursors.SizeWE;
                }
              } else if (i == lastindex - 1 && ortho) {
                Point a = route.GetPoint(lastindex - 1);
                Point b = route.GetPoint(lastindex);
                if (IsApprox(a, b)) {
                  a = route.GetPoint(lastindex + 1);
                  if (IsApprox(a.X, b.X)) {
                    SetReshapeBehavior(h, ReshapeBehavior.Vertical);
                    h.Cursor = Cursors.SizeNS;
                  } else if (IsApprox(a.Y, b.Y)) {
                    SetReshapeBehavior(h, ReshapeBehavior.Horizontal);
                    h.Cursor = Cursors.SizeWE;
                  }
                } else if (IsApprox(a.X, b.X)) {
                  SetReshapeBehavior(h, ReshapeBehavior.Vertical);
                  h.Cursor = Cursors.SizeNS;
                } else if (IsApprox(a.Y, b.Y)) {
                  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);
    }

    public override void DoActivate() {
      base.DoActivate();
      if (this.Handle == null) return;
      if (/*route.Resegmentable &&*/ LinkPanel.GetFraction(this.Handle) == 0.5) {
        Link link = this.AdornedLink;
        Route route = link.Route;
        int idx = this.HandleIndex+1;  // insert new point after the current index
        var pts = route.Points.ToList();
        var newPt = pts[idx];
        pts.Insert(idx, newPt);
        if (route.Orthogonal) {
          pts.Insert(idx, newPt);
        }
        route.Points = pts;
        // force the reshape handles to be re-created
        link.SetAdornment(ToolCategory, null);
        UpdateAdornments(link);
        // find the new vertex point reshape handle
        Adornment ad = link.GetAdornment(ToolCategory);
        if (ad == null) return;
        LinkPanel lp = ad.FindDescendant(e => e is LinkPanel) as LinkPanel;
        if (lp == null) return;
        this.Handle = lp.Children.OfType<ToolHandle>().FirstOrDefault(h => LinkPanel.GetIndex(h) == idx && LinkPanel.GetFraction(h) == 0);
        this.HandleIndex = idx;
        // now continue reshaping with the new vertex point handle
      }
    }

    public override void DoMouseUp() {
      Diagram diagram = this.Diagram;
      if (this.Active && diagram != null) {
        Point newpt = ComputeReshape(diagram.LastMousePointInModel);
        DoReshape(newpt);

        Link link = this.AdornedLink;
        Route route = link.Route;
        if (/*route.Resegmentable*/true) {
          int idx = this.HandleIndex;
          Point a = route.GetPoint(idx-1);
          Point b = route.GetPoint(idx);
          Point c = route.GetPoint(idx+1);
          // see if we should delete a segment because both adjacent segments are co-linear
          if (route.Orthogonal) {
            if (idx > route.FirstPickIndex+1 && idx < route.LastPickIndex-1) {
              Point q = route.GetPoint(idx-2);
              if (this.IsWithinResegmentingDistance(a, b) &&
                  (this.IsInLineOrtho(q, a, b, c, true) || this.IsInLineOrtho(q, a, b, c, false))) {
                var pts = route.Points.ToList();
                // re-align remaining two points Q and C to maintain orthogonality
                if (this.IsInLineOrtho(q, a, b, c, true)) {
                  pts[idx - 2] = new Point(q.X, (c.Y + q.Y) / 2);
                  pts[idx + 1] = new Point(c.X, (c.Y + q.Y) / 2);
                } else {
                  pts[idx - 2] = new Point((c.X + q.X) / 2, q.Y);
                  pts[idx + 1] = new Point((c.X + q.X) / 2, c.Y);
                }
                // remove points A and B
                pts.RemoveAt(idx);  // order matters!
                pts.RemoveAt(idx - 1);
                route.Points = pts;
                link.SetAdornment(ToolCategory, null);
                link.UpdateAdornments();
              } else {
                q = route.GetPoint(idx + 2);
                if (this.IsWithinResegmentingDistance(b, c) &&
                    (this.IsInLineOrtho(a, b, c, q, true) || this.IsInLineOrtho(a, b, c, q, false))) {
                  var pts = route.Points.ToList();
                  // re-align remaining two points A and Q to maintain orthogonality
                  if (this.IsInLineOrtho(a, b, c, q, true)) {
                    pts[idx - 1] = new Point(a.X, (a.Y + q.Y) / 2);
                    pts[idx + 2] = new Point(q.X, (a.Y + q.Y) / 2);
                  } else {
                    pts[idx - 1] = new Point((a.X + q.X) / 2, a.Y);
                    pts[idx + 2] = new Point((a.X + q.X) / 2, q.Y);
                  }
                  // remove points B and C
                  pts.RemoveAt(idx + 1);  // order matters!
                  pts.RemoveAt(idx);
                  route.Points = pts;
                  link.SetAdornment(ToolCategory, null);
                  link.UpdateAdornments();
                }
              }
            }
          } else {
            Point q = new Point();
            if (NearestPointOnLine(a, c, b, out q)) {
              // if B is within 3 units of the line from A to C, and if Q is between A and C,
              // remove that point from the route
              if (DistanceSquared(q, b) < this.ResegmentingDistance * this.ResegmentingDistance) {
                var pts = route.Points.ToList();
                pts.RemoveAt(idx);
                route.Points = pts;
                link.SetAdornment(ToolCategory, null);
                link.UpdateAdornments();
              }
            }
          }
        }

        diagram.Panel.UpdateDiagramBounds();
        // set the EditResult before raising event, in case it changes the result or cancels the tool
        this.TransactionResult = ToolCategory;
        RaiseEvent(Diagram.LinkReshapedEvent, new DiagramEventArgs(this.AdornedLink));
      }
      StopTool();
    }

    public static double DistanceSquared(Point a, Point b) {
      return (a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y);
    }

    private bool IsWithinResegmentingDistance(Point p1, Point p2) {
      return Math.Abs(p1.X-p2.X) < this.ResegmentingDistance && Math.Abs(p1.Y-p2.Y) < this.ResegmentingDistance;
    }

    private bool IsInLineOrtho(Point a, Point b, Point c, Point d, bool horiz) {
      if (horiz) {
        return (Math.Abs(a.Y - b.Y) < this.ResegmentingDistance &&
                Math.Abs(b.Y - c.Y) < this.ResegmentingDistance &&
                Math.Abs(c.Y - d.Y) < this.ResegmentingDistance);
      } else {
        return (Math.Abs(a.X - b.X) < this.ResegmentingDistance &&
                Math.Abs(b.X - c.X) < this.ResegmentingDistance &&
                Math.Abs(c.X - d.X) < this.ResegmentingDistance);
      }
    }

    private double ResegmentingDistance = 3.0;

    protected override void DoReshape(Point newPoint) {
      Link link = this.AdornedLink;
      Route route = link.Route;
      int index = this.HandleIndex;
      ReshapeBehavior behavior = GetReshapeBehavior(this.Handle);
      if (behavior == ReshapeBehavior.None) return;
      if (route.Orthogonal) {  // need to adjust adjacent points as well
        if (index == 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 (index == 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 = index;
          Point oldpt = route.GetPoint(i);
          Point before = route.GetPoint(i-1);
          Point after = route.GetPoint(i+1);
          if (IsApprox(before.X, oldpt.X) && IsApprox(oldpt.Y, after.Y)) {
            if (IsApprox(before.X, route.GetPoint(i-2).X) && !IsApprox(before.Y, route.GetPoint(i-2).Y)) {
              route.InsertPoint(i, new Point(newPoint.X, before.Y));
              index++;
              i++;
            } else route.SetPoint(i-1, new Point(newPoint.X, before.Y));
            if (IsApprox(after.Y, route.GetPoint(i+2).Y) && !IsApprox(after.X, route.GetPoint(i+2).X)) {
              route.InsertPoint(i+1, new Point(after.X, newPoint.Y));
            } else route.SetPoint(i+1, new Point(after.X, newPoint.Y));
          } else if (IsApprox(before.Y, oldpt.Y) && IsApprox(oldpt.X, after.X)) {
            if (IsApprox(before.Y, route.GetPoint(i-2).Y) && !IsApprox(before.X, route.GetPoint(i-2).X)) {
              route.InsertPoint(i, new Point(before.X, newPoint.Y));
              index++;
              i++;
            } else route.SetPoint(i-1, new Point(before.X, newPoint.Y));
            if (IsApprox(after.X, route.GetPoint(i+2).X) && !IsApprox(after.Y, route.GetPoint(i+2).Y)) {
              route.InsertPoint(i+1, new Point(newPoint.X, after.Y));
            } else route.SetPoint(i+1, new Point(newPoint.X, after.Y));
          } else if (IsApprox(before.X, oldpt.X) && IsApprox(oldpt.X, after.X)) {
            if (IsApprox(before.X, route.GetPoint(i-2).X) && !IsApprox(before.Y, route.GetPoint(i-2).Y)) {
              route.InsertPoint(i, new Point(newPoint.X, before.Y));
              index++;
              i++;
            } else route.SetPoint(i-1, new Point(newPoint.X, before.Y));
            if (IsApprox(after.X, route.GetPoint(i+2).X) && !IsApprox(after.Y, route.GetPoint(i+2).Y)) {
              route.InsertPoint(i+1, new Point(newPoint.X, after.Y));
            } else route.SetPoint(i+1, new Point(newPoint.X, after.Y));
          } else if (IsApprox(before.Y, oldpt.Y) && IsApprox(oldpt.Y, after.Y)) {
            if (IsApprox(before.Y, route.GetPoint(i-2).Y) && !IsApprox(before.X, route.GetPoint(i-2).X)) {
              route.InsertPoint(i, new Point(before.X, newPoint.Y));
              index++;
              i++;
            } else route.SetPoint(i-1, new Point(before.X, newPoint.Y));
            if (IsApprox(after.Y, route.GetPoint(i+2).Y) && !IsApprox(after.X, route.GetPoint(i+2).X)) {
              route.InsertPoint(i+1, new Point(after.X, newPoint.Y));
            } else route.SetPoint(i+1, new Point(after.X, newPoint.Y));
          }
          route.SetPoint(index, newPoint);
        }
      } else {  // no Orthogonal constraints, just set the new point
        route.SetPoint(this.HandleIndex, newPoint);
        // also adjust the end point if there is no spot for the port
        var fromNode = link.FromNode;
        var fromPort = link.FromPort;
        var fromSpot = Node.GetFromSpot(fromPort);
        var toNode = link.ToNode;
        var toPort = link.ToPort;
        var toSpot = Node.GetToSpot(toPort);
        if (this.HandleIndex == 1 && fromSpot.IsNoSpot) {
          var endpt = route.GetLinkPoint(fromNode, fromPort, Spot.None, true, false, toNode, toPort);
          route.SetPoint(0, endpt);
        }
        if (this.HandleIndex == route.PointsCount - 2 && toSpot.IsNoSpot) {
          var endpt = route.GetLinkPoint(toNode, toPort, Spot.None, false, false, fromNode, fromPort);
          route.SetPoint(route.PointsCount - 1, endpt);
        }
      }
    }

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

    public static bool NearestPointOnLine(Point a, Point b, Point p, out Point result) {
      // A and B are the endpoints of this segment
      double Ax = a.X;
      double Ay = a.Y;
      double Bx = b.X;
      double By = b.Y;
      // P is the point we want to get closest to
      double Px = p.X;
      double Py = p.Y;

      // handle vertical and horizontal lines specially
      if (Ax == Bx) {
        double min, max;
        if (Ay < By) {
          min = Ay;
          max = By;
        } else {
          min = By;
          max = Ay;
        }
        double newp = Py;
        if (newp < min) {
          result = new Point(Ax, min);
          return false;
        }
        if (newp > max) {
          result = new Point(Ax, max);
          return false;
        }
        result = new Point(Ax, newp);
        return true;
      } else if (Ay == By) {
        double min, max;
        if (Ax < Bx) {
          min = Ax;
          max = Bx;
        } else {
          min = Bx;
          max = Ax;
        }
        double newp = Px;
        if (newp < min) {
          result = new Point(min, Ay);
          return false;
        }
        if (newp > max) {
          result = new Point(max, Ay);
          return false;
        }
        result = new Point(newp, Ay);
        return true;
      } else {
        // ought to take sqrt to get real length, but don't bother...
        double L = (Bx - Ax) * (Bx - Ax) + (By - Ay) * (By - Ay);
        // ought to be dividing By L^2, but didn't bother to sqrt!
        double Q = ((Ax - Px) * (Ax - Bx) + (Ay - Py) * (Ay - By)) / L;

        if (Q < 0) {
          result = a;
          return false;
        } else if (Q > 1) {
          result = b;
          return false;
        } else {
          // OK to use point on line between A and B
          double x = Ax + Q * (Bx - Ax);
          double y = Ay + Q * (By - Ay);
          result = new Point(x, y);
          return true;
        }
      }
    }
  }

#20

I had a doubt regarding the orthogonality itself. Does the Routing= “Orthogonal” mean it maintains orthogonality with respect to the node or with respect to the link?

As you can see, if I add a link to the right side port of the node, then I am able to move the first handle horizontally to the left and right.
If I add a link to the bottom of the node, I am able to move the first handle vertically to the top and bottom.

  1. Does it maintain orthogonality with respect to the port here or?
  2. Will I not be able to do the vice versa, Like add link to right and move it vertically?