|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> |
|
<link rel="stylesheet" type="text/css" href="style.css"> |
|
<script src="scrubbing.js"></script> |
|
<style> |
|
.sin { |
|
background-color: #bdeae8 !important; |
|
} |
|
.cos { |
|
background-color: #bbb9de !important; |
|
} |
|
</style> |
|
</head> |
|
|
|
<body> |
|
<svg width=960 height=250></svg> |
|
<div id="math"> |
|
<div class="angle-container unselectable"> |
|
<div>Angle:</div> <input class="angle" value="0"/> |
|
|
|
<span class="unit sin">sin(<span class="angle-in">0</span>) = <span class="sin-out">0</span></span> |
|
<span class="unit cos">cos(<span class="angle-in">0</span>) = <span class="cos-out">1</span></span> |
|
<!-- add dial to directly rotate --> |
|
</div> |
|
<div id="mtx_transform" class="matrix unselectable" style="width:180px"> |
|
<div class="transforms cos"></div><div class="transforms sin"></div> <input value="0.0"/> |
|
<div class="transforms sin"></div><div class="transforms cos"/></div> <input value="0.0"/> |
|
<div plain style="position: absolute; top: 120px;">0</div> |
|
<div plain style="position: absolute; top: 120px; left:70px">0</div> |
|
<div plain style="position: absolute; top: 120px; left:130px">1</div> |
|
<div class="label"> |
|
the transformation matrix |
|
<br> |
|
<span>(adjust the numbers!)</span> |
|
</div> |
|
</div> |
|
<div id="mtx_input" class="matrix unselectable" style="width:60px"> |
|
<div plain style="position: absolute; top: 0px;">x</div> |
|
<div plain style="position: absolute; top: 60px;">y</div> |
|
<div plain style="position: absolute; top: 120px;">1</div> |
|
<div class="label"> |
|
a vector |
|
<br> |
|
<span>(hover over the dots)</span> |
|
</div> |
|
</div> |
|
|
|
<div class="equals"></div> |
|
<div id="mtx_output" class="matrix" style="width:60px"> |
|
<div>x'</div> |
|
<div>y'</div> |
|
<div>1</div> |
|
<div class="label"> |
|
new vector |
|
<br> |
|
<span>(hover over the dots)</span> |
|
</div> |
|
</div> |
|
|
|
</div> |
|
<script> |
|
var transform = {}; // global transform |
|
var t = transform; // convenience |
|
var bullets = []; // global data |
|
|
|
var mtx_inputs = document.querySelectorAll("#mtx_input div"); |
|
var mtx_outputs = document.querySelectorAll("#mtx_output div"); |
|
var mtx_transforms = document.querySelectorAll("#mtx_transform input"); |
|
|
|
var transforms = d3.selectAll("div.transforms"); |
|
var angle = d3.select("input.angle").node(); |
|
var angleIn = d3.selectAll("span.angle-in"); |
|
var sinOut = d3.select("span.sin-out") |
|
var cosOut = d3.select("span.cos-out") |
|
|
|
function calculate(x,y){ |
|
x = x || 0; |
|
y = y || 0; |
|
var x2 = t.a*x + t.b*y + t.tx; |
|
var y2 = t.c*x + t.d*y + t.ty; |
|
return {x:x2, y:y2}; |
|
} |
|
|
|
function render() { |
|
var xscale = d3.scale.linear() |
|
.domain([-1, 1]) |
|
.range([350, 510]); |
|
var yscale = d3.scale.linear() |
|
.domain([-1, 1]) |
|
.range([200, 50]) |
|
|
|
var transformed = bullets.map(function(d) { |
|
return calculate(d.x, d.y) |
|
}) |
|
|
|
var svg = d3.select("svg"); |
|
|
|
function hover(d,i) { |
|
mtx_inputs[0].innerHTML = bullets[i].x.toFixed(1); |
|
mtx_inputs[1].innerHTML = bullets[i].y.toFixed(1); |
|
d3.select(mtx_inputs[0]).style("border", "3px solid red"); |
|
d3.select(mtx_inputs[1]).style("border", "3px solid red"); |
|
function filter(f,j) { return j === i } |
|
d3.selectAll("line") |
|
.filter(filter).style("stroke", "red") |
|
d3.selectAll("circle.bullet") |
|
.filter(filter).style("stroke", "red") |
|
d3.selectAll("circle.transformed") |
|
.filter(filter).style({stroke:"red", fill:"red"}) |
|
} |
|
function mouseout(d,i) { |
|
mtx_inputs[0].innerHTML = "x"; |
|
mtx_inputs[1].innerHTML = "y"; |
|
d3.select(mtx_inputs[0]).style("border", "3px solid #eee"); |
|
d3.select(mtx_inputs[1]).style("border", "3px solid #eee"); |
|
d3.selectAll("line").style("stroke", "#111"); |
|
d3.selectAll("circle.bullet").style("stroke", "#111") |
|
d3.selectAll("circle.transformed").style({stroke:"#111", fill:"#111"}) |
|
} |
|
|
|
var lines = svg.selectAll("line") |
|
.data(bullets) |
|
lines.enter().append("line") |
|
.on("mouseover", hover) |
|
.on("mouseout", mouseout) |
|
lines |
|
.transition() |
|
.duration(170) |
|
.ease("linear") |
|
.attr({ |
|
x1: function(d,i) { return xscale(d.x) }, |
|
y1: function(d,i) { return yscale(d.y) }, |
|
x2: function(d,i) { return xscale(transformed[i].x)}, |
|
y2: function(d,i) { return yscale(transformed[i].y)}, |
|
stroke: "#111" |
|
}) |
|
|
|
|
|
|
|
var circlesB = svg.selectAll("circle.bullet") |
|
.data(bullets) |
|
circlesB.enter().append("circle").classed("bullet", true) |
|
.on("mouseover", hover) |
|
.on("mouseout", mouseout) |
|
circlesB.attr({ |
|
r: 4, |
|
fill: "none", |
|
stroke: "#111" |
|
}).attr({ |
|
cx: function(d) { return xscale(d.x) }, |
|
cy: function(d) { return yscale(d.y) }, |
|
}) |
|
|
|
|
|
var circlesT = svg.selectAll("circle.transformed") |
|
.data(transformed) |
|
circlesT.enter().append("circle").classed("transformed", true) |
|
.on("mouseover", hover) |
|
.on("mouseout", mouseout) |
|
circlesT.attr({ |
|
r: 8, |
|
fill: "#111", |
|
stroke: "#111" |
|
}) |
|
.transition() |
|
.duration(170) |
|
.ease("linear") |
|
.attr({ |
|
cx: function(d) { return xscale(d.x) }, |
|
cy: function(d) { return yscale(d.y) }, |
|
}) |
|
|
|
} |
|
|
|
function updateMatrixLeft() { |
|
var theta = angle.value; |
|
//https://en.wikipedia.org/wiki/Rotation_matrix#Basic_rotations |
|
var sin = Math.sin(theta*Math.PI/180).toFixed(2); |
|
var cos = Math.cos(theta*Math.PI/180).toFixed(2); |
|
transform.a = cos; |
|
transform.b = -sin; |
|
transform.c = sin; |
|
transform.d = cos; |
|
transform.tx = parseFloat(mtx_transforms[0].value) || 0; |
|
transform.ty = parseFloat(mtx_transforms[1].value) || 0; |
|
/* |
|
mtx_transforms[0].value = cos; |
|
mtx_transforms[1].value = -sin; |
|
mtx_transforms[3].value = sin; |
|
mtx_transforms[4].value = cos; |
|
|
|
transform.a = parseFloat(mtx_transforms[0].value) || 0; |
|
transform.b = parseFloat(mtx_transforms[1].value) || 0; |
|
transform.tx = parseFloat(mtx_transforms[2].value) || 0; |
|
transform.c = parseFloat(mtx_transforms[3].value) || 0; |
|
transform.d = parseFloat(mtx_transforms[4].value) || 0; |
|
transform.ty = parseFloat(mtx_transforms[5].value) || 0; |
|
*/ |
|
var tvalues = [ |
|
transform.a, transform.b, transform.c, transform.d |
|
] |
|
transforms.text(function(d,i) { return tvalues[i] }) |
|
|
|
angleIn.text(theta); |
|
sinOut.text(sin); |
|
cosOut.text(cos); |
|
render(); |
|
} |
|
|
|
setupScrubbing(updateMatrixLeft); |
|
for(var i=0;i<mtx_transforms.length;i++){ |
|
var input = mtx_transforms[i]; |
|
input.onchange = updateMatrixLeft; |
|
makeScrubbable(input); |
|
} |
|
makeScrubbable(angle, 5); |
|
|
|
|
|
|
|
// get the data and trigger the initial rendering |
|
d3.json("bullets.json", function(err, data) { |
|
bullets = data.map(function(d) { |
|
return {x: d.x, y: d.y}; |
|
}); |
|
updateMatrixLeft(); |
|
}) |
|
|
|
</script> |
|
</body> |