Home > Software engineering >  How to find rotated originX and originY
How to find rotated originX and originY

Time:07-21

Problem description with example

Let's say that at the beginning I have a group of 1 rectangle of size 50x50. Top left corner of my group has following coordinates: { x: 0, y: 0 }. Now I apply following operations:

  • I perform rotation of my group by 90° degrees what makes me to set transform: rotate with origin equal { originX: 25, originY: 25 }. My whole group is rotated by 90° from the center.
  • I add the same 50x50 rectangle to my group 10 away from the lower edge of the first rectangle. This operation makes my whole group extend by gap size and size of added rectangle. In total my whole group has size is equal to 50x110 (2x rectangle 50x50 and 1x gap 50x10), but I do not change the rotation origin just to prevent this group to move right. I want to make this group to stay in previous position and I just want to add same rectangle on the end of this group.
  • I would like to rotate my whole group to . The group should be rotated from the center of the rotated group. So, if top left coroner of my group has the following coordinates: { x: 0, y: 0 } and the size of my rotated group is equal to 50x110 then to perform this rotation I should set origin to { originX: 25, originY: 55 } (this is what I think, but I think I can be wrong). If I do what I have described above, then my whole group is going back to default position but is not rotated by 90° degrees, what I do not want.

The group position points never change, I only apply transform: rotation(angle, originX, originY) to see rotated group

Graphical representation of what I have described above

enter image description here

What I have currently achieved

function degreesToRadians(angle) {
    return Number((angle * (Math.PI / 180)).toFixed(6));
}

function getRotatedCords(x, y, centerX, centerY, angleRad) {
    const rotatedX = centerX   (x - centerX) * Math.cos(angleRad) - (y - centerY) * -Math.sin(angleRad);
    const rotatedY = centerY   (x - centerX) * -Math.sin(angleRad)   (y - centerY) * Math.cos(angleRad);

    return {
        x: rotatedX,
        y: rotatedY,
    };
}

function getRectangleCenter({ topLeft, topRight, bottomLeft, bottomRight }) {
    const centerX = (topLeft.x   topRight.x   bottomLeft.x   bottomRight.x) / 4;
    const centerY = (topLeft.y   topRight.y   bottomLeft.y   bottomRight.y) / 4;

    return {
        x: centerX,
        y: centerY
    };
}

class RectangleGroup {
    constructor(wrapper) {
        this.wrapper = wrapper;
        this.setDefaults();
    }

    get groupWidth() {
        return this.count * this.singleRectangleWidth   (this.count - 1) * this.gapSize;
    }

    get groupHeigth() {
        return this.singleRectangleWidth;
    }

    get groupCoordinates() {
        let topLeft = {
            x: this.position.x,
            y: this.position.y
        }
        let topRight = {
            x: topLeft.x   this.groupWidth,
            y: topLeft.y
        };
        let bottomLeft = {
            x: topLeft.x,
            y: topLeft.y   this.groupHeigth
        };
        let bottomRight = {
            x: topRight.x,
            y: bottomLeft. y
        };
        if(this.rotation.angleDeg !== 0) {
            const angleRad = degreesToRadians(this.rotation.angleDeg);

            topLeft = getRotatedCords(topLeft.x, topLeft.y, this.rotation.originX, this.rotation.originY, -angleRad);
            topRight = getRotatedCords(topRight.x, topRight.y, this.rotation.originX, this.rotation.originY, -angleRad);
            bottomLeft = getRotatedCords(bottomLeft.x, bottomLeft.y, this.rotation.originX, this.rotation.originY, -angleRad);
            bottomRight = getRotatedCords(bottomRight.x, bottomRight.y, this.rotation.originX, this.rotation.originY, -angleRad);
        }
        return {
            topLeft,
            topRight,
            bottomLeft,
            bottomRight
        };
    }

    setDefaults() {
        this.count = 1;
        this.singleRectangleWidth = 50;
        this.gapSize = 10;
        this.position = {
            x: 0,
            y: 0
        }
        this.rotation = {
            angleDeg: 0,
            originX: 25,
            originY: 25
        }
        this.group = this.createGroup();
    }

