After flipping and rotating, the connecting lines appear to be inappropriate

My client is planning to use flip and rotate in their UI, and while searching for a solution, I came across the ucFlip.xaml file Walter created. However, when using it as a basis for multiple flips and rotations, I noticed that the connecting lines are abnormally connected. Is there a solution to this?

<UserControl x:Class="WpfWindow1.Flip"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:go="http://schemas.nwoods.com/GoXam"
             xmlns:local="clr-namespace:WpfWindow1">
    <UserControl.Resources>
        <local:FlipToTransformConverter x:Key="theFlipToTransformConverter"/>
        <local:FlipToSpotConverter x:Key="theFlipToSpotConverter"/>

        <DataTemplate x:Key="NodeTemplate">
            <Grid
            Background="Transparent"
            go:Node.LocationSpot="Center"
            go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"
            go:Part.Resizable="False" go:Part.ResizeElementName="PANEL"
                go:Part.SelectionAdorned="True"
            go:Part.Rotatable="True">
                <go:SpotPanel x:Name="PANEL" Grid.Row="0" Grid.Column="0" Background="Transparent"
                              
                        LayoutTransform="{Binding Path=Data.Flip, Converter={StaticResource theFlipToTransformConverter}}">
                    <Path x:Name="SHAPE" Data="M0 80 L0 0 50 0 M0 40 L50 40"
                  Stroke="Green" StrokeThickness="4" Margin="2 2 2 2"
                  Stretch="Fill" />
                    <Rectangle x:Name="TR" Width="8" Height="8" go:Node.PortId="TR" Fill="LightBlue" StrokeThickness="0"
                         go:SpotPanel.Spot="TopRight" go:SpotPanel.Alignment="1 0 0 0"
                         go:Node.FromSpot="{Binding Path=Data.Flip, Converter={StaticResource theFlipToSpotConverter}, ConverterParameter=R}"
                         go:Node.ToSpot="{Binding Path=Data.Flip, Converter={StaticResource theFlipToSpotConverter}, ConverterParameter=R}"
                         go:Node.LinkableFrom="True" go:Node.LinkableTo="True" />
                    <Rectangle x:Name="R" Width="8" Height="8" go:Node.PortId="R" Fill="Pink" StrokeThickness="0"
                         go:SpotPanel.Spot="MiddleRight" go:SpotPanel.Alignment="1 0.5 0 0"
                         go:Node.FromSpot="{Binding Path=Data.Flip, Converter={StaticResource theFlipToSpotConverter}, ConverterParameter=R}"
                         go:Node.ToSpot="{Binding Path=Data.Flip, Converter={StaticResource theFlipToSpotConverter}, ConverterParameter=R}"
                         go:Node.LinkableFrom="True" go:Node.LinkableTo="True" />
                    <Rectangle x:Name="B" Width="8" Height="8" go:Node.PortId="BL" Fill="LightGreen" StrokeThickness="0"
                         go:SpotPanel.Spot="BottomLeft" go:SpotPanel.Alignment="0 1 0 0"
                         go:Node.FromSpot="{Binding Path=Data.Flip, Converter={StaticResource theFlipToSpotConverter}, ConverterParameter=B}"
                         go:Node.ToSpot="{Binding Path=Data.Flip, Converter={StaticResource theFlipToSpotConverter}, ConverterParameter=B}"
                         go:Node.LinkableFrom="True" go:Node.LinkableTo="True" />
                    <Rectangle x:Name="TL" Width="8" Height="8" go:Node.PortId="TL" Fill="Yellow" StrokeThickness="0"
                         go:SpotPanel.Spot="TopLeft" go:SpotPanel.Alignment="0 0 0 0"
                         go:Node.FromSpot="{Binding Path=Data.Flip, Converter={StaticResource theFlipToSpotConverter}, ConverterParameter=L}"
                         go:Node.ToSpot="{Binding Path=Data.Flip, Converter={StaticResource theFlipToSpotConverter}, ConverterParameter=L}"
                         go:Node.LinkableFrom="True" go:Node.LinkableTo="True" />
                    <ContextMenuService.ContextMenu>
                        <ContextMenu>
                            <MenuItem Header="Flip Horizontally" Click="FlipH_Click" />
                            <MenuItem Header="Flip Vertically" Click="FlipV_Click" />
                            <MenuItem Header="Rotate 90Degree" Click="Rotate90_click" />
                        </ContextMenu>
                    </ContextMenuService.ContextMenu>
                </go:SpotPanel>
            </Grid>
        </DataTemplate>
        <DataTemplate x:Key="LinkTemplate">
            <go:LinkShape Stroke="Black" StrokeThickness="2"
                    go:Part.SelectionAdorned="True" go:Part.Reshapable="True">
                <go:Link.Route>
                    <go:Route Routing="Orthogonal" Corner="4" Curve="JumpOver" 
                    RelinkableFrom="True" RelinkableTo="True" />
                </go:Link.Route>
            </go:LinkShape>
        </DataTemplate>
    </UserControl.Resources>
    <Grid>
        <go:Diagram x:Name="myDiagram"
                Padding="10"
                HorizontalContentAlignment="Center"
                VerticalContentAlignment="Center"
                InitialScale="2"
                NodeTemplate="{StaticResource NodeTemplate}"
                LinkTemplate="{StaticResource LinkTemplate}">
            <go:Diagram.ResizingTool>
                <local:CustomResizingTool />
            </go:Diagram.ResizingTool>
        </go:Diagram>
    </Grid>
