Given a circle centered at (200,200), radius 25, how do I draw an arc from 270 degree to 135 degree and one that goes from 270 to 45 degree?
0 degree means it is right on the x-axis (the right side) (meaning it is 3 o' clock position) 270 degree means it is 12 o'clock position, and 90 means it is 6 o'clock position
More generally, what is a path for an arc for part of a circle with
x, y, r, d1, d2, direction
meaning
center (x,y), radius r, degree_start, degree_end, direction
This question is related to
svg
Just to clarify better and offer another solution. Arc
[A
] command use the current position as a starting point so you have to use Moveto
[M] command first.
Then the parameters of Arc
are the following:
rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, xf, yf
If we define for example the following svg file:
<svg viewBox="0 0 500 500">_x000D_
<path fill="red" d="_x000D_
M 250 250_x000D_
A 100 100 0 0 0 450 250_x000D_
Z"/> _x000D_
</svg>
_x000D_
You will set the starting point with M
the ending point with the parameters xf
and yf
of A
.
We are looking for circles so we set rx
equal to ry
doing so basically now it will try to find all the circle of radius rx
that intersect the starting and end point.
import numpy as np
def write_svgarc(xcenter,ycenter,r,startangle,endangle,output='arc.svg'):
if startangle > endangle:
raise ValueError("startangle must be smaller than endangle")
if endangle - startangle < 360:
large_arc_flag = 0
radiansconversion = np.pi/180.
xstartpoint = xcenter + r*np.cos(startangle*radiansconversion)
ystartpoint = ycenter - r*np.sin(startangle*radiansconversion)
xendpoint = xcenter + r*np.cos(endangle*radiansconversion)
yendpoint = ycenter - r*np.sin(endangle*radiansconversion)
#If we want to plot angles larger than 180 degrees we need this
if endangle - startangle > 180: large_arc_flag = 1
with open(output,'a') as f:
f.write(r"""<path d=" """)
f.write("M %s %s" %(xstartpoint,ystartpoint))
f.write("A %s %s 0 %s 0 %s %s"
%(r,r,large_arc_flag,xendpoint,yendpoint))
f.write("L %s %s" %(xcenter,ycenter))
f.write(r"""Z"/>""" )
else:
with open(output,'a') as f:
f.write(r"""<circle cx="%s" cy="%s" r="%s"/>"""
%(xcenter,ycenter,r))
You can have a more detailed explanation in this post that I wrote.
@opsb's answers is neat, but the center point is not accurate, moreover, as @Jithin noted, if the angle is 360, then nothing is drawn at all.
@Jithin fixed the 360 issue, but if you selected less than 360 degree, then you'll get a line closing the arc loop, which is not required.
I fixed that, and added some animation in the code below:
function myArc(cx, cy, radius, max){ _x000D_
var circle = document.getElementById("arc");_x000D_
var e = circle.getAttribute("d");_x000D_
var d = " M "+ (cx + radius) + " " + cy;_x000D_
var angle=0;_x000D_
window.timer = window.setInterval(_x000D_
function() {_x000D_
var radians= angle * (Math.PI / 180); // convert degree to radians_x000D_
var x = cx + Math.cos(radians) * radius; _x000D_
var y = cy + Math.sin(radians) * radius;_x000D_
_x000D_
d += " L "+x + " " + y;_x000D_
circle.setAttribute("d", d)_x000D_
if(angle==max)window.clearInterval(window.timer);_x000D_
angle++;_x000D_
}_x000D_
,5)_x000D_
} _x000D_
_x000D_
myArc(110, 110, 100, 360);_x000D_
_x000D_
<svg xmlns="http://www.w3.org/2000/svg" style="width:220; height:220;"> _x000D_
<path d="" id="arc" fill="none" stroke="red" stroke-width="2" />_x000D_
</svg>
_x000D_
Note for the answer-seekers (who I also was) - if using arc is not obligatory, a far simpler solution to draw a part-circle is to use stroke-dasharray
of SVG <circle>
.
Divide dash array into two elements, and scale their range to the desired angle. Starting angle can be adjusted using stroke-dashoffset
.
Not a single cosine in sight.
Full example with explanations: https://codepen.io/mjurczyk/pen/wvBKOvP
This is an old question, but I found the code useful and saved me three minutes of thinking :) So I am adding a small expansion to @opsb's answer.
If you wanted to convert this arc into a slice (to allow for fill) we can modify the code slightly:
function describeArc(x, y, radius, spread, startAngle, endAngle){_x000D_
var innerStart = polarToCartesian(x, y, radius, endAngle);_x000D_
var innerEnd = polarToCartesian(x, y, radius, startAngle);_x000D_
var outerStart = polarToCartesian(x, y, radius + spread, endAngle);_x000D_
var outerEnd = polarToCartesian(x, y, radius + spread, startAngle);_x000D_
_x000D_
var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";_x000D_
_x000D_
var d = [_x000D_
"M", outerStart.x, outerStart.y,_x000D_
"A", radius + spread, radius + spread, 0, largeArcFlag, 0, outerEnd.x, outerEnd.y,_x000D_
"L", innerEnd.x, innerEnd.y, _x000D_
"A", radius, radius, 0, largeArcFlag, 1, innerStart.x, innerStart.y, _x000D_
"L", outerStart.x, outerStart.y, "Z"_x000D_
].join(" ");_x000D_
_x000D_
return d;_x000D_
}_x000D_
_x000D_
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {_x000D_
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;_x000D_
_x000D_
return {_x000D_
x: centerX + (radius * Math.cos(angleInRadians)),_x000D_
y: centerY + (radius * Math.sin(angleInRadians))_x000D_
};_x000D_
}_x000D_
_x000D_
var path = describeArc(150, 150, 50, 30, 0, 50)_x000D_
document.getElementById("p").innerHTML = path_x000D_
document.getElementById("path").setAttribute('d',path)
_x000D_
<p id="p">_x000D_
</p>_x000D_
<svg width="300" height="300" style="border:1px gray solid">_x000D_
<path id="path" fill="blue" stroke="cyan"></path>_x000D_
</svg>
_x000D_
and there you go!
You want to use the elliptical A
rc command. Unfortunately for you, this requires you to specify the Cartesian coordinates (x, y) of the start and end points rather than the polar coordinates (radius, angle) that you have, so you have to do some math. Here's a JavaScript function which should work (though I haven't tested it), and which I hope is fairly self-explanatory:
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = angleInDegrees * Math.PI / 180.0;
var x = centerX + radius * Math.cos(angleInRadians);
var y = centerY + radius * Math.sin(angleInRadians);
return [x,y];
}
Which angles correspond to which clock positions will depend on the coordinate system; just swap and/or negate the sin/cos terms as necessary.
The arc command has these parameters:
rx, ry, x-axis-rotation, large-arc-flag, sweep-flag, x, y
For your first example:
rx
=ry
=25 and x-axis-rotation
=0, since you want a circle and not an ellipse. You can compute both the starting coordinates (which you should M
ove to) and ending coordinates (x, y) using the function above, yielding (200, 175) and about (182.322, 217.678), respectively. Given these constraints so far, there are actually four arcs that could be drawn, so the two flags select one of them. I'm guessing you probably want to draw a small arc (meaning large-arc-flag
=0), in the direction of decreasing angle (meaning sweep-flag
=0). All together, the SVG path is:
M 200 175 A 25 25 0 0 0 182.322 217.678
For the second example (assuming you mean going the same direction, and thus a large arc), the SVG path is:
M 200 175 A 25 25 0 1 0 217.678 217.678
Again, I haven't tested these.
(edit 2016-06-01) If, like @clocksmith, you're wondering why they chose this API, have a look at the implementation notes. They describe two possible arc parameterizations, "endpoint parameterization" (the one they chose), and "center parameterization" (which is like what the question uses). In the description of "endpoint parameterization" they say:
One of the advantages of endpoint parameterization is that it permits a consistent path syntax in which all path commands end in the coordinates of the new "current point".
So basically it's a side-effect of arcs being considered as part of a larger path rather than their own separate object. I suppose that if your SVG renderer is incomplete it could just skip over any path components it doesn't know how to render, as long as it knows how many arguments they take. Or maybe it enables parallel rendering of different chunks of a path with many components. Or maybe they did it to make sure rounding errors didn't build up along the length of a complex path.
The implementation notes are also useful for the original question, since they have more mathematical pseudocode for converting between the two parameterizations (which I didn't realize when I first wrote this answer).
ES6 version:
const angleInRadians = angleInDegrees => (angleInDegrees - 90) * (Math.PI / 180.0);
const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => {
const a = angleInRadians(angleInDegrees);
return {
x: centerX + (radius * Math.cos(a)),
y: centerY + (radius * Math.sin(a)),
};
};
const arc = (x, y, radius, startAngle, endAngle) => {
const fullCircle = endAngle - startAngle === 360;
const start = polarToCartesian(x, y, radius, endAngle - 0.01);
const end = polarToCartesian(x, y, radius, startAngle);
const arcSweep = endAngle - startAngle <= 180 ? '0' : '1';
const d = [
'M', start.x, start.y,
'A', radius, radius, 0, arcSweep, 0, end.x, end.y,
].join(' ');
if (fullCircle) d.push('z');
return d;
};
I slightly modified the answer of opsb and made in support fill for the circle sector. http://codepen.io/anon/pen/AkoGx
JS
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}
function describeArc(x, y, radius, startAngle, endAngle){
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, arcSweep, 0, end.x, end.y,
"L", x,y,
"L", start.x, start.y
].join(" ");
return d;
}
document.getElementById("arc1").setAttribute("d", describeArc(200, 400, 100, 0, 220));
HTML
<svg>
<path id="arc1" fill="orange" stroke="#446688" stroke-width="0" />
</svg>
A slight modification to @opsb's answer. We cant draw a full circle with this method. ie If we give (0, 360) it will not draw anything at all. So a slight modification made to fix this. It could be useful to display scores that sometimes reach 100%.
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
}
function describeArc(x, y, radius, startAngle, endAngle){
var endAngleOriginal = endAngle;
if(endAngleOriginal - startAngle === 360){
endAngle = 359;
}
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";
if(endAngleOriginal - startAngle === 360){
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, arcSweep, 0, end.x, end.y, "z"
].join(" ");
}
else{
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, arcSweep, 0, end.x, end.y
].join(" ");
}
return d;
}
document.getElementById("arc1").setAttribute("d", describeArc(120, 120, 100, 0, 359));
ReactJS component based on the selected answer:
import React from 'react';
const polarToCartesian = (centerX, centerY, radius, angleInDegrees) => {
const angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;
return {
x: centerX + (radius * Math.cos(angleInRadians)),
y: centerY + (radius * Math.sin(angleInRadians))
};
};
const describeSlice = (x, y, radius, startAngle, endAngle) => {
const start = polarToCartesian(x, y, radius, endAngle);
const end = polarToCartesian(x, y, radius, startAngle);
const largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
const d = [
"M", 0, 0, start.x, start.y,
"A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
].join(" ");
return d;
};
const path = (degrees = 90, radius = 10) => {
return describeSlice(0, 0, radius, 0, degrees) + 'Z';
};
export const Arc = (props) => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">
<g transform="translate(150,150)" stroke="#000" strokeWidth="2">
<path d={path(props.degrees, props.radius)} fill="#333"/>
</g>
</svg>;
export default Arc;
I wanted to comment on @Ahtenus answer, specifically on Ray Hulha comment saying the codepen does not show any arc, but my reputation is not high enough.
The reason for this codepen not working is that its html is faulty with a stroke-width of zero.
I fixed it and added a second example here : http://codepen.io/AnotherLinuxUser/pen/QEJmkN.
The html :
<svg>
<path id="theSvgArc"/>
<path id="theSvgArc2"/>
</svg>
The relevant CSS :
svg {
width : 500px;
height : 500px;
}
path {
stroke-width : 5;
stroke : lime;
fill : #151515;
}
The javascript :
document.getElementById("theSvgArc").setAttribute("d", describeArc(150, 150, 100, 0, 180));
document.getElementById("theSvgArc2").setAttribute("d", describeArc(300, 150, 100, 45, 190));
The orginal polarToCartesian function by wdebeaum is correct:
var angleInRadians = angleInDegrees * Math.PI / 180.0;
Reversing of start and end points by using:
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
Is confusing (to me) because this will reverse the sweep-flag. Using:
var start = polarToCartesian(x, y, radius, startAngle);
var end = polarToCartesian(x, y, radius, endAngle);
with the sweep-flag = "0" draws "normal" counter-clock-wise arcs, which I think is more straight forward. See https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
you can use JSFiddle code i made for answer above:
https://jsfiddle.net/tyw6nfee/
all you need to do is change last line console.log code and give it your own parameter:
console.log(describeArc(255,255,220,30,180));
console.log(describeArc(CenterX,CenterY,Radius,startAngle,EndAngle))
Source: Stackoverflow.com