Skip to content

Instantly share code, notes, and snippets.

@shancarter
Last active November 28, 2019 02:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save shancarter/1034db3e675f2d3814e6006cf31dbfdc to your computer and use it in GitHub Desktop.
Save shancarter/1034db3e675f2d3814e6006cf31dbfdc to your computer and use it in GitHub Desktop.
Calculating the relative angle difference between two vectors

Here are the math-y bits, courtesy of @ch402:

var a2 = Math.atan2(source.y, source.x);
var a1 = Math.atan2(compare.y, compare.x);
var sign = a1 > a2 ? 1 : -1;
var angle = a1 - a2;
var K = -sign * Math.PI * 2;
var angle = (Math.abs(K + angle) < Math.abs(angle))? K + angle : angle;
<!doctype html>
<meta charset="utf-8">
<style>
body {
margin: 0;
font-family: sans-serif;
font-size: 12px;
}
svg > g > line {
stroke: #ddd;
shape-rendering: crispEdges;
}
svg text {
text-anchor: middle;
}
g line {
stroke: black;
}
.compare line {
stroke-dashArray: 2, 4;
}
.handle circle {
fill: yellow;
fill-opacity: 0.8;
stroke: black;
stroke-opacity: 0.15;
}
.handle text {
fill: grey;
text-anchor: middle;
}
.difference {
fill-opacity: 0.4;
}
</style>
<svg>
<defs>
<marker id="arrowhead" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="8" markerHeight="8" orient="auto">
<path d="M0,0L10,5L0,10z" />
</marker>
</defs>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
"use strict";
var width = 960,
height = 500;
var svg = d3.select("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var drag = d3.drag()
.on("drag", function(d) { d.x = d3.event.x; d.y = d3.event.y; update(); });
var arc = d3.arc()
.cornerRadius(4);
var format = d3.format(".2f")
// Origin
svg.append("line")
.attr("y1", -height / 2)
.attr("y2", height / 2);
svg.append("line")
.attr("x1", -width / 2)
.attr("x2", width / 2)
svg.append("circle")
.attr("r", 4);
// Difference
var differenceArc = svg.append("g")
.datum({});
var differencePath = differenceArc.append("path")
.attr("class", "difference");
var differenceText = differenceArc.append("text");
// Source vector
var sourceVector = svg.append("g")
.attr("class", "source")
.datum({x: 0, y: -150});
var sourceHandle = sourceVector.append("g")
.attr("class", "handle")
.call(drag);
var sourceLine = sourceVector.append("line")
.attr("marker-end", "url(#arrowhead)");
sourceHandle.append("circle")
.attr("r", 10);
var sourceText = sourceHandle.append("text")
.attr("dy", -15);
// Compare vector
var compareVector = svg.append("g")
.attr("class", "compare")
.datum({x: -250, y: -75});
var compareHandle = compareVector.append("g")
.attr("class", "handle")
.call(drag);
var compareLine = compareVector.append("line")
.attr("marker-end", "url(#arrowhead)");
compareHandle.append("circle")
.attr("r", 10);
var compareText = compareHandle.append("text")
.attr("dy", -15);
// Update
function update() {
var source = sourceVector.datum(),
compare = compareVector.datum();
var sourceLength = Math.sqrt(source.x * source.x + source.y * source.y),
compareLength = Math.sqrt(compare.x * compare.x + compare.y * compare.y);
// The math-y bits
var a2 = Math.atan2(source.y, source.x);
var a1 = Math.atan2(compare.y, compare.x);
var sign = a1 > a2 ? 1 : -1;
var angle = a1 - a2;
var K = -sign * Math.PI * 2;
var angle = (Math.abs(K + angle) < Math.abs(angle))? K + angle : angle;
sourceLine
.attr("x2", (d) => d.x)
.attr("y2", (d) => d.y);
sourceHandle
.attr("transform", (d) => `translate(${d.x}, ${d.y})`);
sourceText
.text(`${source.x}, ${source.y}`)
compareLine
.attr("x2", (d) => d.x)
.attr("y2", (d) => d.y);
compareHandle
.attr("transform", (d) => `translate(${d.x}, ${d.y})`)
compareText
.text(`${compare.x}, ${compare.y}`);
arc
.innerRadius(20)
.outerRadius(Math.max(30, Math.min(sourceLength, compareLength) * 0.9))
.startAngle(a2 + Math.PI / 2)
.endAngle(a2 + angle + Math.PI / 2);
differencePath
.style("fill", angle > 0 ? "cyan" : "magenta")
.attr("d", arc());
differenceText
.attr("transform", "translate(" + arc.centroid() + ")")
.text(Math.abs(Math.round(360 * angle / (Math.PI * 2))) + "º " + (angle > 0 ? "starboard" : "port"))
}
update();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment