Built with blockbuilder.org
Last active
March 10, 2020 13:45
-
-
Save mforando/ca6e3aa62081f381b4bd9fa127df4868 to your computer and use it in GitHub Desktop.
SwoopyLogic
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
.swoopyArrow{ | |
fill:none; | |
stroke:black; | |
} | |
</style> | |
</head> | |
<svg> | |
<marker id="arrowhead" viewBox="-10 -10 20 20" refX="0" refY="0" markerWidth="20" markerHeight="20" stroke-width="1" orient="auto"><polyline stroke-linejoin="bevel" points="-6.75,-6.75 0,0 -6.75,6.75"></polyline></marker> | |
<circle id="targetCircle"></circle> | |
<circle id="annotationCircle"></circle> | |
<path id="swoop"></path> | |
</svg> | |
<script> | |
//https://github.com/bizweekgraphics/swoopyarrows/blob/master/swoopyArrow.js | |
function swoopyArrow() { | |
var angle = Math.PI, | |
clockwise = true, | |
xValue = function(d) { return d[0]; }, | |
yValue = function(d) { return d[1]; }; | |
function render(data) { | |
data = data.map(function(d, i) { | |
return [xValue.call(data, d, i), yValue.call(data, d, i)]; | |
}); | |
// get the chord length ("height" {h}) between points | |
var h = hypotenuse(data[1][0]-data[0][0], data[1][1]-data[0][1]) | |
// get the distance at which chord of height h subtends {angle} radians | |
var d = h / ( 2 * Math.tan(angle / 2) ); | |
// get the radius {r} of the circumscribed circle | |
var r = hypotenuse(d, h/2) | |
/* | |
SECOND, compose the corresponding SVG arc. | |
read up: http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands | |
example: <path d = "M 200,50 a 50,50 0 0,1 100,0"/> | |
M 200,50 Moves pen to (200,50); | |
a draws elliptical arc; | |
50,50 following a degenerate ellipse, r1 == r2 == 50; | |
i.e. a circle of radius 50; | |
0 with no x-axis-rotation (irrelevant for circles); | |
0,1 with large-axis-flag=0 and sweep-flag=1 (clockwise); | |
100,0 to a point +100 in x and +0 in y, i.e. (300,50). | |
*/ | |
var path = "M " + data[0][0] + "," + data[0][1] | |
+ " a " + r + "," + r | |
+ " 0 0," + (clockwise ? "1" : "0") + " " | |
+ (data[1][0]-data[0][0]) + "," + (data[1][1]-data[0][1]); | |
return path | |
} | |
function hypotenuse(a, b) { | |
return Math.sqrt( Math.pow(a,2) + Math.pow(b,2) ); | |
} | |
render.angle = function(_) { | |
if (!arguments.length) return angle; | |
angle = Math.min(Math.max(_, 1e-6), Math.PI-1e-6); | |
return render; | |
}; | |
render.clockwise = function(_) { | |
if (!arguments.length) return clockwise; | |
clockwise = !!_; | |
return render; | |
}; | |
render.x = function(_) { | |
if (!arguments.length) return xValue; | |
xValue = _; | |
return render; | |
}; | |
render.y = function(_) { | |
if (!arguments.length) return yValue; | |
yValue = _; | |
return render; | |
}; | |
return render; | |
} | |
</script> | |
<body> | |
<script> | |
//https://github.com/bizweekgraphics/swoopyarrows | |
var svg = d3.select("svg") | |
.attr("width", 500) | |
.attr("height", 500) | |
.style("outline","1px solid black") | |
.style("margin","50px") | |
var target = [250, 250]; | |
var annotation = [200, 200]; | |
svg.select("#targetCircle") | |
.call(d3.drag() | |
.on("start", dragstarted) | |
.on("drag", dragged) | |
.on("end", dragended)) | |
svg.select("#annotationCircle") | |
.call(d3.drag() | |
.on("start", dragstarted2) | |
.on("drag", dragged2) | |
.on("end", dragended2)) | |
function dragstarted(){ | |
} | |
function dragged(){ | |
var coords = d3.mouse(svg.node()); | |
d3.select("#targetCircle") | |
.attr('cx', ()=>{return coords[0]}) | |
.attr('cy', ()=>{return coords[1]}); | |
target = coords; | |
updateSwoop(annotation, target, 6); | |
} | |
function dragended(){ | |
} | |
function dragstarted2(){ | |
} | |
function dragged2(){ | |
var coords = d3.mouse(svg.node()); | |
annotation = coords; | |
d3.select("#annotationCircle") | |
.attr('cx', ()=>{return coords[0]}) | |
.attr('cy', ()=>{return coords[1]}); | |
updateSwoop(annotation, target, 6); | |
} | |
function dragended2(){ | |
} | |
updateSwoop(annotation, target, 6); | |
function updateSwoop(annotation, target, pad){ | |
//toggle clockwise based on x position of annotation relative to target. | |
var clockwise = false; | |
//to handle a pad dynamically, offset the x-y for the target by desired angle | |
var padx = 0; | |
var pady = 0; | |
if(annotation[0]>target[0] && annotation[1]<=target[1]){ | |
// annotation is to the right and above/equal to the target. | |
clockwise = false; | |
pady = -pad; | |
} else if(annotation[0]>target[0]){ | |
// annotation is to the right and below to the target. | |
clockwise = false; | |
padx = pad; | |
} else if(annotation[1]<=target[1]){ | |
// annotation is to the right and above the target. | |
clockwise = true; | |
pady = -pad; | |
} else { | |
// annotation is to the left. | |
clockwise = true; | |
padx = -pad; | |
} | |
var swoopy = swoopyArrow() | |
.angle(Math.PI/2) | |
.clockwise(clockwise) | |
.x(function(d) {return d[0];}) | |
.y(function(d) {return d[1];}); | |
svg.select("#annotationCircle") | |
.attr("cx", annotation[0]) | |
.attr("cy", annotation[1]) | |
.attr("r",12) | |
.style("fill","green"); | |
svg.select("#targetCircle") | |
.attr("cx", target[0]) | |
.attr("cy", target[1]) | |
.attr("r",5) | |
.style("fill","red"); | |
var offsetTarget = [target[0]+padx, target[1]+pady] | |
svg.select("path") | |
.attr("class","swoopyArrow") | |
.attr('marker-end', 'url(#arrowhead)') | |
.datum([annotation, offsetTarget]) | |
.attr("d", swoopy); | |
} | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment