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