'WPF Canvas: Rotate a grouped shape

In my WPF application, I am trying to rotate a set of grouped shapes inside a canvas. I am getting grouped shape object TOP/LEFT position using the formulae mentioned in the below link.

Rotate a point by another point in 2D

If you rotate point (px, py) around point (ox, oy) by angle theta you'll get:

  p'x = cos(theta) * (px-ox) - sin(theta) * (py-oy) + ox
  p'y = sin(theta) * (px-ox) + cos(theta) * (py-oy) + oy

Since my rotated object is a grouped object it can contain multi level child. When I am ungrouping the grouped shape in canvas, I need to get the position and angle of the child shapes at the same position which is available in parent grouped shape object. How I can calculate the position of child object.

Sample Grouped object as shown in below image:

  • Rectangle (Parent Shape)
    • Rectangle (Child Shape 1)
    • Triangle (Child Shape 2)

Canvas grouped shape rotation Left part shows the rotated grouped shape whereas right part shows the rotated ungrouped child shapes.

For all shapes with in group, I have below information:

  • Child shape position with respect to parent group X & Y (Child_X & Child_Y)
  • Child shape angle with respect to parent group (Child_Angle)
  • Parent group angle and position with respect canvas (Parent_X, Parent_Y & Parent_Angle)
  • Width and Height of child shapes
  • Width and Height of Parent shapes

How I can get the location and angle for child shapes (Rectangle and triangle) after ungrouping? The child shapes should get placed at the same location in canvas where they are placed when they are inside the group. How I can calculate the location and angle for child shapes, Thanks in advance.

Below code I am using for storing the shape related parameters and logic for grouping and ungrouping.

/// <summary>
/// ShapeDiagram can be any shape like Rectangle/Triangle/Ellipse etc.
/// For now I am using only Rectangle template for 'ShapeDiagram' type.
/// </summary>
public class ShapeDiagram
{
    public double Left { get; set; }
    public double Top { get; set; }
    public double Angle { get; set; }
    public double Width { get; set; }
    public double Height { get; set; }
}

/// <summary>
/// Group can have more than one 'ShapeDiagram' elements. A custom shape can be created using child 'ShapeDiagram' elements.
/// User can rotate multiple 'ShapeDiagram' elements and create a group.
/// Group will have a rectangle border around the childrens as shown in the above image.
/// Group rectangle border is created using templates based on type like GroupShapeDigram or ShapeDiagram.
/// </summary>
public class GroupShapeDiagram : ShapeDiagram
{
    public ObservableCollection<ShapeDiagram> Childrens { get; set; }

}

/// <summary>
/// This class is binded to canvas and canvas child elements are binded to 'CanvasChildrens'
/// </summary>
public class ShapeHandler
{
    private ObservableCollection<ShapeDiagram> canvasChildrens;
    public ObservableCollection<ShapeDiagram> CanvasChildrens { get => canvasChildrens; set => canvasChildrens = value; }

    public ShapeHandler()
    {
        this.CanvasChildrens = new ObservableCollection<ShapeDiagram>();
    }

    public void AddCanvasChild(ShapeDiagram shape)
    {
        if(shape != null)
        {
            this.CanvasChildrens.Add(shape);
        }
    }

    public void RemoveCanvasChild(ShapeDiagram shape)
    {
        if (shape != null)
        {
            this.CanvasChildrens.Remove(shape);
        }
    }


    public GroupShapeDiagram DoGrouping(ObservableCollection<ShapeDiagram> childrens)
    {
        GroupShapeDiagram newGroup = new GroupShapeDiagram();

        newGroup.Childrens = childrens;
        newGroup.Angle = 0;
        newGroup.Top = 0;
        newGroup.Left = 0;
        newGroup.Width = GetNewGroupWidth(childrens);
        newGroup.Height = GetNewGroupHeight(childrens);

        // Add new group in the canvas child shapes collection
        this.CanvasChildrens.Add(newGroup);

        return newGroup;
    }

    public void DoUnGrouping(GroupShapeDiagram group)
    {
        // Remove selected group from the canvas child shapes collection
        this.CanvasChildrens.Remove(group);

        // TO-DO: Now adjust the group child shapes to correct location
        foreach(ShapeDiagram childShape in group.Childrens)
        {
            // *** This is where i am not getting correct values for Top/Left/Angle for child shapes after ungrouping
            childShape.Top += group.Top;
            childShape.Left += group.Left;
            childShape.Angle += group.Angle;
        }
    }

    private double GetNewGroupWidth(ObservableCollection<ShapeDiagram> childrens)
    {
        double maxWidth = 300;

        // TO-DO: Logic to get max width of all childrens with some buffer width, for now I am returning 300 as default group width

        return maxWidth;
    }

    private double GetNewGroupHeight(ObservableCollection<ShapeDiagram> childrens)
    {
        double maxHeight = 300;

        // TO-DO: Logic to get max heigth of all childrens with some buffer heigth, for now I am returning 300 as default group height

        return maxHeight;
    }

}

Here is the updated complete sample code which I am using for my application. ShapesDesignerCode

Thanks, IamHuM



Solution 1:[1]

It's not clear to me how you rotate the shapes and how you can get the required values to perform the calculations and what point you actually need. You still didn't reveal enough details to give you a working solution.

In case you need the shape position on the Canvas, you can reference the Canvas.Left and Canvas.Top properties and the angle of your group.

In case you need the new absolute location of the top-left corner of the shape, you can use the Matrix to calculate them:

double rotationAngle;
Point rotationCenter;
Point shapePositionOnCanvas;
var transformationMatrix = new Matrix();
transformationMatrix.RotateAt(rotationAngle, rotationCenter.X, rotationCenter.Y);
Point topLeftShapePosition = transformationMatrix.Transform(shapePositionOnCanvas);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 BionicCode