Flipping (Mirroring) Nodes

Is there any built-in way to flip nodes? I’ve tried adding a ScaleTransform with ScaleX set to -1 to the nodes RenderTransform, but have run into a few issues doing it that way.

The first problem is that the resizing anchors are no longer around the object once it has been flipped (the lift side anchors are lined up with the right side of the object).

The second problem is that with the anchors and how I have them set up, they do not flip with the node (because they are not a descendant of the element that is being transformed, so I’m not currently expecting them to). If I move them inside the transforming element, the links created from them are drawn in the wrong direction because of the go:Node.FromSpot & go:Node.ToSpot properties set on the anchors.

Here is the node template I am using:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:go="http://schemas.nwoods.com/GoXam"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <go:BooleanBrushConverter x:Key="backgroundChooser" TrueBrush="White" FalseBrush="Transparent" />
    <go:BooleanBrushConverter x:Key="pathStrokeChooser" TrueBrush="Red" FalseBrush="Black" />

    <go:BooleanThicknessConverter x:Key="AnchorThicknessChooser" TrueThickness="1" FalseThickness="0" />
    <go:BooleanBrushConverter x:Key="AnchorStrokeChooser" TrueBrush="Black" FalseBrush="Red" />
    <BooleanToVisibilityConverter x:Key="AnchorVisibilityChooser" />

    <DataTemplate x:Key="Tanker">
        <go:SpotPanel
            go:Part.SelectionAdorned="True"       
            go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"
            
            go:Part.Resizable="{Binding Path=Data.Resizable, Mode=OneTime}"
            go:Part.ResizeElementName="Container"
            go:Part.ResizeAdornmentTemplate="{DynamicResource NodeResizeAdornmentTemplate}"
            go:Part.Rotatable="{Binding Path=Data.Rotatable, Mode=OneWay}"
            go:Node.RotationAngle="{Binding Path=Data.Angle, Mode=TwoWay}" go:Part.RotateAdornmentTemplate="{DynamicResource NodeRotationAdornmentTemplate}"
            go:Part.Reshapable="False">

            <ContextMenuService.ContextMenu>
                <ContextMenu ItemsSource="{DynamicResource NodeContextMenuTemplate}" />
            </ContextMenuService.ContextMenu>

            <Grid Name="Container"
                  Height="{Binding Path=Data.Height, Mode=TwoWay}"
                  Width="{Binding Path=Data.Width, Mode=TwoWay}">
                <Grid Background="{Binding Path=Data.isInPalette, Converter={StaticResource backgroundChooser}}">
                    <FrameworkElement.ToolTip>
                        <TextBlock Text="{Binding Path=Data.Text}" />
                    </FrameworkElement.ToolTip>

                    <Viewbox Stretch="Fill" Name="Paths">
                        <Grid UseLayoutRounding="True">
                            <Path StrokeThickness="1.0" Stroke="#ff000000" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 18.185,76.088 L 1.935,87.088"/>

                            <Path Fill="#ffffffff" Data="F1 M 299.188,53.340 C 299.188,53.340 299.268,93.000 299.188,93.090 C 292.438,100.510 283.188,99.090 283.188,99.090 L 283.188,94.840 C 270.938,58.670 234.188,77.170 234.188,90.840 C 234.158,90.840 234.188,96.590 234.188,96.590 L 210.688,96.590 L 212.768,96.550 C 212.768,96.550 212.678,24.210 212.688,24.090 C 212.938,17.340 218.688,17.840 218.688,17.840 C 218.688,17.840 239.348,17.530 242.188,17.840 C 246.768,18.340 247.688,21.340 247.688,21.340 L 254.938,38.090 C 257.098,42.170 260.688,41.960 260.688,41.960 C 260.688,41.960 286.938,42.090 286.808,41.960 C 297.098,42.840 299.188,53.340 299.188,53.340 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 268.558,96.810 C 268.558,91.040 263.878,86.360 258.108,86.360 C 252.338,86.360 247.658,91.040 247.658,96.810 C 247.658,102.580 252.338,107.260 258.108,107.260 C 263.878,107.260 268.558,102.580 268.558,96.810 Z M 258.108,79.149 C 267.858,79.149 275.768,87.060 275.768,96.810 C 275.768,106.560 267.858,114.470 258.108,114.470 C 248.358,114.470 240.448,106.560 240.448,96.810 C 240.448,87.060 248.358,79.149 258.108,79.149 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 260.658,96.810 C 260.658,95.399 259.518,94.260 258.108,94.260 C 256.698,94.260 255.558,95.399 255.558,96.810 C 255.558,98.220 256.698,99.360 258.108,99.360 C 259.518,99.360 260.658,98.220 260.658,96.810 Z M 258.108,86.360 C 263.878,86.360 268.558,91.040 268.558,96.810 C 268.558,102.580 263.878,107.260 258.108,107.260 C 252.338,107.260 247.658,102.580 247.658,96.810 C 247.658,91.040 252.338,86.360 258.108,86.360 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 258.108,94.260 C 259.518,94.260 260.658,95.399 260.658,96.810 C 260.658,98.220 259.518,99.360 258.108,99.360 C 256.698,99.360 255.558,98.220 255.558,96.810 C 255.558,95.399 256.698,94.260 258.108,94.260 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 211.958,6.180 L 210.188,6.180 L 210.168,7.530 L 210.148,7.570 L 206.508,7.570 L 206.548,6.180 L 204.778,6.180 C 204.778,6.180 205.368,4.230 208.368,4.230 C 211.368,4.230 211.958,6.180 211.958,6.180 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 210.688,7.590 L 210.688,96.590 L 205.938,96.590 L 205.938,90.840 L 205.938,85.340 L 205.938,75.840 L 205.938,7.590 L 210.688,7.590 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 205.938,85.340 L 205.938,90.840 L 205.848,90.840 L 166.228,90.840 L 166.228,85.340 L 166.598,85.340 L 205.848,85.340 L 205.938,85.340 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 205.938,75.840 L 205.938,85.340 L 205.848,85.340 L 166.598,85.340 L 166.228,85.340 L 166.228,76.090 L 166.228,75.840 L 182.188,75.840 L 205.848,75.840 L 205.938,75.840 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 182.188,2.590 C 198.098,7.670 217.938,46.090 182.188,69.340 L 182.188,69.590 L 19.188,69.590 L 19.188,69.340 C -13.902,45.000 3.768,10.000 20.188,2.590 C 20.188,2.740 48.008,2.710 71.188,2.670 L 84.598,2.670 L 84.578,2.640 C 93.238,2.620 99.758,2.600 101.488,2.590 C 103.858,2.600 110.028,2.620 117.958,2.630 L 117.938,2.670 L 136.218,2.670 C 158.198,2.710 182.188,2.730 182.188,2.590 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 182.188,69.590 L 182.188,75.840 L 166.228,75.840 L 160.768,75.840 L 105.598,75.840 L 2.228,75.840 L 1.938,75.840 L 1.938,69.590 L 19.188,69.590 L 182.188,69.590 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 160.768,76.010 L 160.768,96.340 L 105.598,96.340 L 105.598,75.840 L 160.768,75.840 L 160.768,76.010 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 152.268,0.500 L 153.598,2.670 L 136.218,2.670 C 129.738,2.660 123.428,2.650 117.958,2.630 L 119.268,0.500 L 152.268,0.500 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 89.558,96.810 C 89.558,91.040 84.878,86.360 79.108,86.360 C 73.338,86.360 68.658,91.040 68.658,96.810 C 68.658,102.580 73.338,107.260 79.108,107.260 C 84.878,107.260 89.558,102.580 89.558,96.810 Z M 79.108,79.149 C 88.858,79.149 96.768,87.060 96.768,96.810 C 96.768,106.560 88.858,114.470 79.108,114.470 C 69.358,114.470 61.448,106.560 61.448,96.810 C 61.448,87.060 69.358,79.149 79.108,79.149 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 81.658,96.810 C 81.658,95.399 80.518,94.260 79.108,94.260 C 77.698,94.260 76.558,95.399 76.558,96.810 C 76.558,98.220 77.698,99.360 79.108,99.360 C 80.518,99.360 81.658,98.220 81.658,96.810 Z M 79.108,86.360 C 84.878,86.360 89.558,91.040 89.558,96.810 C 89.558,102.580 84.878,107.260 79.108,107.260 C 73.338,107.260 68.658,102.580 68.658,96.810 C 68.658,91.040 73.338,86.360 79.108,86.360 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 83.268,0.500 L 84.578,2.640 C 80.498,2.650 75.938,2.660 71.188,2.670 L 48.938,2.670 L 50.268,0.500 L 83.268,0.500 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 79.108,94.260 C 80.518,94.260 81.658,95.399 81.658,96.810 C 81.658,98.220 80.518,99.360 79.108,99.360 C 77.698,99.360 76.558,98.220 76.558,96.810 C 76.558,95.399 77.698,94.260 79.108,94.260 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 49.558,96.810 C 49.558,91.040 44.878,86.360 39.108,86.360 C 33.338,86.360 28.658,91.040 28.658,96.810 C 28.658,102.580 33.338,107.260 39.108,107.260 C 44.878,107.260 49.558,102.580 49.558,96.810 Z M 39.108,79.149 C 48.858,79.149 56.768,87.060 56.768,96.810 C 56.768,106.560 48.858,114.470 39.108,114.470 C 29.358,114.470 21.448,106.560 21.448,96.810 C 21.448,87.060 29.358,79.149 39.108,79.149 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 41.658,96.810 C 41.658,95.399 40.518,94.260 39.108,94.260 C 37.698,94.260 36.558,95.399 36.558,96.810 C 36.558,98.220 37.698,99.360 39.108,99.360 C 40.518,99.360 41.658,98.220 41.658,96.810 Z M 39.108,86.360 C 44.878,86.360 49.558,91.040 49.558,96.810 C 49.558,102.580 44.878,107.260 39.108,107.260 C 33.338,107.260 28.658,102.580 28.658,96.810 C 28.658,91.040 33.338,86.360 39.108,86.360 Z"/>
                            <Path Fill="#ffffffff" Data="F1 M 39.108,94.260 C 40.518,94.260 41.658,95.399 41.658,96.810 C 41.658,98.220 40.518,99.360 39.108,99.360 C 37.698,99.360 36.558,98.220 36.558,96.810 C 36.558,95.399 37.698,94.260 39.108,94.260 Z"/>


                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 1.938,102.090 L 1.938,75.840 L 1.938,69.590 L 19.188,69.590 L 182.188,69.590 L 182.188,75.840"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 2.228,75.840 L 105.598,75.840 L 160.768,75.840 L 166.228,75.840 L 182.188,75.840 L 205.848,75.840"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 166.228,76.090 L 166.228,85.340 L 166.228,90.840 L 205.848,90.840"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 205.848,85.340 L 166.598,85.340"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 210.688,96.590 L 234.188,96.590 C 234.188,96.590 234.158,90.840 234.188,90.840 C 234.188,77.170 270.938,58.670 283.188,94.840 L 283.188,99.090 C 283.188,99.090 292.438,100.510 299.188,93.090 C 299.268,93.000 299.188,53.340 299.188,53.340 C 299.188,53.340 297.098,42.840 286.808,41.960 C 286.938,42.090 260.688,41.960 260.688,41.960 C 260.688,41.960 257.098,42.170 254.938,38.090 L 247.688,21.340 C 247.688,21.340 246.768,18.340 242.188,17.840 C 239.348,17.530 218.688,17.840 218.688,17.840 C 218.688,17.840 212.938,17.340 212.688,24.090 C 212.678,24.210 212.768,96.550 212.768,96.550 L 210.688,96.590 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 210.688,96.590 L 210.688,7.590 L 205.938,7.590 L 205.938,75.840 L 205.938,85.340 L 205.938,90.840 L 205.938,96.590 L 210.688,96.590"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 41.658,96.810 C 41.658,98.220 40.518,99.360 39.108,99.360 C 37.698,99.360 36.558,98.220 36.558,96.810 C 36.558,95.399 37.698,94.260 39.108,94.260 C 40.518,94.260 41.658,95.399 41.658,96.810 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 49.558,96.810 C 49.558,102.580 44.878,107.260 39.108,107.260 C 33.338,107.260 28.658,102.580 28.658,96.810 C 28.658,91.040 33.338,86.360 39.108,86.360 C 44.878,86.360 49.558,91.040 49.558,96.810 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 56.768,96.810 C 56.768,106.560 48.858,114.470 39.108,114.470 C 29.358,114.470 21.448,106.560 21.448,96.810 C 21.448,87.060 29.358,79.149 39.108,79.149 C 48.858,79.149 56.768,87.060 56.768,96.810 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 81.658,96.810 C 81.658,98.220 80.518,99.360 79.108,99.360 C 77.698,99.360 76.558,98.220 76.558,96.810 C 76.558,95.399 77.698,94.260 79.108,94.260 C 80.518,94.260 81.658,95.399 81.658,96.810 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 89.558,96.810 C 89.558,102.580 84.878,107.260 79.108,107.260 C 73.338,107.260 68.658,102.580 68.658,96.810 C 68.658,91.040 73.338,86.360 79.108,86.360 C 84.878,86.360 89.558,91.040 89.558,96.810 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 96.768,96.810 C 96.768,106.560 88.858,114.470 79.108,114.470 C 69.358,114.470 61.448,106.560 61.448,96.810 C 61.448,87.060 69.358,79.149 79.108,79.149 C 88.858,79.149 96.768,87.060 96.768,96.810 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 260.658,96.810 C 260.658,98.220 259.518,99.360 258.108,99.360 C 256.698,99.360 255.558,98.220 255.558,96.810 C 255.558,95.399 256.698,94.260 258.108,94.260 C 259.518,94.260 260.658,95.399 260.658,96.810 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 268.558,96.810 C 268.558,102.580 263.878,107.260 258.108,107.260 C 252.338,107.260 247.658,102.580 247.658,96.810 C 247.658,91.040 252.338,86.360 258.108,86.360 C 263.878,86.360 268.558,91.040 268.558,96.810 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 275.768,96.810 C 275.768,106.560 267.858,114.470 258.108,114.470 C 248.358,114.470 240.448,106.560 240.448,96.810 C 240.448,87.060 248.358,79.149 258.108,79.149 C 267.858,79.149 275.768,87.060 275.768,96.810 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 101.488,2.590 C 99.758,2.600 93.238,2.620 84.578,2.640 C 80.498,2.650 75.938,2.660 71.188,2.670 C 48.008,2.710 20.188,2.740 20.188,2.590 C 3.768,10.000 -13.902,45.000 19.188,69.340"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 182.188,69.340 C 217.938,46.090 198.098,7.670 182.188,2.590 C 182.188,2.730 158.198,2.710 136.218,2.670 C 129.738,2.660 123.428,2.650 117.958,2.630 C 110.028,2.620 103.858,2.600 101.488,2.590 C 100.808,2.590 100.438,2.590 100.438,2.590"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 105.598,75.840 L 105.598,96.340 L 160.768,96.340 L 160.768,76.010"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 84.578,2.640 L 83.268,0.500 L 50.268,0.500 L 48.938,2.670 L 71.188,2.670 L 84.598,2.670 L 84.578,2.640 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 117.958,2.630 L 117.938,2.670 L 136.218,2.670 L 153.598,2.670 L 152.268,0.500 L 119.268,0.500 L 117.958,2.630 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeMiterLimit="1.0" Data="F1 M 208.368,4.230 C 211.368,4.230 211.958,6.180 211.958,6.180 L 210.188,6.180 L 210.168,7.530 L 210.148,7.570 L 206.508,7.570 L 206.548,6.180 L 204.778,6.180 C 204.778,6.180 205.368,4.230 208.368,4.230 Z"/>


                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 206.551,6.175 C 206.551,6.133 210.186,6.175 210.186,6.175"/>

                            <!--
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 1.935,102.083 L 1.935,69.583 L 182.184,69.583 L 182.184,75.833"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 18.185,76.083 L 1.935,87.083"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 2.226,75.833 L 205.850,75.833"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 166.225,76.083 L 166.225,90.833 L 205.850,90.833"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 205.852,85.333 L 166.602,85.333"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 210.684,96.583 L 210.684,7.583 L 205.934,7.583 L 205.934,96.583 L 234.184,96.583 C 234.184,96.583 234.159,90.834 234.184,90.834 C 234.184,77.167 270.934,58.667 283.184,94.833 L 283.184,99.083 C 283.184,99.083 292.434,100.500 299.184,93.083 C 299.264,92.997 299.184,53.333 299.184,53.333 C 299.184,53.333 297.102,42.834 286.809,41.959 C 286.934,42.084 260.684,41.959 260.684,41.959 C 260.684,41.959 257.102,42.167 254.934,38.083 L 247.684,21.333 C 247.684,21.333 246.768,18.333 242.184,17.833 C 239.350,17.524 218.684,17.833 218.684,17.833 C 218.684,17.833 212.934,17.333 212.684,24.083 C 212.680,24.201 212.768,96.542 212.768,96.542 L 210.684,96.583 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 41.655,96.803 C 41.655,98.211 40.514,99.352 39.106,99.352 C 37.699,99.352 36.558,98.211 36.558,96.803 C 36.558,95.397 37.699,94.255 39.106,94.255 C 40.514,94.255 41.655,95.397 41.655,96.803 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 49.556,96.803 C 49.556,102.575 44.877,107.253 39.106,107.253 C 33.336,107.253 28.656,102.575 28.656,96.803 C 28.656,91.034 33.336,86.354 39.106,86.354 C 44.877,86.354 49.556,91.034 49.556,96.803 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 56.766,96.803 C 56.766,106.555 48.858,114.463 39.106,114.463 C 29.355,114.463 21.447,106.555 21.447,96.803 C 21.447,87.052 29.355,79.144 39.106,79.144 C 48.858,79.144 56.766,87.052 56.766,96.803 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 81.655,96.803 C 81.655,98.211 80.514,99.352 79.106,99.352 C 77.699,99.352 76.558,98.211 76.558,96.803 C 76.558,95.397 77.699,94.255 79.106,94.255 C 80.514,94.255 81.655,95.397 81.655,96.803 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 89.556,96.803 C 89.556,102.575 84.877,107.253 79.106,107.253 C 73.336,107.253 68.656,102.575 68.656,96.803 C 68.656,91.034 73.336,86.354 79.106,86.354 C 84.877,86.354 89.556,91.034 89.556,96.803 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 96.766,96.803 C 96.766,106.555 88.858,114.463 79.106,114.463 C 69.355,114.463 61.447,106.555 61.447,96.803 C 61.447,87.052 69.355,79.144 79.106,79.144 C 88.858,79.144 96.766,87.052 96.766,96.803 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 260.655,96.803 C 260.655,98.211 259.514,99.352 258.106,99.352 C 256.700,99.352 255.557,98.211 255.557,96.803 C 255.557,95.397 256.700,94.255 258.106,94.255 C 259.514,94.255 260.655,95.397 260.655,96.803 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 268.555,96.803 C 268.555,102.575 263.877,107.253 258.106,107.253 C 252.336,107.253 247.657,102.575 247.657,96.803 C 247.657,91.034 252.336,86.354 258.106,86.354 C 263.877,86.354 268.555,91.034 268.555,96.803 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 275.766,96.803 C 275.766,106.555 267.858,114.463 258.106,114.463 C 248.354,114.463 240.446,106.555 240.446,96.803 C 240.446,87.052 248.354,79.144 258.106,79.144 C 267.858,79.144 275.766,87.052 275.766,96.803 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 182.184,69.333 C 217.934,46.083 198.102,7.667 182.184,2.583 C 182.184,2.833 100.434,2.583 100.434,2.583 L 101.935,2.583 C 101.935,2.583 20.185,2.833 20.185,2.583 C 3.768,10.000 -13.899,45.000 19.184,69.333"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 105.602,75.833 L 105.602,96.333 L 160.768,96.333 L 160.768,76.000"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 48.935,2.667 L 84.602,2.667 L 83.268,0.500 L 50.268,0.500 L 48.935,2.667 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 117.935,2.667 L 153.602,2.667 L 152.268,0.500 L 119.269,0.500 L 117.935,2.667 Z"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 208.370,4.227 C 211.370,4.227 211.961,6.171 211.961,6.171 L 210.186,6.171 L 210.165,7.523 L 210.145,7.565 L 206.510,7.565 L 206.551,6.171 L 204.778,6.171 C 204.778,6.171 205.368,4.227 208.370,4.227"/>
                            <Path StrokeThickness="1.0" Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource pathStrokeChooser}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 206.551,6.171 C 206.551,6.128 210.186,6.171 210.186,6.171"/>
                            -->
                        </Grid>
                    </Viewbox>
                </Grid>

                <Rectangle Fill="Transparent" Margin="13" Cursor="SizeAll" />
            </Grid>

            <Rectangle Fill="Transparent" Width="5" Height="5" Name="L"
                           go:SpotPanel.Alignment="MiddleLeft" go:SpotPanel.Spot="0.0333 0.5 0 0"
                       
                           Visibility="{Binding Path=Data.isInDiagram, Converter={StaticResource AnchorVisibilityChooser}}"
                           StrokeThickness="{Binding Path=Node.IsMouseOver, Converter={StaticResource AnchorThicknessChooser}}"
                           Stroke="{Binding Path=Data.HasError.Value, Converter={StaticResource AnchorStrokeChooser}}"
                       
                           go:Node.PortId="L" go:Node.LinkableFrom="True" go:Node.LinkableTo="True" Cursor="Hand"       
                           go:Node.FromSpot="MiddleLeft" go:Node.ToSpot="MiddleLeft" />
        </go:SpotPanel>
    </DataTemplate>
</ResourceDictionary>

And here is the code for the transform:


private void FlipNode(object sender, RoutedEventArgs e)
        {
            Node node = this.myDiagram.SelectedNode;

            FrameworkElement container = node.FindNamedDescendant("Container") as FrameworkElement;

            if (container.RenderTransform is ScaleTransform)
            {
                container.RenderTransform = null;
            }
            else
            {
                container.RenderTransformOrigin = new Point(0.5, 0.5);
                container.RenderTransform = new ScaleTransform(-1, 1);
            }

            return;       
        }

Any suggestions as to how I can get this working?

Thanks
Ryan

Is there a reason you can’t change the FromSpot and ToSpot of all of the port elements? Since you are just flipping in the X coordinates, you need to replace Spot.TopLeft with Spot.TopRight, etc.

I now have the following:


private void FlipNode(object sender, RoutedEventArgs e)
        {
            Node node = this.myDiagram.SelectedNode;

            FrameworkElement container = node.FindNamedDescendant("Container") as FrameworkElement;

            if (container.RenderTransform is ScaleTransform)
            {
                container.RenderTransform = null;
            }
            else
            {
                container.RenderTransformOrigin = new Point(0.5, 0.5);
                container.RenderTransform = new ScaleTransform(-1, 1);
            }

            foreach (FrameworkElement port in node.Ports)
                FlipPort(port);

            foreach (Link link in node.LinksConnected)
                link.Route.RecomputePoints();
        }

        private void FlipPort(FrameworkElement port)
        {
            Spot spot = SpotPanel.GetSpot(port);
            spot = new Spot((1 - spot.X), spot.Y);

            SpotPanel.SetSpot(port, spot);


            Spot linkSpot = Node.GetFromSpot(port);

            if (linkSpot == Spot.MiddleLeft)
                linkSpot = Spot.MiddleRight;
            else if (linkSpot == Spot.MiddleRight)
                linkSpot = Spot.MiddleLeft;

            Node.SetFromSpot(port, linkSpot);
            Node.SetToSpot(port, linkSpot);

            if (port.Name.Contains("L") || port.Name.Contains("R"))
                SpotPanel.SetAlignment(port, linkSpot);
        }

