|
<!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> |