D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
mforando
Full window
Github gist
SwoopyLogic
Built with
blockbuilder.org
<!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: https://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>
https://d3js.org/d3.v4.min.js