And this is what I am seeing…

Initial:

Link Drawn:

Flipped:

Deselected & reselected:

That’s interesting. I had created a similar application in Silverlight a while ago, so I ported it to WPF. (You are using WPF, yes?) Interestingly, it failed in both of the same ways that you have observed. This must be yet another subtle difference between Silverlight and WPF.

Regarding links connecting to flipped ports, it appears that the order in which the arrangement/layout of the node is such that a port still has its old position when the connected link is rerouted. You don’t need to call RecomputePoints or anything like that–it does properly invalidate the routes of connected links.

Use this method (adapted from our implementation):

internal void InvalidateVisual(UIElement elt) { if (elt != null) { elt.InvalidateVisual(); System.Windows.Media.Visual fe = elt; // WPF only while (fe != null && fe != this) { var e = fe as UIElement; if (e != null) { e.InvalidateMeasure(); } fe = System.Windows.Media.VisualTreeHelper.GetParent(fe) as System.Windows.Media.Visual; } } }
and then replace:
foreach (Link link in node.LinksConnected) link.Route.RecomputePoints();
with:
InvalidateVisual(node.VisualElement);
Or call node.FindNamedDescendant(“name of SpotPanel holding your ports”) if that panel is not the root element of your node template.

Regarding the second problem, where the adornment is in the wrong place: does it remain in the wrong place as you perform subsequent flips or rotates?

Actually I could post that app I was talking about, but it has a lot of features you might not care about, so it’s more complicated than needed for expository purposes. We ought to add something like it as yet another sample.

The anchors do remain in the wrong place after rotating/resizing the flipped node, but when the node is flipped back to the original position (and after the node is moved or selected/deselected) the anchors go back to the right spot.


In the same way, the anchors remain in the correct position after flipping until the node is moved or selected/deselected.

Are the links routed correctly when you call InvalidateVisual (instead of calling RecomputePoints)?

Yes that is fixing the linking problem, thanks…

I’ve found that removing go:Part.ResizeElementName=“Container” from the root element makes it so the resize anchors are in the right position after flipping, but then the object doesn’t resize properly.

Not sure if this is the best way to go about it, but I’ve got everything working by adding

Height="{Binding Path=Data.Height, Mode=TwoWay}"
Width="{Binding Path=Data.Width, Mode=TwoWay}"

to the root element, and changing the Mode for the height and width on the Container to OneWay.

well, I know it’s a pretty old thread but what I am facing is the same issue, so just thought to reply on this thread. While Mirroring the nodes – By the above solution I am able to mirror a Node but Adornments are not getting updated. Situation remains the same as above image is depicting shared by rwdial . However, I am not facing any issue in resizing the Node. I’ve tried different function UpdateAdornments(), UpdateLayout(), InvalidateArrange() but none of them really worked. just to add on I am trying all this for WPF (GoXam). What can be the possible ways to update Adornments along with Node ?

Do you have before-and-after screenshots for the problem that you have?
When do you want to update the Adornment(s)?

Hey Walter, As i’ve mentioned above image shared by rwdial is the same problem I am facing.

I think you don’t want to transform the whole node, but only that which is inside the outer panel.

No, I would like to transform the whole node not just the outer panel. expected behavior is it should transform (flip) on it’s same position including Adornments. But as we can see in above image Adornments are going off the track after flipping the node.

After flipping a selected node, you are saying that the resizing handles do not surround the contents of the node. Is that correct?

Do the handles appear correctly if the user deselects the node and then reselects it? If calling Part.UpdateAdornments didn’t work, I’m wondering if you need to call:
node.SetAdornment("Resize", null);
and then call:
node.UpdateAdornments();

After flipping a selected node, you are saying that the resizing handles do not surround the contents of the node. Is that correct?
Ans: Yes

Do the handles appear correctly if the user deselects the node and then reselects it?
Ans: No, That’s the problem, In Starting(after flipping) resizing handles appears on the correct position but as I deselect and reselects the node, handles do not surround the content. (like shown in the image above.)

I’ve tried the below option too like you suggested but the handles are still on the same place, still no luck.
node.SetAdornment(“Resize”, null);
node.UpdateAdornments();

FYI, I am trying the same code written by rwdial (Apr '12). (Code)

But you said you wanted to flip the whole Node, whereas rwdial was only changing the rendering of the element named “Container” and was explicitily adjusting the positions and orientations and spots of the ports.

yes, I wanted to flip the whole node and I know rwdial is just rendering the element. While i was going through with the GoXam documentation, I was unable to find any direct way to flip the node. so I opt the above way of doing it.

So, Is there any other way to achieve this directly rather then just rendering it or we can do some hacks to update the adorments in code above as it’s fulfilling rest all things like flipping port and all ?

Yes, the reason there is no built-in way to “flip” a node is because there are so many kinds of effects that you might reasonably want. And it is also an uncommon thing to want to do.

Instead of modifying the RenderTransform, try modifying the LayoutTransform of the nested element. That way the rest of the elements in the Node that contain that flipped element will naturally adapt to the changed bounds of that transformed element.