Skip to content

Instantly share code, notes, and snippets.

@emeeks
Last active August 29, 2015 14:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save emeeks/840a3af6e48196de718a to your computer and use it in GitHub Desktop.
Save emeeks/840a3af6e48196de718a to your computer and use it in GitHub Desktop.
Visualizing Touch Rotate

This is a simple example showing how you can calculate the necessary angle of rotation from a touch interaction utilizing d3.touches. The slope of the initial position of the line created from the first two touch fingertips is stored and compared to the slope of the current position of the line from those two inputs.

Notice that you need to use three fingers to signal that you're "rotating" (instead of, say, zooming or panning) even though the actual rotation calculation ignores the third fingertip.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>D3 Visualizing Rotation</title>
<meta charset="utf-8" />
</head>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/colorbrewer.v1.min.js"></script>
<style>
body, html {
width:100%;
height:100%;
}
#vizcontainer {
width:100%;
height:100%;
}
svg {
width: 100%;
height: 100%;
}
</style>
<body onload="touchDemo()">
<div id="vizcontainer">
<svg></svg>
</div>
<footer>
<script>
function touchDemo() {
var initialD = [];
var initialRotate;
d3.select("svg").on("touchstart", touchBegin);
d3.select("svg").on("touchmove", touchStatus);
var touchColor = d3.scale.linear().domain([0, 10]).range(["pink", "darkred"])
var graphicsG = d3.select("svg").append("g").attr("id", "graphics").attr("transform", "translate(250,250)");
graphicsG.append("rect").attr("width", 250).attr("height", 50).attr("x", 50).attr("y", 50)
.style("fill", "lightgray").style("stroke", "gray").style("stroke-width", "1px");
graphicsG.append("rect").attr("width", 100).attr("height", 400).attr("x", 350).attr("cy", 400)
.style("fill", "lightgray").style("stroke", "gray").style("stroke-width", "1px");
graphicsG.append("rect").attr("width", 400).attr("height", 50).attr("x", 50).attr("cy", 400)
.style("fill", "lightgray").style("stroke", "gray").style("stroke-width", "1px");
function touchBegin() {
d3.event.preventDefault();
d3.event.stopPropagation();
initialD = d;
initialRotate = d3.transform(d3.select("#graphics").attr("transform")).rotate;
d = d3.touches(this);
if (d.length == 3) {
d3.select("svg").selectAll("line").remove();
d3.select("svg").selectAll("text").remove();
d3.select("svg").append("line")
.attr("class", "initial")
.style("stroke", function(d,i) {return i == 0 ? "red" : "black"})
.style("stroke-width", function(d,i) {return i == 0 ? "20px" : "5px"})
}
}
function touchStatus() {
d3.event.preventDefault();
d3.event.stopPropagation();
d = d3.touches(this);
d3.select("svg").selectAll("circle").data(d).enter().append("circle").attr("r", 75).style("fill", function(d, i) {
return touchColor(i)
});
d3.select("svg").selectAll("circle").data(d).exit().remove();
d3.select("svg").selectAll("circle").attr("cx", function(d) {
return d[0]
}).attr("cy", function(d) {
return d[1]
});
var l = [];
if (d.length > 1) {
for (x in d) {
for (y in d) {
if (y != x) {
var lineObj = {
source: d[x],
target: d[y]
};
l.push(lineObj);
}
}
}
d3.select("svg").selectAll("line.status").data(l).enter().append("line").attr("class", "status")
.style("stroke", function(d,i) {return i == 0 ? "red" : "black"})
.style("stroke-width", function(d,i) {return i == 0 ? "20px" : "5px"});
d3.select("svg").selectAll("line.status").attr("x1", function(d) {
return d.source[0]
}).attr("y1", function(d) {
return d.source[1]
}).attr("x2", function(d) {
return d.target[0]
}).attr("y2", function(d) {
return d.target[1]
})
}
d3.select("svg").selectAll("line.status").data(l).exit().remove();
if (d.length == 3) {
var slope1 = (initialD[0][1] - initialD[1][1]) / (initialD[0][0] - initialD[1][0]);
var slope2 = (d[0][1] - d[1][1]) / (d[0][0] - d[1][0]);
var lineAdjust = [d[0][0] - (initialD[0][0] - initialD[1][0]), d[0][1] - (initialD[0][1] - initialD[1][1])]
d3.select("text.newslope").remove();
d3.select("text.firstslope").remove();
d3.select("svg").append("text")
.attr("x", (d[0][0] + lineAdjust[0]) / 2)
.attr("y", (d[0][1] + lineAdjust[1]) / 2)
.attr("class", "firstslope")
.style("color", "darkred")
.style("font-weight", 900)
.text("Initial Slope: " + d3.format(".2f")(slope1));
d3.select("line.initial")
.attr("x1", d[0][0])
.attr("x2", lineAdjust[0])
.attr("y1", d[0][1])
.attr("y2", lineAdjust[1])
d3.select("svg").append("text")
.attr("x", (d[0][0] + d[1][0]) / 2)
.attr("y", (d[0][1] + d[1][1]) / 2)
.attr("class", "newslope")
.style("color", "darkred")
.style("font-weight", 900)
.text("Changed Slope: " + d3.format(".2f")(slope2));
//One way to calculate the angle
var angle = Math.atan((slope1 - slope2)/(1 + slope1*slope2)) * 180/Math.PI;
//Another way to calculate the angle, which also provides the necessary angles for a d3.svg.angle
var arcAngle1 = Math.atan2(d[0][1] - lineAdjust[1],d[0][0] - lineAdjust[0]);
var arcAngle2 = Math.atan2((d[0][1] - d[1][1]),(d[0][0] - d[1][0]));
var newArc = d3.svg.arc().innerRadius(100).outerRadius(200).startAngle(arcAngle1 - (Math.PI / 2)).endAngle(arcAngle2 - (Math.PI / 2));
var altAngle = (arcAngle2 - arcAngle1) * (180/Math.PI)
d3.select("svg").selectAll("g.arc").remove();
d3.select("svg").insert("g","g").attr("class","arc").attr("transform", "translate("+d[0][0]+","+d[0][1]+")").append("path").attr("class", "arc").attr("d", newArc).style("fill", "#B2B2CC")
d3.select("g.arc").append("text").text(d3.format(".2f")(altAngle) + " degrees").attr("transform", "translate(" +newArc.centroid()[0] +","+ newArc.centroid()[1]+")").style("font-size", "24px").style("font-weight", 900)
var newRotate = initialRotate - altAngle;
d3.select("#graphics").attr("transform", "translate(250,250) rotate(" +newRotate +")")
}
}
}
</script>
</footer>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment