I'd like to make an Archimedean spiral in SVG.
I created a spiral with four quadratic bezier points, but I'm not sure where I should put the control points for each to get a perfect Archimedean spiral:
<path class="spiral"
d="M100 50
C 100 116 12.5 99.5 12.5 50
C 12.5 0.5 75 9 75 50
C 75 83 37.5 74 37.5 50
C 37.5 38 50 42 50 50"
stroke="black" stroke-width="1" fill="none">
I'd like to expand on a question by Zev Eisenberg at math.stackexchange. From that, Zev implented a solution as a C function. It uses quadratic bezier curves instead of cubic, but has the advantage that you can set the angles for the path sections freely, thus minimizing the error as you like.
Here is a Javascript port. Set the parameters to getPath to your liking (angles are in degree). thetaStep is the angle each path section covers. I think 30° gives pretty decent results.
function lineIntersection (m1, b1, m2, b2) {
if (m1 === m2) {
throw new Error("parallel slopes");
}
const x = (b2 - b1) / (m1 - m2);
return {x: x, y: m1 * x + b1};
}
function pStr (point) {
return `${point.x},${point.y} `;
}
function getPath (center, startRadius, spacePerLoop, startTheta, endTheta, thetaStep) {
// Rename spiral parameters for the formula r = a + bθ
const a = startRadius; // start distance from center
const b = spacePerLoop / Math.PI / 2; // space between each loop
// convert angles to radians
let oldTheta = newTheta = startTheta * Math.PI / 180;
endTheta = endTheta * Math.PI / 180;
thetaStep = thetaStep * Math.PI / 180;
// radii
let oldR,
newR = a + b * newTheta;
// start and end points
const oldPoint = {x: 0, y: 0};
const newPoint = {
x: center.x + newR * Math.cos(newTheta),
y: center.y + newR * Math.sin(newTheta)
};
// slopes of tangents
let oldslope,
newSlope = (b * Math.sin(oldTheta) + (a + b * newTheta) * Math.cos(oldTheta)) /
(b * Math.cos(oldTheta) - (a + b * newTheta) * Math.sin(oldTheta));
let path = "M " + pStr(newPoint);
while (oldTheta < endTheta - thetaStep) {
oldTheta = newTheta;
newTheta += thetaStep;
oldR = newR;
newR = a + b * newTheta;
oldPoint.x = newPoint.x;
oldPoint.y = newPoint.y;
newPoint.x = center.x + newR * Math.cos(newTheta);
newPoint.y = center.y + newR * Math.sin(newTheta);
// Slope calculation with the formula:
// (b * sinΘ + (a + bΘ) * cosΘ) / (b * cosΘ - (a + bΘ) * sinΘ)
const aPlusBTheta = a + b * newTheta;
oldSlope = newSlope;
newSlope = (b * Math.sin(newTheta) + aPlusBTheta * Math.cos(newTheta)) /
(b * Math.cos(newTheta) - aPlusBTheta * Math.sin(newTheta));
const oldIntercept = -(oldSlope * oldR * Math.cos(oldTheta) - oldR * Math.sin(oldTheta));
const newIntercept = -(newSlope * newR* Math.cos(newTheta) - newR * Math.sin(newTheta));
const controlPoint = lineIntersection(oldSlope, oldIntercept, newSlope, newIntercept);
// Offset the control point by the center offset.
controlPoint.x += center.x;
controlPoint.y += center.y;
path += "Q " + pStr(controlPoint) + pStr(newPoint);
}
return path;
}
const path = getPath({x:400,y:400}, 0, 50, 0, 6*360, 30);
const spiral = document.querySelector('#spiral');
spiral.setAttribute("d", path);
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 800 800">
<path id="spiral" d="" fill="none" stroke="black" stroke-width="3"/>
</svg>
To get the sprien code, you can use a vector editor, for example, Inkscape
In the vector editor toolbar, select the spiral (F9), where you can select the parameters of the spiral - the number of turns, the inner radius.
Save the file. We need the string <path> ... </ path> Copy it.
<svg xmlns="http://www.w3.org/2000/svg" width="50%" heihgt="50%" viewBox="0 150 744 1052">
<path d="m351 487c0 8-11 4-14-1-6-11 4-24 15-27 19-5 37 11 40 30 4 27-18 50-44 53-35 4-64-25-66-59-3-42 32-77 73-79 50-3 90 39 92 88 2 57-46 104-102 105-65 2-117-53-119-117-1-72 60-131 131-132 80-1 144 67 145 146 1 87-74 158-160 158-95 0-171-81-171-175 0-102 88-185 190-184 110 1 198 95 197 204C557 615 456 709 340 708 215 706 115 598 117 475 119 342 233 236 364 238 504 240 616 361 614 500 611 648 484 766 337 763 182 760 58 626 61 472 65 309 206 179 367 183c170 4 306 151 302 320-4 178-158 319-335 315" fill="none" stroke="grey" stroke-width="3"/>
Example of spiral animation
For the animation of the drawing of the spiral we will use the patch attribute stroke-dashoffset - indent from the beginning of the line. With a maximum indent equal to the length of the line (patch), the line is not visible. With stroke-dashoffset = "0" the line will be drawn completely.
In other words, to implement the drawing animation of the line, you need to decrease the margin from the maximum to zero.
Find the length of the line - var len = Math.round (path.getTotalLength ());
For our patch - 6265px
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="50%" heihgt="50%" viewBox="0 150 744 1052" id="svg2" version="1">
<path stroke-dashoffset="6265" stroke-dasharray="6265" d="m351 487c0 8-11 4-14-1-6-11 4-24 15-27 19-5 37 11 40 30 4 27-18 50-44 53-35 4-64-25-66-59-3-42 32-77 73-79 50-3 90 39 92 88 2 57-46 104-102 105-65 2-117-53-119-117-1-72 60-131 131-132 80-1 144 67 145 146 1 87-74 158-160 158-95 0-171-81-171-175 0-102 88-185 190-184 110 1 198 95 197 204C557 615 456 709 340 708 215 706 115 598 117 475 119 342 233 236 364 238 504 240 616 361 614 500 611 648 484 766 337 763 182 760 58 626 61 472 65 309 206 179 367 183c170 4 306 151 302 320-4 178-158 319-335 315" style="fill:none;stroke:#000" stroke-width="2">
<animate attributeName="stroke-dashoffset" values="6265;0;6265;0" dur="15s" fill="freeze" />
</path>
Implementing animation with CSS
Beginning of animation when hovering over the cursor
#spiral {
stroke: dodgerblue;
stroke-width:4;
fill:#FCFCFC;
stroke-dasharray: 6265;
stroke-dashoffset: 6265;
transition: stroke-dashoffset 10s;
}
#spiral:hover {
stroke-dashoffset: 0;
}
svg text {
font-size:36px;
pointer-events:none;
}
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" width="50%" heihgt="50%" viewBox="0 150 744 1052" id="svg2" version="1" >
<path id="spiral" stroke-dashoffset="6265" stroke-dasharray="6265" d="m351 487c0 8-11 4-14-1-6-11 4-24 15-27 19-5 37 11 40 30 4 27-18 50-44 53-35 4-64-25-66-59-3-42 32-77 73-79 50-3 90 39 92 88 2 57-46 104-102 105-65 2-117-53-119-117-1-72 60-131 131-132 80-1 144 67 145 146 1 87-74 158-160 158-95 0-171-81-171-175 0-102 88-185 190-184 110 1 198 95 197 204C557 615 456 709 340 708 215 706 115 598 117 475 119 342 233 236 364 238 504 240 616 361 614 500 611 648 484 766 337 763 182 760 58 626 61 472 65 309 206 179 367 183c170 4 306 151 302 320-4 178-158 319-335 315" />
<text x="10" y="200" > Mouse over </text>
</svg>
Do you need to use bezier curves? You can't get a perfect spiral with bezier curves, just like you can't get an exact circular arc.
You might want to consider using a <polyline> made of straight line segments.
// pathId the id of the path element to modify.
// centreX, centreY: the position of the centre of the spiral.
// startRadius: radius at start (inside) of spiral.
// endRadius: radius after one complete (360deg) rotation.
// quarterTurns: the number of quarter turns to generate.
function makeSpiral(pathId, centreX, centreY, startRadius, endRadius, quarterTurns)
{
var pointsPerQuarter = 90;
var radiusStep = (endRadius - startRadius) / 4 / pointsPerQuarter;
var points = [];
for (var i=0; i < quarterTurns * pointsPerQuarter; i++)
{
var radius = startRadius + radiusStep * i;
var angle = i * Math.PI / 2 / pointsPerQuarter;
points.push(radius * Math.cos(angle));
points.push(radius * Math.sin(angle));
}
document.getElementById(pathId).setAttribute("points", points.join(','));
}
makeSpiral("spiral", 0, 0, 1, 2, 31);
<svg width="300" viewBox="-10 -10 20 20">
<g class="arc" fill="none" stroke="blue" stroke-width="0.05">
<polyline id="spiral" points=""/>
</g>
</svg>
With beziers
Like I said, beziers can never be exact, but with care, you can get a very accurate approximation.
Let's start by imagining a quarter circle. The control points to approximate that turn out to have a ratio of around 0.55 of the radius. The exact value varies depending on whether you want to minimise the maximum error, or the average error, or pass through a specific point, etc.
You can read about one approach to calculate this here..
The first approach on that page, giving (4/3)*(sqrt(2) - 1) is the most common value used.
<svg width="300" viewBox="-0.5 -0.5 2 2">
<g class="axes" stroke="black" stroke-width="0.01">
<line x2="1.1" y2="0"/>
<line x2="0" y2="1.1"/>
</g>
<g class="arc" fill="none" stroke="blue" stroke-width="0.01">
<path d="M 1,0 C 1,0.552, 0.552,1, 0,1"/>
</g>
</svg>
To make your spiral, you can just imagine the radius growing as you make each quarter circle step.
To make this easier, I'll use some JS to calculate our bezier values. I'll also include a reference spiral in red to see how accurate the bezier version is.
// pathId the id of the path element to modify.
// centreX, centreY: the position of the centre of the spiral.
// startRadius: radius at start (inside) of spiral.
// endRadius: radius after one complete (360deg) rotation.
// quarterTurns: the number of quarter turns to generate.
function makeSpiral(pathId, centreX, centreY, startRadius, endRadius, quarterTurns)
{
var radiusStep = (endRadius - startRadius) / 4;
var FACTOR = 0.5522847498;
var step = 0;
var radius = startRadius;
var nextRadius = radius + radiusStep;
var d = "M " + (centreX + startRadius) + "," + centreY;
while (step < quarterTurns)
{
switch(step % 4)
{
case 0:
d += "c" + [0, radius * FACTOR, -radius + nextRadius * FACTOR, nextRadius, -radius, nextRadius].join(',');
break;
case 1:
d += "c" + [-radius * FACTOR, 0, -nextRadius, -radius + nextRadius * FACTOR, -nextRadius, -radius].join(',');
break;
case 2:
d += "c" + [0, -radius * FACTOR, radius - nextRadius * FACTOR, -nextRadius, radius, -nextRadius].join(',');
break;
case 3:
d += "c" + [radius * FACTOR, 0, nextRadius, radius - nextRadius * FACTOR, nextRadius, radius].join(',');
break;
}
step++;
radius = nextRadius;
nextRadius += radiusStep;
}
document.getElementById(pathId).setAttribute("d", d);
}
function makePolylineSpiral(pathId, centreX, centreY, startRadius, endRadius, quarterTurns)
{
var pointsPerQuarter = 90;
var radiusStep = (endRadius - startRadius) / 4 / pointsPerQuarter;
var points = [];
for (var i=0; i < quarterTurns * pointsPerQuarter; i++)
{
var radius = startRadius + radiusStep * i;
var angle = i * Math.PI / 2 / pointsPerQuarter;
points.push(centreX + radius * Math.cos(angle));
points.push(centreY + radius * Math.sin(angle));
}
document.getElementById(pathId).setAttribute("points", points.join(','));
}
makePolylineSpiral("reference-spiral", 0, 0, 1, 2, 4);
makeSpiral("spiral", 0, 0, 1, 2, 4);
<svg width="300" viewBox="-2 -2 5 5">
<g class="arc" fill="none" stroke="blue" stroke-width="0.1">
<polyline id="reference-spiral" points="" stroke="red"/>
<path id="spiral" d=""/>
</g>
</svg>
Unfortunately we can see that the naive bezier version doesn't match the reference spiral very well. Your could try tweaking the control point ratio, but you'll find that it'll always look a bit wonky.
For a better approximation of the spiral, you'll need to use bezier curves that cover a smaller portion of the circle (ie. less than 90 degrees).
I'm not going to do that here, but you might want to try yourself. Personally, I'd stick with the <polyline> version. If you need fewer or more points, you can modify the pointsPerQuarter value.
Related
How could I convert a line to a rect by specially preserving the information about line's start and end points. E.g. by rotating or skewing the rect so that I would get a rect which has the same length and the same direction as the line.
lineToPath converts an SVG <line> to a closed <path> using the line's start and end points and stroke width:
const lineToPath = ({x1,y1,x2,y2,width}) => {
const angle = Math.atan((y2 - y1) / (x2 - x1));
const dx = width / 2 * -Math.sin(angle);
const dy = width / 2 * Math.cos(angle);
return `M ${x1 + dx},${y1 + dy} L ${x2 + dx},${y2 + dy}
L ${x2 - dx},${y2 - dy} L ${x1 - dx},${y1 - dy} Z`;
}
const line = d3.select('line');
const x1 = parseFloat(line.attr('x1'));
const x2 = parseFloat(line.attr('x2'));
const y1 = parseFloat(line.attr('y1'));
const y2 = parseFloat(line.attr('y2'));
const width = parseFloat(line.attr('stroke-width'));
const linePath = lineToPath({x1,y1,x2,y2,width});
d3.select('path').attr('d', linePath);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg>
<line x1="10" y1="90" x2="220" y2="30" stroke="yellow" stroke-width="30" />
<path stroke="red" fill="none" />
</svg>
I want to draw the FID trajectory curve using SVG, similar to red curve: http://chem.ch.huji.ac.il/nmr/techniques/1d/pulseq_files/fid.gif
My code is presented:
<svg>
<path d="M 180 45 q -10 10 0 20 q 40 20 -20 20 a40,20 0 1,1 -20,20 a60,30 0 1,1 -20,35 a80,30 0 1,1 -10,40 " stroke="blue" stroke-width="2" fill="none" />
</svg>
Unfortunately, the curves at the junction places are sharp. I can not make a smooth connection of curves.
Do you know, how to solve my problem?
What you intent to draw is a conical helix or conical spiral. Basically I'm calculating the points for the helix, and next I'm using the points to build the d attribute for the path.
let r = 30;
let ry = [];// the array of points used to draw a conical helix
function getPoints() {
var a = 0.8;// angle for the tilt
for (var t = 0.1; t < 2 * Math.PI; t += 0.008) {
let x = r * t * Math.cos(6 * t + a);
let y =
(r + r / 2 * Math.cos(a)) * t -
r / 2 * Math.sin(a) * t * Math.sin(6 * t + a) -
150;
ry.push({ x, y });
}
}
getPoints();
//using the points to build the d attribute for the path
let d = `M${ry[0].x},${ry[0].y}L`;
for (let i = 0; i < ry.length; i++) {
d += `${ry[i].x},${ry[i].y} `;
}
ch.setAttributeNS(null, "d", d);
svg{border:1px solid; width:90vh}
<svg viewBox="-225 -225 450 450">
<path id="ch" stroke="red" stroke-width="2" fill="none" />
</svg>
I have written these functions to center one svg element in another:
import { Matrix, translate } from 'transformation-matrix';
export const getElementCenter = (element: SVGGraphicsElement) => {
const bBox = element.getBBox();
return { x: bBox.x + bBox.width / 2, y: bBox.y + bBox.height / 2 };
};
export const centerElementInOther = (
element: SVGGraphicsElement,
other: SVGGraphicsElement,
scaleFactor: number = 1
): Matrix => {
const elementCentre = getElementCenter(element);
const otherCentre = getElementCenter(other);
const x = elementCentre.x - otherCentre.x;
const y = elementCentre.y - otherCentre.y;
// how can I work out the scaleFactor? If it the actual scale is 0.5 then I need to divide by 2 but if it is 1 then I need to divide by 1
return translate(-x / scaleFactor, -y / scaleFactor);
};
Everything works unless the element is scaled then I need to apply some maths but I do not understand the ration.
Everything worked fine until I changed the scale of the element to 0.5 and then I had to divide the center x and center y by 2.
Actually you need to multiply x, and y with scaleFactor because scaleFactor is a Decimal Fraction:
return translate(-x * scaleFactor, -y * scaleFactor);
If it the actual scale is 0.5 then I need to divide by 2 but if it is 1 then I need to divide by 1
and what that means is:
x / 2 <=> x * 0.5
x / 1 <=> x * 1
so x * scaleFactor and y * scaleFactor will work just fine with all values of scaleFactor decimal fraction.
Here is some examples on some test x, y values:
const centerElementInOtherDiv = (x, y, scaleFactor = 1) => {
return {
x: -x / scaleFactor,
y: -y / scaleFactor
};
};
const centerElementInOtherMul = (x, y, scaleFactor = 1) => {
return {
x: -x * scaleFactor,
y: -y * scaleFactor
};
};
const svgCoords = [
{ x: 100, y: 100 },
{ x: 200, y: 200 },
{ x: 100, y: 300 }
];
for(svgCoord of svgCoords) {
let mulXY = centerElementInOtherMul(svgCoord.x, svgCoord.y);
let divXY = centerElementInOtherDiv(svgCoord.x, svgCoord.y);
console.log(`scaleFactor = 1 -> mul(x: ${mulXY.x}, y: ${mulXY.y}), div(x: ${divXY.x}, y: ${divXY.y})`)
mulXY = centerElementInOtherMul(svgCoord.x, svgCoord.y, 0.5);
divXY = centerElementInOtherDiv(svgCoord.x, svgCoord.y, 0.5);
console.log(`scaleFactor = 0.5 -> mul(x: ${mulXY.x}, y: ${mulXY.y}), div(x: ${divXY.x}, y: ${divXY.y})`)
}
and the result output will be:
scaleFactor = 1 -> mul(x: -100, y: -100), div(x: -100, y: -100)
scaleFactor = 0.5 -> mul(x: -50, y: -50), div(x: -200, y: -200)
scaleFactor = 1 -> mul(x: -200, y: -200), div(x: -200, y: -200)
scaleFactor = 0.5 -> mul(x: -100, y: -100), div(x: -400, y: -400)
scaleFactor = 1 -> mul(x: -100, y: -300), div(x: -100, y: -300)
scaleFactor = 0.5 -> mul(x: -50, y: -150), div(x: -200, y: -600)
as you can see, multiplication works as expected because you're dealing with decimal fractions.
I would have loved to see the SVG code.
One way to center an svg element inside another transformed one is to wrap both elements in a <g> and apply the transformation to the group like so:
const bBox1 = theRect1.getBBox();
c1.setAttributeNS(null,"cx",bBox1.x + bBox1.width / 2)
c1.setAttributeNS(null,"cy",bBox1.y + bBox1.height / 2);
svg{border:1px solid;}
<svg viewBox="0 0 200 100">
<g transform="scale(.5,.5) translate(150,50) rotate(30,30,10)">
<rect id="theRect1" x="30" y="10" width="80" height="40" />
<circle id="c1" r="2" fill="red" />
</g>
</svg>
If wrapping the elements in a group is not possible you may apply the same transformation to the element you want to center
const bBox2 = theRect2.getBBox();
// get the transformation applied to the rect
let theTransformation = theRect2.getAttribute("transform")
c2.setAttributeNS(null,"cx",bBox2.x + bBox2.width / 2)
c2.setAttributeNS(null,"cy",bBox2.y + bBox2.height / 2);
//apply the same transformation to the circle
c2.setAttributeNS(null,"transform",theTransformation)
svg{border:1px solid;}
<svg viewBox="0 0 200 100">
<rect id="theRect2" x="30" y="10" width="80" height="40" transform="scale(.5,.5) translate(150,50) rotate(30,30,10)" />
<circle id="c2" r="2" fill="red" />
</svg>
In the case you want only the rect to be downscaled but not the circle you can wrap the circle in a group <g>, use the group and upscale the circle the right amount:
const bBox3 = theRect3.getBBox();
// get the transformation applied to the rect
let theTransformation = theRect3.getAttribute("transform");
// get the scale applied
let scaleRy = theTransformation
.split("scale(")[1]
.split(")")[0]
.split(","); //[".5", ".5"]
//calculate the circle's scale
let circleScale = `scale(${1 / Number(scaleRy[0])}, ${1 / Number(scaleRy[1])})`;
theUse.setAttributeNS(null, "x", bBox3.x + bBox3.width / 2);
theUse.setAttributeNS(null, "y", bBox3.y + bBox3.height / 2);
//apply the same transformation to the circle
theUse.setAttributeNS(null, "transform", theTransformation);
//scale the circle
c3.setAttributeNS(null, "transform", circleScale);
svg{border:1px solid;}
<svg viewBox="0 0 200 100">
<defs>
<g id="cWrap">
<circle id="c3" r="2" fill="red" transform="scale(2,2)" ></circle>
</g>
</defs>
<rect id="theRect3" x="30" y="10" width="80" height="40" transform="scale(.5,.5) translate(150,50) rotate(30,30,10)" />
<use id="theUse" xlink:href="#cWrap" />
</svg>
I have two rectangles, one of which is rotated 90 degrees, how can I draw a line between the two.
<svg width="200" height="100" viewBox="-100 -50 200 100">
<g transform="scale(1,-1)">
<g class="group" transform="">
<g class="g1">
<rect x="5" y="5" width="5" height="5" fill="red" class="rectA" style="fill: red;"></rect>
</g>
<g class="g2" transform="rotate(-90)">
<rect x="10" y="10" width="10" height="10" fill="red" class="rectB" style="fill: green;"></rect>
</g>
</g>
</g>
and I think scripts is,
var rectA = d3.select(".rectA");
var rectB = d3.select(".rectB");
var x1 = rectA.attr("x");
var y1 = rectA.attr("y");
var x2 = rectB.attr("x");
var y2 = rectB.attr("y");
d3.select(".group").append("line").attr({ x1: x1, y1: y1, x2: x2, y2: y2 })
.style("stroke", "blue").attr("class", "distanceLine").style("stroke-width", 3);
But the result is not correct, how should I do?
thanks.
My JSFiddle
I want like this pic
If you want the end of the line to match some transformed coordinates, then you have to apply the same transformation to it.
var x1 = rectA.attr("x");
var y1 = rectA.attr("y");
var x2 = rectB.attr("x");
var y2 = rectB.attr("y");
var p2 = rotate(x2,y2, -90);
d3.select(".group")
.append("line")
.attr({ 'x1': x1, 'y1': y1, 'x2': p2[0], 'y2': p2[1] })
.style("stroke", "blue")
.attr("class", "distanceLine")
.style("stroke-width", 3);
function rotate(x, y, angle) {
angle = angle * Math.PI / 180; // convert to radians
return [Math.cos(angle) * x - Math.sin(angle) * y,
Math.cos(angle) * y + Math.sin(angle) * x];
}
Demo fiddle here
I need draw a stroke arc around the circle
Circle
cx=110,cy=60,r=50
Path
d=M60,60 A50,50 0 0,1 160,60
<svg style="position:absolute" id="svg_test" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);" cx="110" cy="60" r="50" fill="none" stroke="#e4e4e4" stroke-width="2"></circle>
<path id="svgpath" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);" fill="none" stroke="#16a6b6" d="M60,60 A50,50 0 0,1 160,60" stroke-width="2"></path>
</svg>
How to determine the path if cx,cy and r is changed?
Here formula is given d="M cx cy+r+d A r+d r+d 0 0 0 cx+r+d cy".
Computed path M110, 110+d A50+d, 50 0 0 0 150,60 using above cx and cy does not match the value M60,60 A50,50 0 0,1 160,60
What should be the value of d?
jsfiddle
It's not clear what your question is, or what you ar trying to achieve.
The formula will draw an arc that covers quarter of a circle (110,110 round to 160,60 - the lower right quadrant). If you want more than that, you will have to alter the formula.
Where did you get "M60,60 A50,50 0 0,1 160,60" from? Why are you expecting it to match the path generated by the formula?
You need to find both the end points of the arc. each time your cx, cy and r is changed in order to update the path.
firstX = cx + (Math.cos(firstAngle * (Math.PI / 180)) * r);
firstY = cy - (Math.sin(firstAngle * (Math.PI / 180)) * r);
secondX = cx + (Math.cos(secondAngle * (Math.PI / 180)) * r);
secondY = cy - (Math.sin(secondAngle * (Math.PI / 180)) * r);
'M ' + firstX + ',' + firstY + ' A' + r + ',' + r + ' 0 1 0 ' + secondX + ',' + secondY