Rotate a Group doesn't Rotate the childs

Hi,
I have some Nodes grouped. Now I enabled Rotatable for the NodeTemplates and the GroupTemplate, but when I rotate the Group, containing some nodes, the node don’t rotate.

Is it the same problem as in Setting Pos of a Group via binding?

Yes I believe the subjects are related.

I suppose you could customize the RotatingTool to move the member nodes to revolve around a center spot, as well as to rotate each member node by changing their angle. Note that that will not rotate any Orthogonal Links to be at odd angles.

Hi Walter,
do you have this code GoJS RotateMultipleTool for GoXam also?

I’ve tried it by myself, but I got problems with the centerPoint of the rotation.

I just searched but could not find some old tool code that implemented multiple object rotation in GoXam.

I suppose you could just translate the code from GoJS/JavaScript to GoXam/C#? But there are probably a number of subtle differences between the libraries, so that the translation cannot be mindless.

Here’s my translated code.
It is working, but there are some issues with the centerPoint. It’s not the centerPoint! On a single Node it’s the bottomLeft Point if I don’t press Ctrl. With Ctrl it’s the real centerPoint (which in my opinion is right).

Even with two selected nodes (one left, one right with same ycoord) it’s not the centerPoint of the boundingRect it’s some point on the bottomLine of the right node. With ctrl pressed it’s the real centerPoint of each node (which is also right).

public class CustomRotatingTool : RotatingTool
{
    private Point centerPoint;
    private double initalAngle;
    private Dictionary<Part, PartInfo> initialInfo;

    public override void DoActivate()
    {
        base.DoActivate();
        // center point of the collection
        centerPoint = Diagram.Panel.ComputeBounds(Diagram.SelectedParts).Center();

        // remember the angle realtive to the center point when rotating the whole collection
        initalAngle = centerPoint.DirectionPoint(Diagram.LastMousePointInModel);

        // remember initial angle and distance for each part
        var infos = new Dictionary<Part, PartInfo>();
        Diagram.SelectedParts.ForEach(p => WalkTree(p, infos));
        initialInfo = infos;
    }

    private void WalkTree(Part part, Dictionary<Part, PartInfo> infos)
    {
        if (part == null || part is Link) return;
        // distance from centerPoint to locationSpot of part
        var node = part as Node;
        if (node == null) return;
        var dist = centerPoint.DistancePoint(node.Location);
        // calculate initial relative angle
        var dir = centerPoint.DirectionPoint(node.Location);
        // saves part-angle combination in dirtionary
        infos.Add(part, new PartInfo(dir, dist, node.RotationAngle));
        // recurse into groups - Achtung! Die Group wurde oben nicht eingetütet
        var group = part as Group;
        if (group == null) return;
        using (var it = group.MemberNodes.GetEnumerator())
        {
            while (it.MoveNext())
            {
                WalkTree(it.Current, infos);
            }                
        }
    }

    /// <summary>
    /// Override Rotate to rotate all selected objects about their collective center.
    /// When the control key is held down while rotating, all selected objects are rotated individually.
    /// </summary>
    /// <param name="newangle"></param>
    protected override void DoRotate(double newangle)
    {
        Debug.WriteLine($"{centerPoint}   {OriginalAngle}   {newangle}");

        // when rotating individual parts, remember the original angle difference
        var angleDiff = newangle - AdornedNode.RotationAngle;
        var tool = this;
        initialInfo.ForEach(kvp =>
            {
                var part = kvp.Key;
                if (part is Link) return;   // only nodes and simple parts
                var partInfo = kvp.Value;
                // rotate every selected non-link part
                // find information about the part set in initialInfo
                if ((Keyboard.Modifiers & ModifierKeys.Control) > 0) // control-key pressed
                {
                    if (Equals(tool.AdornedNode, part))
                    {
                        tool.AdornedNode.RotationAngle = newangle;
                    }
                    else
                    {
                        var node = part as Node;
                        if (node != null)
                            node.RotationAngle += angleDiff;
                    }
                }
                else
                {
                    var radAngle = newangle*(Math.PI/180);  // converts the angle traveled from degrees to radians
                    // calculate the part's x-y location relative to the central rotation point
                    var offsetX = partInfo.Distance*Math.Cos(radAngle + partInfo.PlacementAngle);
                    var offsetY = partInfo.Distance*Math.Sin(radAngle + partInfo.PlacementAngle);
                    var node = part as Node;
                    if (node != null)
                    {
                        // move part
                        node.Location = new Point(tool.centerPoint.X + offsetX, tool.centerPoint.Y + offsetY);
                        // rotate part
                        node.RotationAngle = partInfo.RotationAngle + newangle;
                    }
                }
            }
        );

        //base.DoRotate(newangle);
    }

    /// <summary>
    /// This override needs to calculate the desired angle with different rotation points,
    /// depanding on wheter we are rotating the whole selection as one, or Parts individually.
    /// </summary>
    /// <param name="newPoint"></param>
    /// <returns></returns>
    protected override double ComputeRotate(Point newPoint)
    {
        double angle;

        if ((Keyboard.Modifiers & ModifierKeys.Control) > 0 || Diagram.SelectedParts.Count == 1)
        {
            // relative to the center of the Node whose handle we are rotating
            var part = AdornedNode;
            var rotationPoint = part.Location;
            angle = rotationPoint.DirectionPoint(newPoint);
        }
        else
        {
            // relative to the center of the whole selection
            angle = centerPoint.DirectionPoint(newPoint) - initalAngle;
        }
        if (angle >= 360) angle -= 360;
        else if (angle < 0) angle += 360;

        var interval = Math.Min(Math.Abs(SnapAngleMultiple), 180);
        var epsilon = Math.Min(Math.Abs(SnapAngleEpsilon), interval / 2);
        // if it's close to a multiple of INTERVAL degrees, make it exactly so
        if ((Keyboard.Modifiers & ModifierKeys.Shift) == 0 && interval > 0 && epsilon > 0)
        {
            if (angle % interval < epsilon)
            {
                angle = Math.Floor(angle / interval) * interval;
            }
            else if (angle % interval > interval - epsilon)
            {
                angle = (Math.Floor(angle / interval) + 1) * interval;
            }
            if (angle >= 360) angle -= 360;
            else if (angle < 0) angle += 360;
        }

        return angle;
    }
}

public class PartInfo
{
    public double PlacementAngle;
    public double Distance;
    public double RotationAngle;

    public PartInfo(double placementAngle, double distance, double rotationAngle)
    {
        PlacementAngle = placementAngle * (Math.PI / 180); // in radians
        Distance = distance;
        RotationAngle = rotationAngle; // in degrees
    }
}

public static class PointExtensions
{
    public static double DirectionPoint(this Point pointUr, Point point)
    {
        Vector v1 = new Vector(point.X, point.Y);
        Vector v2 = new Vector(pointUr.X, pointUr.Y);
        return Vector.AngleBetween(new Vector(1, 0), v1 - v2);
    }
    public static double DistancePoint(this Point pointUr, Point point)
    {
        Vector v1 = new Vector(point.X, point.Y);
        Vector v2 = new Vector(pointUr.X, pointUr.Y);
        return (v1 - v2).Length;
    }
}`