</UserControl>
using Northwoods.GoXam;
using Northwoods.GoXam.Model;
using Northwoods.GoXam.Tool;
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;

namespace WpfWindow1
{
    /// <summary>
    /// Interaction logic for Flip.xaml
    /// </summary>
    public partial class Flip : UserControl
    {
        public Flip()
        {
            InitializeComponent();

            // model is a GraphLinksModel using instances of MyNodeData as the node data
            // and MyLinkData as the link data
            var model = new GraphLinksModel<MyNodeData, String, String, MyLinkData>();

            // Flip: 0 normal, 1 flipped horizontally, 2 flipped vertically, 3 flipped both ways 
            model.NodesSource = new ObservableCollection<MyNodeData>() {
          new MyNodeData() { Key="Alpha", Location=new Point(0, 0) },
          new MyNodeData() { Key="Beta", Location=new Point(150, 50), Flip=1 },
          new MyNodeData() { Key="Gamma", Location=new Point(-80, 120), Flip=2 },
          new MyNodeData() { Key="Delta", Location=new Point(100, 200), Flip=3 }
        };

            model.LinksSource = new ObservableCollection<MyLinkData>() {
          new MyLinkData() { From="Alpha", FromPort="TR", To="Beta", ToPort="TR" },
          new MyLinkData() { From="Alpha", FromPort="BL", To="Gamma", ToPort="TR" },
          new MyLinkData() { From="Gamma", FromPort="R", To="Delta", ToPort="R" },
          new MyLinkData() { From="Beta", FromPort="BL", To="Delta", ToPort="TL" }
      };

            model.Modifiable = true;
            model.HasUndoManager = true;

            myDiagram.Model = model;
        }

        private void FlipH_Click(object sender, RoutedEventArgs e)
        {

            Console.WriteLine("FlipH_Click.01");
            var elt = sender as FrameworkElement;
            if (elt != null && elt.DataContext != null)
            {
                Console.WriteLine("FlipH_Click.02");

                //MessageBox.Show("dataType:" + ((PartManager.PartBinding)elt.DataContext).Data.GetType().ToString());
                var data = ((PartManager.PartBinding)elt.DataContext).Data as MyNodeData;
                if (data != null)
                {
                    Console.WriteLine("FlipH_Click.03");
                    myDiagram.StartTransaction("FlippedH");
                    data.Flip ^= 1;
                    myDiagram.CommitTransaction("FlippedH");
                }
                Console.WriteLine("FlipH_Click.04");
            }
        }
        private void FlipV_Click(object sender, RoutedEventArgs e)
        {
            var elt = sender as FrameworkElement;
            if (elt != null && elt.DataContext != null)
            {
                var data = ((PartManager.PartBinding)elt.DataContext).Data as MyNodeData;
                if (data != null)
                {
                    myDiagram.StartTransaction("FlippedV");
                    data.Flip ^= 2;
                    myDiagram.CommitTransaction("FlippedV");
                }
            }
        }

        private void Rotate90_click(object sender, RoutedEventArgs e)
        {
            var elt = sender as FrameworkElement;
            if (elt != null && elt.DataContext != null)
            {
                var data = ((PartManager.PartBinding)elt.DataContext).Data as MyNodeData;
                if (data != null)
                {
                    myDiagram.StartTransaction("rot90");
                    ((PartManager.PartBinding)elt.DataContext).Node.RotationAngle += 90;
                    myDiagram.CommitTransaction("rot90");
                }
            }
        }
    }

    // Define custom node data; the node key is of type String.
    // Add a property named Color that might change.
    [Serializable]  // serializable in WPF to support the clipboard
    public class MyNodeData : GraphLinksModelNodeData<String>
    {
        public String Color
        {
            get { return _Color; }
            set
            {
                if (_Color != value)
                {
                    String old = _Color;
                    _Color = value;
                    RaisePropertyChanged("Color", old, value);
                }
            }
        }
        private String _Color = "White";

        public int Flip
        {
            get { return _Flip; }
            set
            {
                if (_Flip != value)
                {
                    int old = _Flip;
                    _Flip = value;
                    RaisePropertyChanged("Flip", old, value);
                }
            }
        }
        private int _Flip = 0;
    }

    // Define custom link data; the node key is of type String,
    // the port key should be of type String but is unused in this app.
    [Serializable]  // serializable in WPF to support the clipboard
    public class MyLinkData : GraphLinksModelLinkData<String, String>
    {
        // nothing to add
    }

    // Flip: 0 normal, 1 flipped horizontally, 2 flipped vertically, 3 flipped both ways 