    createGroup() {
        const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
        group.append(this.createRectangle(this.position.x, this.position.y));
        return group;
    }

    createRectangle(x, y) {
        const rectangle = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
        rectangle.setAttribute('width', this.singleRectangleWidth.toString());
        rectangle.setAttribute('height', this.singleRectangleWidth.toString());
        rectangle.setAttribute('x', x.toString());
        rectangle.setAttribute('y', y.toString());
        return rectangle;
    }

    attach() {
        this.wrapper.append(this.group);
    }

    detach() {
        this.group.remove();
    }

    addRectangle = () => {
        const newX = this.count * this.singleRectangleWidth   this.gapSize;
        const newy = 0;
        this.group.append(this.createRectangle(newX, newy));
        this.count  = 1;
    }

    setRotation = (angleDeg) => {
        const {x: originX, y: originY} = getRectangleCenter(this.groupCoordinates);
        this.rotation.angleDeg = angleDeg;
        this.rotation.originX = originX;
        this.rotation.originY = originY;
        this.group.setAttribute('transform', `rotate(${angleDeg}, ${originX}, ${originY})`);
    }

    resetDefault = () => {
        this.detach();
        this.setDefaults();
        this.attach();
    }
}

const drawing = document.querySelector('.drawing');
const step1Btn = document.querySelector('.step-1');
const step2Btn = document.querySelector('.step-2');
const step3Btn = document.querySelector('.step-3');
const rectangleGroup = new RectangleGroup(drawing);
rectangleGroup.attach();
step1Btn.addEventListener('click', () => {
    rectangleGroup.resetDefault();
    rectangleGroup.setRotation(90);
});
step2Btn.addEventListener('click', () => {
    rectangleGroup.resetDefault();
    rectangleGroup.setRotation(90);
    rectangleGroup.addRectangle();
});
step3Btn.addEventListener('click', () => {
    rectangleGroup.resetDefault();
    rectangleGroup.setRotation(90);
    rectangleGroup.addRectangle();
    rectangleGroup.setRotation(0);
});
<!-- Set rotation 90 -->
<button >Set step 1</button>
<!-- Set rotation 90 and add rectangle in the end of group -->
<button >Set step 2</button>
<!-- Set rotation 90, add rectangle and set back rotation to 0 -->
<button >Set step 3</button>
<svg ></svg>

Do you know how I can achieve this?

CodePudding user response:

One solution is to concatentate the translations instead of trying to reset the transform.

After step 1

<g transform="rotate(90, 25, 25)">
  <rect width="50" height="50" x="0" y="0"/>
</g>

After step 2

<g transform="rotate(90, 25, 25)">
  <rect width="50" height="50" x="0" y="0"/>
  <rect width="50" height="50" x="60" y="0"/>
</g>

After step 3

<g transform="rotate(-90, 25, 55) rotate(90, 25, 25)">
  <rect width="50" height="50" x="0" y="0"/>
  <rect width="50" height="50" x="60" y="0"/>
</g>

Note that the second rotation needs be listed first here.


Another solution is to wrap the last step in a group whenever you want to modify the transform

After step 1

<g transform="rotate(90, 25, 25)">
  <rect width="50" height="50" x="0" y="0"/>
</g>

After step 2

<g>
  <g transform="rotate(90, 25, 25)">
    <rect width="50" height="50" x="0" y="0"/>
  </g>
  <rect width="50" height="50" x="0" y="60"/>
</g>

Note the position of the second rectangle is different here. It is positioned in the place you visually want it to be. You don't need to compensate for the first rotation.

After step 3

<g transform="rotate(-90, 25, 55)">
  <g transform="rotate(90, 25, 25)">
    <rect width="50" height="50" x="0" y="0"/>
  </g>
  <rect width="50" height="50" x="0" y="60"/>
</g>
  • Related