I have an oval (as an svg)
I want to distribute n points along the oval:
evenly
inset the points by x percent
constrained to the lower f percent of the oval
How can I do this programmatically? I just need a list of coordinates as output.
SVG of an ellipse:
<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="297mm" width="210mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="0 0 744.09448819 1052.3622047">
<g id="layer1">
<ellipse id="path8074" rx="78.559" ry="105.84" stroke="#000" cy="489.51" cx="314.29" stroke-width=".38188px" fill="none"/>
</g>
</svg>
Calculating equidistant point on ellipse circumference is quite complex math problem. Moreover, parallel curve (inward) for an ellipse is not ellipse.
If your ellipse is not very squeezed (a/b ratio is in range 0.5..2), you can use simple approximation through equidistantly spaced t parameter of ellipse equaion. Otherwise distance variance will too high and you need more complex approach based on arc length/distance calculation (requires numerical integration).
[Edit]: I added some correction of t to make point distribution better. Idea taken from here,
Using parallel curve equation, we can calculate points in such way (Delphi code as reference):
var
i, a, b, cx, cy, x, y, k, N: Integer;
sq, t: Double;
begin
N := 30; // Number of points
a := 120; //semiaxes
b := 200;
cx := 300; //center
cy:= 300;
k := 30; //inward distance
Canvas.Ellipse(cx - a, cy - b, cx + a, cy + b);
for i := 0 to N - 1 do begin
t := 2 * Pi * i / N;
//empirically adopted coefficient 0.3
t := t + 0.3 * ArcTan((a-b)*tan(t)/(a + b * sqr(tan(t))));
sq := 1.0 / Hypot(a * sin(t), b * cos(t));
x := Round(cx + (a - b * k * sq) * Cos(t));
y := Round(cy + (b - a * k * sq) * Sin(t));
Canvas.Ellipse(x-2,y-2,x+3,y+3);
end;
This is how I would do it:
I've changed the ellipse you have so that I center it around 0. To keep the position I translate the group <g id="layer1" transform="translate(314.29,489.51)">.
I draw another ellipse inside. The rx attribute of this ellipse is the rx of the path8074 ellipse multiplied by a factor. Let's say .8. The same for the ry. I'm calling this ellipse inner
I calculate the total length of the inner ellipse using let innerLength = inner.getTotalLength();
let n = 10: this is the number of points you need to inset
I'm using a loop for(let i = 0; i < n; i++){ to calculate the coords of the points on the inner path let length = i * innerLength / n; let p = inner.getPointAtLength(length);and to draw a circle to mark the point: drawCircle({cx:p.x,cy:p.y,r:2}, layer1)
const SVG_NS = 'http://www.w3.org/2000/svg';
let rx = path8074.getAttribute("rx");
let ry = path8074.getAttribute("ry");
let factor = .8;
inner.setAttributeNS(null,"rx",rx*factor);
inner.setAttributeNS(null,"ry",ry*factor);
let innerLength = inner.getTotalLength();
let n = 10;//n points
for(let i = 0; i < n; i++){
let length = i * innerLength / n;
let p = inner.getPointAtLength(length);
drawCircle({cx:p.x,cy:p.y,r:2}, layer1)
}
// a function to draw a circle
function drawCircle(o, parent) {
var circle = document.createElementNS(SVG_NS, 'circle');
for (var name in o) {
if (o.hasOwnProperty(name)) {
circle.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(circle);
return circle;
}
<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="297mm" width="210mm" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" viewBox="200 350 744.09448819 1052.3622047">
<g id="layer1" transform="translate(314.29,489.51)">
<ellipse id="path8074" rx="78.559" ry="105.84" stroke="#000" stroke-width=".38188px" fill="none"/>
<ellipse id="inner" stroke="#000" stroke-width=".38188px" fill="none"/>
</g>
</svg>
I hope it helps.
OBSERVATION: I've changed the viewBox of the svg element because I wanted the ellipses in view. You can change it back to what it was.
Short question: using SVG path, we can draw 99.99% of a circle and it shows up, but when it is 99.99999999% of a circle, then the circle won't show up. How can it be fixed?
The following SVG path can draw 99.99% of a circle: (try it below and see if you see 4 arcs or only 2 arcs, but note that if it is IE, it is rendered in VML, not SVG, but have the similar issue)
var paper = Raphael(0, 0, 300, 800);
// Note that there are supposed to be 4 arcs drawn, but you may see only 1, 2, or 3 arcs depending on which browser you use
paper.path("M 100 100 a 50 50 0 1 0 35 85").attr({stroke: "#080", opacity: 1, "stroke-width" : 6}) // this is about 62.5% of a circle, and it shows on most any browsers
paper.path("M 100 210 a 50 50 0 1 0 0.0001 0").attr({stroke: "#080", opacity: 1, "stroke-width" : 6}) // this one won't show anything if it is IE 8's VML, but will show if it is Chrome or Firefox's SVG. On IE 8, it needs to be 0.01 to show
paper.path("M 100 320 a 50 50 0 1 0 0.0000001 0").attr({stroke: "#080", opacity: 1, "stroke-width" : 6}) // this one won't draw anything at all, unless you change the 0.0000001 to 0.0001 on Chrome or Firefox... Safari will show it though...
paper.path("M 100 430 a 50 50 0 1 0 0 0").attr({stroke: "#080", opacity: 1, "stroke-width" : 6}) // this is 100% of a circle... even Safari won't show it
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.0/raphael-min.js"></script>
M 100 100 a 50 50 0 1 0 0.00001 0
But when it is 99.99999999% of a circle, then nothing will show at all?
M 100 100 a 50 50 0 1 0 0.00000001 0
And that's the same with 100% of a circle (it is still an arc, isn't it, just a very complete arc)
M 100 100 a 50 50 0 1 0 0 0
How can that be fixed? The reason is I use a function to draw a percentage of an arc, and if I need to "special case" a 99.9999% or 100% arc to use the circle function, that'd be kind of silly.
Again, a test case is above
(and if it is VML on IE 8, even the second circle won't show... you have to change it to 0.01)
Update:
This is because I am rendering an arc for a score in our system, so 3.3 points get 1/3 of a circle. 0.5 gets half a circle, and 9.9 points get 99% of a circle. But what if there are scores that are 9.99 in our system? Do I have to check whether it is close to 99.999% of a circle, and use an arc function or a circle function accordingly? Then what about a score of 9.9987? Which one to use? It is ridiculous to need to know what kind of scores will map to a "too complete circle" and switch to a circle function, and when it is "a certain 99.9%" of a circle or a 9.9987 score, then use the arc function.
I know it's a bit late in the game, but I remembered this question from when it was new and I had a similar dillemma, and I accidently found the "right" solution, if anyone is still looking for one:
<path
d="
M cx cy
m -r, 0
a r,r 0 1,0 (r * 2),0
a r,r 0 1,0 -(r * 2),0
"
/>
In other words, this:
<circle cx="100" cy="100" r="75" />
can be achieved as a path with this:
<path
d="
M 100, 100
m -75, 0
a 75,75 0 1,0 150,0
a 75,75 0 1,0 -150,0
"
/>
The trick is to have two arcs, the second one picking up where the first left off and using the negative diameter to get back to the original arc start point.
The reason it can't be done as a full circle in one arc (and I'm just speculating) is because you would be telling it to draw an arc from itself (let's say 150,150) to itself (150,150), which it renders as "oh, I'm already there, no arc necessary!".
The benefits of the solution I'm offering are:
it's easy to translate from a circle directly to a path, and
there is no overlap in the two arc lines (which may cause issues if you are using markers or patterns, etc). It's a clean continuous line, albeit drawn in two pieces.
None of this would matter if they would just allow textpaths to accept shapes. But I think they are avoiding that solution since shape elements like circle don't technically have a "start" point.
snippet demo:
circle, path {
fill: none;
stroke-width: 5;
stroke-opacity: .5;
}
circle {
stroke: red;
}
path {
stroke: yellow;
}
<?xml version="1.0" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1"
width="220px" height="220px">
<circle cx="100" cy="100" r="75" />
<path
d="
M 100, 100
m -75, 0
a 75,75 0 1,0 150,0
a 75,75 0 1,0 -150,0
"
/>
</svg>
Update:
If you are using the path for a textPath reference and you are wanting the text to render on the outer edge of the arc, you would use the exact same method but change the sweep-flag from 0 to 1 so that it treats the outside of the path as the surface instead of the inside (think of 1,0 as someone sitting at the center and drawing a circle around themselves, while 1,1 as someone walking around the center at radius distance and dragging their chalk beside them, if that's any help). Here is the code as above but with the change:
<path
d="
M cx cy
m -r, 0
a r,r 0 1,1 (r * 2),0
a r,r 0 1,1 -(r * 2),0
"
/>
Same for XAML's arc. Just close the 99.99% arc with a Z and you've got a circle!
In reference to Anthony’s solution, here is a function to get the path:
function circlePath(cx, cy, r){
return 'M '+cx+' '+cy+' m -'+r+', 0 a '+r+','+r+' 0 1,0 '+(r*2)+',0 a '+r+','+r+' 0 1,0 -'+(r*2)+',0';
}
A totally different approach:
Instead of fiddling with paths to specify an arc in svg, you can also take a circle element and specify a stroke-dasharray, in pseudo code:
with $score between 0..1, and pi = 3.141592653589793238
$length = $score * 2 * pi * $r
$max = 7 * $r (i.e. well above 2*pi*r)
<circle r="$r" stroke-dasharray="$length $max" />
Its simplicity is the main advantage over the multiple-arc-path method (e.g. when scripting you only plug in one value and you're done for any arc length)
The arc starts at the rightmost point, and can be shifted around using a rotate transform.
Note: Firefox has an odd bug where rotations over 90 degrees or more are ignored. So to start the arc from the top, use:
<circle r="$r" transform="rotate(-89.9)" stroke-dasharray="$length $max" />
Building upon Anthony and Anton's answers I incorporated the ability to rotate the generated circle without affecting it's overall appearance. This is useful if you're using the path for an animation and you need to control where it begins.
function(cx, cy, r, deg){
var theta = deg*Math.PI/180,
dx = r*Math.cos(theta),
dy = -r*Math.sin(theta);
return "M "+cx+" "+cy+"m "+dx+","+dy+"a "+r+","+r+" 0 1,0 "+-2*dx+","+-2*dy+"a "+r+","+r+" 0 1,0 "+2*dx+","+2*dy;
}
i made a jsfiddle to do it in here:
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 largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
].join(" ");
return d;
}
console.log(describeArc(255,255,220,134,136))
link
all you need to do is to change the input of console.log and get the result in console
For those like me who were looking for an ellipse attributes to path conversion:
const ellipseAttrsToPath = (rx,cx,ry,cy) =>
`M${cx-rx},${cy}a${rx},${ry} 0 1,0 ${rx*2},0a${rx},${ry} 0 1,0 -${rx*2},0 Z`
Adobe Illustrator uses bezier curves like SVG, and for circles it creates four points. You can create a circle with two elliptical arc commands...but then for a circle in SVG I would use a <circle /> :)
Written as a function, it looks like this:
function getPath(cx,cy,r){
return "M" + cx + "," + cy + "m" + (-r) + ",0a" + r + "," + r + " 0 1,0 " + (r * 2) + ",0a" + r + "," + r + " 0 1,0 " + (-r * 2) + ",0";
}
It's a good idea that using two arc command to draw a full circle.
usually, I use ellipse or circle element to draw a full circle.
Another way would be to use two Cubic Bezier Curves. That's for iOS folks using pocketSVG which doesn't recognize svg arc parameter.
C x1 y1, x2 y2, x y (or c dx1 dy1, dx2 dy2, dx dy)
The last set of coordinates here (x,y) are where you want the line to end. The other two are control points. (x1,y1) is the control point for the start of your curve, and (x2,y2) for the end point of your curve.
<path d="M25,0 C60,0, 60,50, 25,50 C-10,50, -10,0, 25,0" />
These answers are much too complicated.
A simpler way to do this without creating two arcs or convert to different coordinate systems..
This assumes your canvas area has width w and height h.
`M${w*0.5 + radius},${h*0.5}
A${radius} ${radius} 0 1 0 ${w*0.5 + radius} ${h*0.5001}`
Just use the "long arc" flag, so the full flag is filled. Then make the arcs 99.9999% the full circle. Visually it is the same. Avoid the sweep flag by just starting the circle at the rightmost point in the circle (one radius directly horizontal from the center).
I have following piece of code :
<svg>
<defs>
<rect id = "myRect"
x = "10"
y = "10"
height = "120"
width = "120"
stroke-width = "2px"
stroke = "red"
fill = "blue" />
</defs>
<g transform = "translate(100,30)">
<use xlink:href = "#myRect" />
</g>
<g transform = "translate(100, 100) rotate(45 ? ?)">
<rect id = "myRect"
x = "10"
y = "10"
height = "120"
width = "120"
stroke-width = "2px"
stroke = "green"
fill = "yellow" />
</g>
</svg>
When I translate rectangle without rotation, it is working fine. But when I rotate it, I wanted to rotate it around its center axis point. What should I need to pass to rotate attribute?
You would have to set the center as the center of the filled element. Like this:
svg .rotate {
transform-box: fill-box;
transform-origin: center;
transform: rotate(45deg);
}
You just need to add half the width/height of the rectangle to get its centre.
<g transform = "translate(100, 100) rotate(45 60 60)">
See transform documentation of the rotate function for more information.
The accepted answer works if you are drawing the rectangle starting at point (0,0) which was the OP case. However for me it was not!
Here is what worked for me:
To get the rectangle coordinates i used $('#rectID').getBBox()
method, should return [rect-height , rect-width , rect-y , rect x ]
The center point is ( rect-x + (rect-width/2) , rect-y + (rect-height/2) )
Here is a snippet i used on the browser console:
var coord = $('#elemID')[0].getBBox();
coord.x + (coord.width/2) +' '+ coord.y + (coord.height/2)
origin
x = x + width / 2
y = y + height / 2
here
x is 10
y is 10
width is 120
height is 120
<g transform = "translate(100, 100) rotate(45 70 70)">
I know this is an old post but if there are people out there who are looking make the values modifiable outside the group element
const centerX=100;
const centerY=100;
const firstAngle=45;
const secondAngle=60;
const thirdAngle =60;
<g transform ={`translate(${centerX}, ${centerY}) rotate(${firstAngle} ${secondAngle},
${thirdAngle})`}>
Nothing you just need to write the following code with the element in javascript:
element.rotate(angle Degree);
I'm not sure how to approach drawing a hollow circle in SVG.
I would like a ring shape filled with a colour and then have a black outline.
The way I thought about doing it was have 2 circles, one with a smaller radius than the other. The problem is when I fill them, how do I make the smaller circle take the same fill colour as what it sits on?
Just use fill="none" and then only the stroke (outline) will be drawn.
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="none" />
</svg>
Or this if you want two colours:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<circle cx="100" cy="50" r="40" stroke="black" stroke-width="3" fill="none" />
<circle cx="100" cy="50" r="39" stroke="red" stroke-width="2" fill="none" />
</svg>
MDragon00's answer works, but the inner and outer circles are not perfectly aligned (e.g. centered).
I modified his approach a little, using 4 semi-circle arcs (2 outer and 2 inner in reversed direction) to get the alignment exactly right.
<svg width="100" height="100">
<path d="M 50 10 A 40 40 0 1 0 50 90 A 40 40 0 1 0 50 10 Z M 50 30 A 20 20 0 1 1 50 70 A 20 20 0 1 1 50 30 Z" fill="#0000dd" stroke="#00aaff" stroke-width="3" />
</svg>
<!--
Using this path definition as d:
M centerX (centerY-outerRadius)
A outerRadius outerRadius 0 1 0 centerX (centerY+outerRadius)
A outerRadius outerRadius 0 1 0 centerX (centerY-outerRadius)
Z
M centerX (centerY-innerRadius)
A innerRadius innerRadius 0 1 1 centerX (centerY+innerRadius)
A innerRadius innerRadius 0 1 1 centerX (centerY-innerRadius)
Z
-->
Thanks to Chasbeen, I figured out how to make a true ring/donut in SVG. Note that the outer circle actually isn't closed, which is only apparent when you use a stroke. Very useful when you have many concentric rings, especially if they're interactive (say, with CSS hover commands).
For the draw command...
M cx, cy // Move to center of ring
m 0, -outerRadius // Move to top of ring
a outerRadius, outerRadius, 0, 1, 0, 1, 0 // Draw outer arc, but don't close it
Z // default fill-rule:even-odd will help create the empty innards
m 0 outerRadius-innerRadius // Move to top point of inner radius
a innerRadius, innerRadius, 0, 1, 1, -1, 0 // Draw inner arc, but don't close it
Z // Close the inner ring. Actually will still work without, but inner ring will have one unit missing in stroke
JSFiddle - Contains several rings and CSS to simulate interactivity. Note the downside that there's a single pixel missing at the starting point (at the top), which is only there if you add a stroke on.
Edit:
Found this SO answer (and better yet, this answer) which describes how to get the empty innards in general
You can do this as per the SVG spec by using a path with two components and fill-rule="evenodd". The two components are semi-circular arcs which join to form a circle (in the "d" attribute below, they each end with a 'z'). The area inside the inner circle does not count as part of the shape, hence interactivity is good.
To decode the below a little, the "340 260" is the top middle of the outer circle, the "290 290" is the radius of the outer circle (twice), the "340 840" is the bottom middle of the outer circle, the "340 492" is the top middle of the inner circle, the "58 58" is the radius of the inner circle (twice) and the "340 608" is the bottom middle of the inner circle.
<svg viewBox="0 0 1000 1000" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M340 260A290 290 0 0 1 340 840A290 290 0 0 1 340 260zM340 492A58 58 0 0 1 340 608A58 58 0 0 1 340 492z" stroke-width="4" stroke="rgb(0,0,0)" fill="rgb(0,0,255)">
<title>This will only display on the donut</title>
</path>
</svg>
This is the classic donut shape
I'm not sure if you are trying to achieve this with standard SVG or JavaScript that produces SVG
The objective can be achieved by including a relative "moveto" command in a single path definition
And click "donut holes" on the right side of the interactive examples.
At the very least you can see the path definition that made the red donut.
Here's a routine to create a bezier arc which is as close as makes no odds to a circle. You need four of them in a path for a complete circle.
BezierCurve BezierArc(double ox, double oy, double r, double thetaa, double thetab)
{
double theta;
double cpx[4];
double cpy[4];
int i;
int sign = 1;
while (thetaa > thetab)
thetab += 2 * Pi;
theta = thetab - thetaa;
if (theta > Pi)
{
theta = 2 * Pi - theta;
sign = -1;
}
cpx[0] = 1;
cpy[0] = 0;
cpx[1] = 1;
cpy[1] = 4.0 / 3.0 * tan(theta / 4);
cpx[2] = cos(theta) + cpy[1] * sin(theta);
cpy[2] = sin(theta) - cpy[1] * cos(theta);
cpx[3] = cos(theta);
cpy[3] = sin(theta);
cpy[1] *= sign;
cpy[2] *= sign;
cpy[3] *= sign;
for (i = 0; i < 4; i++)
{
double xp = cpx[i] * cos(thetaa) + cpy[i] * -sin(thetaa);
double yp = cpx[i] * sin(thetaa) + cpy[i] * cos(thetaa);
cpx[i] = xp;
cpy[i] = yp;
cpx[i] *= r;
cpy[i] *= r;
cpx[i] += ox;
cpy[i] += oy;
}
return BezierCurve({cpx[0], cpy[0]},{cpx[1], cpy[1]}, {cpx[2], cpy[2]}, {cpx[3], cpy[3]});
}