    public class FlipToTransformConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            switch (value)
            {
                case 1: return new ScaleTransform(-1, 1);
                case 2: return new ScaleTransform(1, -1);
                case 3: return new ScaleTransform(-1, -1);
                default: return new ScaleTransform(1, 1);
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class FlipToSpotConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var name = parameter == null ? "R" : parameter.ToString();
            if (name.Length == 0) name = "R";
            switch (name[0])
            {
                case 'T':  // top
                    switch (value)
                    {
                        case 1: return new Spot(0.5, 0);  // flipped horizontally
                        case 2: return new Spot(0.5, 1, 0, -8);  // flipped vertically
                        case 3: return new Spot(0.5, 1, 0, -8);  // flipped both
                        default: return new Spot(0.5, 0); // normal
                    }
                case 'L':  // left
                    switch (value)
                    {
                        case 1: return new Spot(1, 0.5, -8, 0);  // flipped horizontally
                        case 2: return new Spot(0, 0.5);  // flipped vertically
                        case 3: return new Spot(1, 0.5, -8, 0);  // flipped both
                        default: return new Spot(0, 0.5); // normal
                    }
                case 'R':  // right
                    switch (value)
                    {
                        case 1: return new Spot(0, 0.5, 8, 0);  // flipped horizontally
                        case 2: return new Spot(1, 0.5);  // flipped vertically
                        case 3: return new Spot(0, 0.5, 8, 0);  // flipped both
                        default: return new Spot(1, 0.5); // normal
                    }
                default:  // bottom
                    switch (value)
                    {
                        case 1: return new Spot(0.5, 1);  // flipped horizontally
                        case 2: return new Spot(0.5, 0, 0, 8);  // flipped vertically
                        case 3: return new Spot(0.5, 0, 0, 8);  // flipped both
                        default: return new Spot(0.5, 1); // normal
                    }
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

    public class CustomResizingTool : ResizingTool
    {
        public override void UpdateAdornments(Part part)
        {
            if (part == null || part is Link) return;  // can't resize links
            var ToolCategory = "Resize";
            Adornment adornment = null;
            if (part.IsSelected)
            {
                FrameworkElement selelt = null;
                //String eltname = part.ResizeElementName;
                //if (eltname != null) selelt = part.FindNamedDescendant(eltname);
                if (selelt == null) selelt = part.SelectionElement;
                if (selelt != null && part.CanResize() && Part.IsVisibleElement(selelt))
                {
                    adornment = part.GetAdornment(ToolCategory);
                    if (adornment == null)
                    {
                        DataTemplate template = part.ResizeAdornmentTemplate;
                        if (template == null) template = Diagram.FindDefault<DataTemplate>("DefaultResizeAdornmentTemplate");
                        adornment = part.MakeAdornment(selelt, template);
                        if (adornment != null)
                        {
                            adornment.Category = ToolCategory;
                            adornment.LocationSpot = Spot.TopLeft;
                        }
                    }
                    if (adornment != null)
                    {
                        Node node = (Node)part;
                        Point loc = part.GetElementPoint(selelt, Spot.TopLeft);
                        var shape = selelt as Shape;
                        if (shape != null) loc.Offset(3, 3);
                        // use the node's angle, not part.GetAngle(selelt)
                        //?? this can be wrong if there are additional rotational transforms in the tree
                        double angle = node.RotationAngle;
                        UpdateResizeHandles(adornment.VisualElement, angle);
                        adornment.Location = loc;
                        adornment.RotationAngle = angle;
                        adornment.Remeasure();
                    }
                }
            }
            part.SetAdornment(ToolCategory, adornment);
        }

        // copied from ResizingTool.cs

        private void UpdateResizeHandles(FrameworkElement elt, double angle)
        {
            if (elt == null) return;
            if (NodePanel.GetFigure(elt) != NodeFigure.None)
            {
                SetResizeCursor(elt, angle);
            }
            else
            {
                int count = VisualTreeHelper.GetChildrenCount(elt);
                for (int i = 0; i < count; i++)
                {
                    UpdateResizeHandles(VisualTreeHelper.GetChild(elt, i) as FrameworkElement, angle);
                }
            }
        }

        private void SetResizeCursor(FrameworkElement h, double angle)
        {
            Spot spot = SpotPanel.GetSpot(h);
            if (spot.IsNoSpot) spot = Spot.Center;
            double a = angle;
            if (spot.X <= 0)
            {  // left
                if (spot.Y <= 0)
                {  // top-left
                    a += 225;
                }
                else if (spot.Y >= 1)
                {  // bottom-left
                    a += 135;
                }
                else
                {  // middle-left
                    a += 180;
                }
            }
            else if (spot.X >= 1)
            {  // right
                if (spot.Y <= 0)
                {  // top-right
                    a += 315;
                }
                else if (spot.Y >= 1)
                {  // bottom-right
                    a += 45;
                }
                else
                {  // middle-right
                   // a += 0;
                }
            }
            else
            {  // middle-X
                if (spot.Y <= 0)
                {  // top-middle
                    a += 270;
                }
                else if (spot.Y >= 1)
                {  // bottom-middle
                    a += 90;
                }
                else
                {
                    // handle is in the middle-middle -- don't do anything
                    return;
                }
            }
            if (a < 0) a += 360;
            else if (a >= 360) a -= 360;
            if (a < 22.5)
                h.Cursor = Cursors.SizeWE;
            else if (a < 67.5)
                h.Cursor = Cursors.SizeNWSE;
            else if (a < 112.5)
                h.Cursor = Cursors.SizeNS;
            else if (a < 157.5)
                h.Cursor = Cursors.SizeNESW;
            else if (a < 202.5)
                h.Cursor = Cursors.SizeWE;
            else if (a < 247.5)
                h.Cursor = Cursors.SizeNWSE;
            else if (a < 292.5)
                h.Cursor = Cursors.SizeNS;
            else if (a < 337.5)
                h.Cursor = Cursors.SizeNESW;
            else
                h.Cursor = Cursors.SizeWE;
        }

        private Spot ComputeResizeSpot(FrameworkElement h)
        {
            int flip = 0;
            var data = this.AdornedNode.Data as MyNodeData;
            if (data != null) flip = data.Flip;

            Spot spot = SpotPanel.GetSpot(h);
            if (spot.IsNoSpot) spot = Spot.Center;
            double a = 0;
            if (spot.X <= 0)
            {  // left
                if (spot.Y <= 0)
                {  // top-left
                    a += 225;
                }
                else if (spot.Y >= 1)
                {  // bottom-left
                    a += 135;
                }
                else
                {  // middle-left
                    a += 180;
                }
            }
            else if (spot.X >= 1)
            {  // right
                if (spot.Y <= 0)
                {  // top-right
                    a += 315;
                }
                else if (spot.Y >= 1)
                {  // bottom-right
                    a += 45;
                }
                else
                {  // middle-right
                   // a += 0;
                }
            }
            else
            {  // middle-X
                if (spot.Y <= 0)
                {  // top-middle
                    a += 270;
                }
                else if (spot.Y >= 1)
                {  // bottom-middle
                    a += 90;
                }
                else
                {
                    // handle is in the middle-middle -- don't do anything
                    return spot;
                }
            }
            if (a < 0) a += 360;
            else if (a >= 360) a -= 360;
            var angle = a;
            if (flip == 1)
            {  // flip horizontally
                a = 180 - a;
            }
            else if (flip == 2)
            {  // flip vertically
                a = -a;
            }
            else if (flip == 3)
            {  // flipping both is same as rotating 180
                a += 180;
            }
            if (a < 0) a += 360;
            else if (a >= 360) a -= 360;
            Spot newspot = spot;
            if (a < 22.5)
                newspot = Spot.MiddleRight;
            else if (a < 67.5)
                newspot = Spot.BottomRight;
            else if (a < 112.5)
                newspot = Spot.BottomCenter;
            else if (a < 157.5)
                newspot = Spot.BottomLeft;
            else if (a < 202.5)
                newspot = Spot.MiddleLeft;
            else if (a < 247.5)
                newspot = Spot.TopLeft;
            else if (a < 292.5)
                newspot = Spot.TopCenter;
            else if (a < 337.5)
                newspot = Spot.TopRight;
            else
                newspot = Spot.MiddleRight;
            return newspot;
        }

        public override void DoActivate()
        {
            base.DoActivate();
            //var spot = SpotPanel.GetSpot(this.Handle);
            var spot = ComputeResizeSpot(this.Handle);
            var pt = this.AdornedNode.GetElementPoint(this.AdornedElement, spot.Opposite);

            int flip = 0;
            var data = this.AdornedNode.Data as MyNodeData;
            if (data != null) flip = data.Flip;
            switch (flip)
            {
                case 1:
                    if (spot.X == 1) pt.X += this.OriginalBounds.Width;
                    else if (spot.X == 0) pt.X -= this.OriginalBounds.Width;
                    break;
                case 2:
                    if (spot.Y == 1) pt.Y += this.OriginalBounds.Height;
                    else if (spot.Y == 0) pt.Y -= this.OriginalBounds.Height;
                    break;
                case 3:
                    if (spot.X == 1) pt.X += this.OriginalBounds.Width;
                    else if (spot.X == 0) pt.X -= this.OriginalBounds.Width;
                    if (spot.Y == 1) pt.Y += this.OriginalBounds.Height;
                    else if (spot.Y == 0) pt.Y -= this.OriginalBounds.Height;
                    break;
                default: break;
            }
            this.OriginalPoint = pt;
        }

        private Point OriginalPoint;

        protected override void DoResize(Rect newr)
        {
            Node node = this.AdornedNode;
            FrameworkElement elt = node.FindNamedDescendant("SHAPE");

            //var spot = SpotPanel.GetSpot(this.Handle);
            var spot = ComputeResizeSpot(this.Handle);

            elt.Width = newr.Width;
            elt.Height = newr.Height;
            node.Remeasure();

            var pt = node.GetElementPoint(elt, spot.Opposite);
            var newloc = node.Location;
            newloc.X = newloc.X - pt.X + this.OriginalPoint.X;
            newloc.Y = newloc.Y - pt.Y + this.OriginalPoint.Y;
            node.Location = newloc;
        }
    }
}

That’s odd. After changing the namespace and UserControl name to “Flip2”, to avoid name conflicts in my test app, but making no other changes to your code, here’s what I get:
image

Which version of GoXam are you using? I’m using the latest, 3.0.4.

Oh, I forgot to mention. If you delete the two blocks below, flip the unit on the right horizontally and vertically, and rotate it several times by 90 degrees, it will end up like that. I’ll send you a video by email as well.

goXam version is 3.0.0.46

I think it helps if you customize the Link.Route to be:

                <go:Link.Route>
                    <go:Route Routing="Orthogonal" Corner="4" Curve="JumpOver"
                              FromEndSegmentDirection="RotatedNodeOrthogonal" ToEndSegmentDirection="RotatedNodeOrthogonal"
                              RelinkableFrom="True" RelinkableTo="True" />
                </go:Link.Route>