Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a curved SVG path between two points?

I need to draw a symmetrically curved line between the centers of two circles.

<svg>
    <circle class="spot" id="au" cx="1680" cy="700" r="0"></circle>
    <circle class="spot" id="sl" cx="1425" cy="525" r="0"></circle>

    <line id="line1" stroke-width="2" stroke="red"/>
</svg>

This is the code I wrote so far. < line > element should be replaced with a curved path.

function drawNow() {
    let point1X = document.getElementById("au").getAttribute("cx");
    let point1Y = document.getElementById("au").getAttribute("cy");
    let point2X = document.getElementById("sl").getAttribute("cx");
    let point2Y = document.getElementById("sl").getAttribute("cy");

    let line1 = document.getElementById("line1");
    line1.setAttribute("x1", point1X);
    line1.setAttribute("y1", point1Y);
    line1.setAttribute("x2", point2X);
    line1.setAttribute("y2", point2Y);
}
like image 268
WenukaGTX Avatar asked Mar 14 '18 09:03

WenukaGTX


1 Answers

An SVG quadratic curve will probably suffice. To draw it, you need the end points (which you have) and a control point which will determine the curve.

To make a symmetrical curve, the control point needs to be on the perpendicular bisector of the line between the end points. A little maths will find it.

So, from two points...

Two points to join

you can get to

Points joined with curve

with the code in

<!DOCTYPE html>
    <html>
    <head>
    	<meta charset="UTF-8">
    	<title></title>
    	<style>
    		svg { background-color: bisque; }
    		.spot { fill: blue; }
    		.spot2 { fill: red; }
    	</style>
    	<script>
    		function x() {
    			var p1x = parseFloat(document.getElementById("au").getAttribute("cx"));
    			var p1y = parseFloat(document.getElementById("au").getAttribute("cy"));
    			var p2x = parseFloat(document.getElementById("sl").getAttribute("cx"));
    			var p2y = parseFloat(document.getElementById("sl").getAttribute("cy"));
    
    			// mid-point of line:
    			var mpx = (p2x + p1x) * 0.5;
    			var mpy = (p2y + p1y) * 0.5;
    
    			// angle of perpendicular to line:
    			var theta = Math.atan2(p2y - p1y, p2x - p1x) - Math.PI / 2;
    
    			// distance of control point from mid-point of line:
    			var offset = 30;
    
    			// location of control point:
    			var c1x = mpx + offset * Math.cos(theta);
    			var c1y = mpy + offset * Math.sin(theta);
    
    			// show where the control point is:
    			var c1 = document.getElementById("cp");
    			c1.setAttribute("cx", c1x);
    			c1.setAttribute("cy", c1y);
    
    			// construct the command to draw a quadratic curve
    			var curve = "M" + p1x + " " + p1y + " Q " + c1x + " " + c1y + " " + p2x + " " + p2y;
    			var curveElement = document.getElementById("curve");
    			curveElement.setAttribute("d", curve);
    		}
    	</script>
    </head>
    <body>
    	<svg width="240" height="160">
    		<circle id="au" class="spot" cx="200" cy="50" r="4"></circle>
    		<circle id="sl" class="spot" cx="100" cy="100" r="4"></circle>
    		<circle id="cp" class="spot2" cx="0" cy="0" r="4"></circle>
    		<path id="curve" d="M0 0" stroke="green" stroke-width="4" stroke-linecap="round" fill="transparent"></path>
    	</svg>
    	<button type="button" onclick="x();">Click</button>
    </body>
    </html>

If you want the curve to go the other way, change the sign of offset.

If you are using ES6-compliant browsers, you can use string interpolation for slightly tidier code:

var curve = `M${p1x} ${p1y} Q${c1x} ${c1y} ${p2x} ${p2y}`;

There is no requirement for the control point to be shown - that's just so you can see where it is and illustrate that the curve doesn't go through it.

Note: an alternative to using atan2 is to calculate the negative reciprocal of the gradient of the line between the points, but that is fiddly for the case where the gradient is zero and may produce wildly inaccurate results when the gradient is close to zero.

like image 90
Andrew Morton Avatar answered Oct 19 '22 22:10

Andrew Morton