I suspect there is some way to use an element’s transformation matrix to calculate its coordinates after being transformed, but I don’t know how to do so.
An example diagram explains this best:
I’m afraid I didn’t take the math route into programming. I can sort-of follow the surface-level details of what a transformation matrix is doing, but my understanding is sort of like being able to read music one note at a time, and only very slowly; I don’t really understand the tune at a higher level, so I don’t really understand the sound of a complete musical phrase—let alone the melody.
Similarly, I don’t understand how transformation matrices work. I have tried searching for explanations to grok transformation matrices, but everything I find is loaded with more math jargon I don’t understand. I just know that they work sort of like a function, and that they are incredibly flexible tools, but that’s it.
Of all the methods available to SVGMatrix
(supposedly deprecated in favor of DOMMatrix
, but Firefox Dev. ed. is still using SVGMatrix
), I have no idea whether .inverse()
or .multiply()
is what I want, and no idea how to coax a simple set of x
and y
coordinates out of that matrix.
Note:
You can use simple trigonometric transformation:
const rotatePoint = (point, center, rotateAngle) => {
const dx = point.x - center.x;
const dy = point.y - center.y;
const distance = Math.hypot(dx, dy);
const currentAngle = Math.atan(dy / dx);
const nextAngle = currentAngle - rotateAngle;
const nextDX = distance * Math.cos(nextAngle);
const nextDY = distance * Math.sin(nextAngle);
return {x: center.x + nextDX, y: center.y + nextDY};
};
The snippet displays rotation of a blue point around the red one (30 / 90 / 123 degrees counter-clockwise)
const rotatePoint = (point, center, angle) => {
const dx = point.x - center.x;
const dy = point.y - center.y;
const distance = Math.hypot(dx, dy);
const current = Math.atan(dy / dx);
const next = current - angle;
const nextDX = distance * Math.cos(next);
const nextDY = distance * Math.sin(next);
return {x: center.x + nextDX, y: center.y + nextDY};
};
const center = {x: 150, y: 150};
const start = {x: 200, y: 30};
const svg = d3.select('svg');
svg.append('circle')
.attr('cx', center.x)
.attr('cy', center.y)
.attr('r', 5)
.style('fill', 'red');
svg.append('circle')
.attr('cx', start.x)
.attr('cy', start.y)
.attr('r', 5)
.style('fill', 'blue');
// Rotate 30 deg
const p30 = rotatePoint(start, center, Math.PI * 30 / 180);
svg.append('circle')
.attr('cx', p30.x)
.attr('cy', p30.y)
.attr('r', 5)
.style('fill', 'green');
// Rotate 90 deg
const p90 = rotatePoint(start, center, Math.PI * 90 / 180);
svg.append('circle')
.attr('cx', p90.x)
.attr('cy', p90.y)
.attr('r', 5)
.style('fill', 'orange');
// Rotate 123 deg
const p123 = rotatePoint(start, center, Math.PI * 123 / 180);
svg.append('circle')
.attr('cx', p123.x)
.attr('cy', p123.y)
.attr('r', 5)
.style('fill', 'yellow');
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="250" height="200"></svg>