Skip to content

Instantly share code, notes, and snippets.

@helderdarocha
Last active June 29, 2016 17:31
Show Gist options
  • Save helderdarocha/8b28505082bf1c81977d7dec797686c7 to your computer and use it in GitHub Desktop.
Save helderdarocha/8b28505082bf1c81977d7dec797686c7 to your computer and use it in GitHub Desktop.
Examples using matrix transforms in SVG
height: 800
license: cc-by-sa-4.0

This image shows examples of affine transforms using the matrix(a,b,c,d,dx,dy) function in SVG, where a, b, c and d are the elements of a square matrix which is used to skew, rotate and scale a coordinate system, and dx and dy are the elements of the translation vector.

The first plane (1,1) shows a 60x60 pixel square positioned at (0,0). If the coordinate system represents the screen, the view port is represented by the lower right quadrant, where y >= 0 (y increases downwards) and x >= 0 (x increases towards the right).

The second plane (1,2) shows the shape being translated by a positive value of x(to the right) and then to a positivel value of y (downwards). This could alsop be done using the translate(x,y) transformation function.

The third plane (1,3) shows a shape being scaled down by 50% on the x axis, distorting it, and then on the y axis, bringing it back to its original aspect ratio. This could be done using the scale(x) and scale(x,y) transformation functions.

The fourth plane (2,1) illustrates the rotation of the shape in three steps, by first skewing, then inverting the skew, and finally scaling it back to its original size. The skewing can be done using the skew(angle_x,angle_y) functions and the rotation using rotate(angle).

The middle plane (2,2) shows how to flip and invert shapes in a coordinate system.

The sixth plane (2,3) attempts to rotate an image at the center by translating its center to the origin of the coordinate system. But the rotation occurs relative to the top-left position of the image.

The seventh plane (3,1) calculates the translation (dx, dy) necessary to compensate the rotation for each angle, which is dx = width/2 * cos(angle) + height/2 * sin(angle) and dy = width/2 * cos(angle) - height/2 * sin(angle). Since the shape is a square with side = 60, height and width are both 30. This transformation can also be done using rotate(angle, width/2, height/2), which is simpler.

The eighth plane (3,2) performs a vertical shear, and the ninth plane (3,3) performs two shears. Then the shapes from (1,1) and (3,2) are combined to draw a cube in isometric projection.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Examples using matrix transforms in SVG</title>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style>
.square {
stroke: black;
stroke-width: 1;
}
text {
font-family: monospace;
font-size: 9pt;
}
line {
stroke-width: 1;
stroke: blue;
}
</style>
</head>
<body>
<script>
var svg = d3.select("body").append("svg").attr({
class: "planes",
width: 900,
height: 800,
viewBox: "-100 -100 900 800"
});
var colors = d3.scale.category10([3, 3]);
var shapeSide = 60;
function makeShape(parent) {
var face = parent.append("g")
.attr("viewBox", "0 0 "+shapeSide+" "+shapeSide+"");
// make a colorful square made up of 9 smaller squares to use as the reference object
var side = shapeSide/3;
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
face.append("rect")
.attr("class", "square")
.attr({width: side, height: side})
.attr("transform", "translate(" + (j * side) + "," + (i * side) + ")")
.style("opacity", .5)
.style("fill", colors([j, i]));
}
};
return face;
}
function makePlane(parent) {
var plane = parent.append("g")
.attr("viewBox", "-100 -100 200 200")
plane.append("line").attr({x1: 0, y1: -100, x2: 0, y2: 100});
plane.append("line").attr({x1: -100, y1: 0, x2: 100, y2: 0});
var text = plane.append("text")
.attr("x", 10)
.attr("y", -100)
.attr("class", "label")
text.append("tspan")
.attr("class","line-1")
.text("⎡ 1 0 ⎤")
text.append("tspan").attr("dy", 11).attr("x", 10)
.attr("class","line-2")
.text("⎣ 0 1 ⎦");
text.append("tspan").attr("dy", 20).attr("x", 10)
.attr("class","line-3")
.text("dx = 0, dy = 0");
return plane;
}
// distribute 9 planes on the view, to demonstrate the transformations
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 3; j++) {
var m = 50;
var dx = (200 + m) * j + m, dy = (200 + m) * i + m;
var plane = makePlane(svg);
var shape = makeShape(plane);
shape.attr("id", "shape_" + i + "_" + j)
.append("circle").attr("r", 2).attr("fill", "red").attr("stroke", "black");;
plane.attr("id", "plane_" + i + "_" + j)
.attr("transform", "translate("+dx+", "+dy+")");
}
}
function label(selector, line1, line2, line3, delay) {
var text = d3.select(selector)
.selectAll("text");
text.select(".line-1").transition().delay(delay).text(line1);
text.select(".line-2").transition().delay(delay).text(line2);
text.select(".line-3").transition().delay(delay).text(line3);
}
// Transformation matrix
var a = 1, b = 0, c = 0, d = 1,
dx = 0, dy = 0; // translation
var matrix = [a, b, c, d, dx, dy];
// 0,0 - no transforms
d3.select("#shape_0_0")
.attr("transform", "matrix("+matrix+")")
label("#plane_0_0", "⎡ 1 0 ⎤", "⎣ 0 1 ⎦", "dx = 0, dy = 0", 0);
d3.select("#plane_0_0")
.append("text")
.text("square").attr("x", -100).attr("y", 30)
.append("tspan")
.text("side = 60").attr("x", -100).attr("dy", 15);
// 0,1 - translate x, then y
dx = 25;
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_0_1")
.transition()
.transition().delay(1000)
.attr("transform", "matrix("+matrix+")");
label("#plane_0_1", "⎡ 1 0 ⎤", "⎣ 0 1 ⎦", "dx = 25, dy = 0", 700);
dy = 50;
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_0_1")
.transition().delay(2000)
.attr("transform", "matrix("+matrix+")");
label("#plane_0_1", "⎡ 1 0 ⎤", "⎣ 0 1 ⎦", "dx = 25, dy = 50", 1700);
// 0,2 - translate 10,10 scale x, then both
dx = 10, dy = 10,
a = .5;
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_0_2")
.transition().delay(3000)
.attr("transform", "matrix("+matrix+")");
label("#plane_0_2", "⎡ 0.5 \u00A0\u00A00 ⎤", "⎣ \u00A0\u00A00 \u00A0\u00A01 ⎦", "dx = 10, dy = 10", 2700);
d = .5;
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_0_2")
.transition().delay(4000)
.attr("transform", "matrix("+matrix+")");
label("#plane_0_2", "⎡ 0.5 \u00A0\u00A00 ⎤", "⎣ \u00A0\u00A00 0.5 ⎦", "dx = 10, dy = 10", 3700);
// 1,0 - positive y skew 30 degrees
dx = 0, dy = 0;
a = 1, b = Math.sin(60 * Math.PI/180), c = 0, d = 1;
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_1_0")
.transition().delay(5000)
.attr("transform", "matrix("+matrix+")");
label("#plane_1_0", "⎡ \u00A0\u00A01.0\u00A0\u00A0\u00A0 \u00A0sin(60) ⎤", "⎣ \u00A0\u00A00.0\u00A0\u00A0\u00A0 \u00A0\u00A0\u00A01.0\u00A0\u00A0 ⎦", "dx = 0, dy = 0", 4700);
// 1,0 - positive rotate 30 degrees - step 1
c = -Math.sin(60 * Math.PI/180), d = 1;
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_1_0")
.transition().delay(6000)
.attr("transform", "matrix("+matrix+")");
label("#plane_1_0", "⎡ \u00A0\u00A01.0\u00A0\u00A0\u00A0 \u00A0sin(60) ⎤", "⎣ -sin(60) \u00A0\u00A01.0\u00A0\u00A0\u00A0 ⎦", "dx = 0, dy = 0", 5700);
// 1,0 - positive rotate 30 degrees - step 2
a = Math.cos(60 * Math.PI/180), d = Math.cos(60 * Math.PI/180);
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_1_0")
.transition().delay(7000)
.attr("transform", "matrix("+matrix+")");
label("#plane_1_0", "⎡ \u00A0cos(60) \u00A0sin(60) ⎤", "⎣ -sin(60) \u00A0cos(60) ⎦", "dx = 0, dy = 0", 6700);
// 1,1 - rotate 90 - convert to standard cartesian (flip vertical)
a = 1, b = 0, c = 0, d = -1;
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_1_1")
.transition().delay(8000)
.attr("transform", "matrix("+matrix+")");
label("#plane_1_1", "⎡ \u00A01 \u00A00 ⎤", "⎣ \u00A00 -1 ⎦", "dx = 0, dy = 0", 7700);
// 1,1 - rotate 180 - invert position (flip diagonally)
a = -1, b = 0, c = 0, d = -1;
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_1_1")
.transition().delay(9000)
.attr("transform", "matrix("+matrix+")");
label("#plane_1_1", "⎡ -1 \u00A00 ⎤", "⎣ \u00A00 -1 ⎦", "dx = 0, dy = 0", 8700);
// 1,1 - rotate 270 - flip horizontal
a = -1, b = 0, c = 0, d = 1;
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_1_1")
.transition().delay(10000)
.attr("transform", "matrix("+matrix+")");
label("#plane_1_1", "⎡ -1 \u00A00 ⎤", "⎣ \u00A00 \u00A01 ⎦", "dx = 0, dy = 0", 9700);
// 1,2 - center, then rotate, then rotate again
a = 1, b = 0, c = 0, d = 1, dx = -30, dy = -30;
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_1_2")
.transition().delay(11000)
.attr("transform", "matrix("+matrix+")");
label("#plane_1_2", "⎡ 1 0 ⎤", "⎣ 0 1 ⎦", "dx = -30, dy = -30", 10700);
d3.select("#plane_1_2")
.append("text").attr("class", "note")
.text("center").attr("x", -100).attr("y", 30).attr("opacity", 0)
.append("tspan")
.text("side/2 = 30").attr("x", -100).attr("dy", 15);
d3.select("#plane_1_2 .note").transition().delay(10300).attr("opacity", 1);
a = Math.cos(45 * Math.PI/180),
b = -Math.sin(45 * Math.PI/180),
c = Math.sin(45 * Math.PI/180),
d = Math.cos(45 * Math.PI/180);
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_1_2")
.transition().delay(12000)
.attr("transform", "matrix("+matrix+")");
label("#plane_1_2", "⎡ \u00A0cos(45) -sin(45) ⎤", "⎣ \u00A0sin(45) \u00A0cos(60) ⎦", "dx = -side/2, dy = -side/2", 11700);
a = Math.cos(150 * Math.PI/180),
b = -Math.sin(150 * Math.PI/180),
c = Math.sin(150 * Math.PI/180),
d = Math.cos(150 * Math.PI/180);
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_1_2")
.transition().delay(13000)
.attr("transform", "matrix("+matrix+")");
label("#plane_1_2", "⎡ \u00A0cos(150) -sin(150) ⎤", "⎣ \u00A0sin(150) \u00A0cos(150) ⎦", "dx = -side/2, dy = -side/2", 12700);
// 2,0 - center, change origin, then rotate, then rotate again
a = 1, b = 0, c = 0, d = 1, dx = -shapeSide/2, dy = -shapeSide/2;
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_2_0 circle").remove();
d3.select("#plane_2_0")
.append("circle").attr("r", 2).attr("fill", "red").attr("stroke", "black");
d3.select("#shape_2_0")
.transition().delay(14000)
.attr("transform", "matrix("+matrix+")");
label("#plane_2_0", "⎡ 1 0 ⎤", "⎣ 0 1 ⎦", "dx = -30, dy = -30", 13700);
// rotation moving center to origin
a = Math.cos(30 * Math.PI/180),
b = -Math.sin(30 * Math.PI/180),
c = Math.sin(30 * Math.PI/180),
d = Math.cos(30 * Math.PI/180);
var x = -shapeSide/2, y = -shapeSide/2;
dx = (a * x + c * y),
dy = (d * x + b * y);
matrix = [a, b, c, d, dx, dy];
var format = d3.format(".2f");
d3.select("#shape_2_0")
.transition().delay(15000)
.attr("transform", "matrix("+matrix+")");
label("#plane_2_0", "⎡ \u00A0cos(30) -sin(30) ⎤", "⎣ \u00A0sin(30) \u00A0cos(30) ⎦", "dx = "+format(dx)+", dy = "+format(dy), 14700);
d3.select("#plane_2_0")
.append("text").attr("class", "note").attr("opacity", 0)
.attr("transform", "translate(-100, 120)")
.transition().delay(14300).attr("opacity", 1);
d3.select("#plane_2_0 .note").append("tspan").text("dx = side/2 * (cos(30) + sin(30))");
d3.select("#plane_2_0 .note").append("tspan").attr("x", 0).attr("dy", 15).text("dy = side/2 * (cos(30) - sin(30))");
// now rotate it a bit more
a = Math.cos(75 * Math.PI/180),
b = -Math.sin(75 * Math.PI/180),
c = Math.sin(75 * Math.PI/180),
d = Math.cos(75 * Math.PI/180);
dx = (a * x + c * y),
dy = (d * x + b * y);
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_2_0")
.transition().delay(16000)
.attr("transform", "matrix("+matrix+")");
label("#plane_2_0", "⎡ \u00A0cos(75) -sin(75) ⎤", "⎣ \u00A0sin(75) \u00A0cos(75) ⎦", "dx = "+format(dx)+", dy = "+format(dy), 15700);
d3.select("#plane_2_0 .note tspan:nth-child(1)").transition().delay(15300).text("dx = side/2 * (cos(75) + sin(75))");
d3.select("#plane_2_0 .note tspan:nth-child(2)").transition().delay(15300).attr("x", 0).attr("dy", 15).text("dy = side/2 * (cos(75) - sin(75))");
// 2,1 - skew up
// 2,1 - skew up
dx = 0, dy = 0;
a = 1, b = 0, c = 0, d = 1;
b = Math.sin(30 * Math.PI/180)
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_2_1")
.transition().delay(17000)
.attr("transform", "matrix("+matrix+")");
label("#plane_2_1", "⎡ \u00A0\u00A01.0\u00A0\u00A0\u00A0 \u00A0sin(30) ⎤", "⎣ \u00A0\u00A00.0\u00A0\u00A0\u00A0 \u00A0\u00A0\u00A01.0\u00A0\u00A0 ⎦", "dx = 0, dy = 0", 16700);
// 2,2 - skew right
dx = 0, dy = 0;
a = -1, b = 0, c = 0, d = Math.sin(30 * Math.PI/180);
b = Math.sin(30 * Math.PI/180),
c = 1;
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_2_2")
.transition().delay(18000)
.attr("transform", "matrix("+matrix+")")
.select("circle").remove();
label("#plane_2_1", "⎡ \u00A0-1.0\u00A0\u00A0\u00A0 \u00A0sin(30) ⎤", "⎣ \u00A0\u00A01.0\u00A0\u00A0\u00A0 \u00A0sin(30) ⎦", "dx = 0, dy = 0", 16700);
// 2,2 - make cube - move and skew shapes 0-0 and 2-1 to the 2-2 plane
dx = 500, dy = 500 + 60 * 2 * Math.sin(30 * Math.PI/180);
a = 1, b = 0, c = 0, d = 1;
b = -Math.sin(30 * Math.PI/180);
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_0_0")
.transition().delay(18500)
.attr("transform", "matrix("+matrix+")")
.select("circle").remove();
dx = 250 - 60 * 2 * Math.sin(30 * Math.PI/180), dy = 30 * 2 * Math.sin(30 * Math.PI/180);
a = 1, b = 0, c = 0, d = 1;
b = Math.sin(30 * Math.PI/180)
matrix = [a, b, c, d, dx, dy];
d3.select("#shape_2_1")
.transition().delay(19000)
.attr("transform", "matrix("+matrix+")")
.select("circle").remove();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment