Circle drawing with SVG's arc path
Asked Answered
E

12

185

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:

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.

Eudocia answered 20/4, 2011 at 23:57 Comment(8)
Both of those links go to the same thing, and it works fine in Safari.Redfield
right, same link, i just want people to see the test case earlier so I add the link at the beginning of the question. Right safari will do it, how nice... Chrome and Firefox won't... kind of strange coz Safari and Chrome are both Webkit... but does SVG engine depend on Webkit?Eudocia
@Marcin looks fine how? do you see 4 arcs or 2 arcs? did you even look at the code?Eudocia
No, I didn't. Did you mention there were four circles, or make them different colors, so people who are helping you can do it more easily?Rox
@Rox if you scan the code briefly, you can tell it is trying to draw 4 arcs... or, don't scan the code, and it is other people's fault, as usualEudocia
@動靜能量: You realise it's not my job to fix your code?Rox
an updated jsfiddle with Raphael included as a library, to get around cross-origin error loading raphael.js: jsfiddle.net/DFhUF/1381Hayne
codepen.io/dcdev/pen/upjDySubphylum
H
40

Same for XAML's arc. Just close the 99.99% arc with a Z and you've got a circle!

Hays answered 21/4, 2011 at 0:41 Comment(7)
so can you close the third case in the jsfiddle sample and make it work?Eudocia
with lowering the value to 0.0001, yes. the issue sounds related to the various browser's implementation of WebKit, not SVG itself. But that is how you make a circle in SVG/XAMLs Arc component.Hays
Ahhh, cheating, always the best solution. Still need to handle the 100% case, but just by "rounding down" to 99.999% rather than with a whole separate code branch.Cherisecherish
Any demo? This answer doesn't fulfillJohanna
@MedetTleukabiluly you have the arc above in the question, add a z at the end and done. You may have to remove zeroes, for example in latest Chrome I had to write 0.0001 to make it work. If you're looking for where to put the path string, look at Paths on MDN.Gowan
This is quite a hack plus you will not get a perfect circle. Rather see the solution below provided by Anthony (two semicircles will make it up for you).Valentino
@ToddMain can you make jsfiddle.net/DFhUF/1381 work? I tried adding a space and then a z or Z to the end of the path, as in jsfiddle.net/m3uyxrL7/1 and it still won't show the 3rd or 4th circleEudocia
B
519

Update:

I have adjusted based on feedback from @loominade, so that the drawing of the circle goes from "East, South, West, North", making stroke-dashoffset and startOffset behave more consistently with a native circle element.


I had a similar dilemma, and I found this solution:

<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:

  1. it's easy to translate from a circle directly to a path, and
  2. 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
    "
/>
Batruk answered 7/5, 2012 at 5:58 Comment(13)
+1, Thanks for the formulas. Of course, the first two commands could be consolidated.Colchester
+1 for the clarity and practicality of the solution in general, but especially for 'shape elements like circle don't technically have a "start" point' - such a clear way to express the problem.Psf
I think the problem here is not that "oh, I'm already there, no arc necessary!", as by providing 1 as large-arc-flag you explicitly tell the engine that you want it to go the further way. The real problem is that there are infinitely many circles which fullfil constraints of passing trough your point. This explains why when you want 360* arc, then engine does not know what to do. The reason it does not work for 359.999 is that this is numerically unstable computation, and while there technically is exactly one circle which matches the request, it would probably not be the one you wanted anywayAskins
@Askins - You're right, I was thinking in terms of the large-arc and sweep being off and the arc having no destination. Or at the very least that even with large-arc and sweep enabled that there was some fundamental design that defined an arc as the curve between two points (so that with one point used twice, it saw it as a collapsed single point.) Your vision of the arc potentially drawing 360 circles around the one point makes sense, though it would collapse to one circle if a center is defined, which raises the question of could a single arc make a full circle if a center did exist?Batruk
Anyone looking to JavaScript function this: function circPath(r) { return "m " + (-r) + ", 0 a " + r + "," + r + " 0 1,0 " + (r * 2) + ",0 a " + r + "," + r + " 0 1,0" + (-(r * 2)) + ",0"; }Quatrefoil
"shape elements like circle don't technically have a "start" point". This is not actually true. The SVG spec absolutely specifies where the start point of all shapes is. It has to do so in order for dash arrays to work on shapes.Taneka
hm... so it requires 2 arcs? It would seem like it is a bit of a hack... because theoretically, there should be a clean and simple method to be able to show a 99.99 complete circle and a 99.999999999% complete circle. If you can, can you use Raphael to do it (and SVG XML as an addition) because the original requirement was using RaphaelJS (unless SVG XML should and would give identical results). But your 2 arc solution seems quite simple... can it work with: given a p% complete circle, show it, when p can be 0, 3, 10, 25, 33, 50, 70, 75, 90... any percentage at all?Eudocia
How is drawing two arcs a hack? A circle is two arcs. And drawing 99.99% -- to me -- is the hack. Hence the need for the discussion in the first place.Batruk
@太極者無極而生 - Also, no, I think as far as I have tested, it fails pretty spectacularly when you try to make a partial circle. At least when all you do is change one obvious parameter. But I've always had trouble with achieving what you are describing with paths anyway. Like wanting to throw my computer across the room frustration.Batruk
This, functionified & compressed: function circPath(x,y,r){return['M',x,y,'m',-r,'0a',r,r,0,1,0,r*2,'0a',r,r,0,1,0,-r*2,0].join(' ')}Zimmermann
"rendering on the outer edge of the arc" FTW!Eisen
Shameless plug: justingolden.me/svg-circle-to-path utility I made in two seconds so you've got a UI if you're doing this often : )Jodijodie
This is a great answer. The "Update" section is the Flip Horizontal of the main answer.Writeoff
H
40

Same for XAML's arc. Just close the 99.99% arc with a Z and you've got a circle!

Hays answered 21/4, 2011 at 0:41 Comment(7)
so can you close the third case in the jsfiddle sample and make it work?Eudocia
with lowering the value to 0.0001, yes. the issue sounds related to the various browser's implementation of WebKit, not SVG itself. But that is how you make a circle in SVG/XAMLs Arc component.Hays
Ahhh, cheating, always the best solution. Still need to handle the 100% case, but just by "rounding down" to 99.999% rather than with a whole separate code branch.Cherisecherish
Any demo? This answer doesn't fulfillJohanna
@MedetTleukabiluly you have the arc above in the question, add a z at the end and done. You may have to remove zeroes, for example in latest Chrome I had to write 0.0001 to make it work. If you're looking for where to put the path string, look at Paths on MDN.Gowan
This is quite a hack plus you will not get a perfect circle. Rather see the solution below provided by Anthony (two semicircles will make it up for you).Valentino
@ToddMain can you make jsfiddle.net/DFhUF/1381 work? I tried adding a space and then a z or Z to the end of the path, as in jsfiddle.net/m3uyxrL7/1 and it still won't show the 3rd or 4th circleEudocia
D
35

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,1 '+(r*2)+',0 a '+r+','+r+' 0 1,1 -'+(r*2)+',0';
}
Distinctly answered 20/4, 2011 at 23:57 Comment(1)
Just wanted to say that this functions saved my life after 3 hours of searchBarr
C
14

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" />
Calicle answered 20/4, 2011 at 23:57 Comment(4)
be aware that this solution is only applicable for cases where you a not using any type of fill and you only need an arc segment with no deviations. The moment you want to draw sectors you even need a new svg path node, which could otherwise be handled in a single path tag.Lm
@Lm not really, if you take stroke-width twice the value of r you get perfect sectors.Calicle
With stroke-dasharray="0 10cm 5cm 1000cm" it is possible to avoid the transformationCleisthenes
@Cleisthenes yes, but the downside is that you cannot have one parameter in the dasharray to scale from 0% to 100% fill (which was one of my objectives)Calicle
R
10

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;
}
Rizzio answered 20/4, 2011 at 23:57 Comment(1)
This is exactly what I needed. I was using the greensock morph plugin to morph a circle path to a rectangular path and needed a slight rotation to get it to look just right.Eyelet
I
6

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 the console

Item answered 20/4, 2011 at 23:57 Comment(2)
This was exactly what i was looking for. Best answer here! upvote this guyPetulance
You saved my life :) Thank you for this wonderful answer! Looking for this the entire day!Activism
D
6

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`
Designation answered 20/4, 2011 at 23:57 Comment(0)
H
5

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 /> :)

Halitosis answered 21/4, 2011 at 2:42 Comment(7)
"You can create a circle with two elliptical arc commands"? what do you mean? Can't you just use one? At least by making to go from (0,0) to (0.01, 0) so that it renders something. For using circle, please see the Update in the question instead.Eudocia
No, I don't think you can use just one. You've shown that you can't, and you have a similar problem with HTML5 Canvas arcs.Halitosis
you mean, using arc to create a full circle by using the left arc and the right arc? So arc cannot draw a complete circle... to do so two arc paths are neededEudocia
@動靜能量 Correct, two arc paths are needed (or the ugly hack of one arc going most of the way and then a closepath command to straight-line to the end).Halitosis
Illustrator sucks. You can't make a circle with bezier curves. Only something close to a circle, but still not a circle.Marieann
@Marieann actually, if you create a circle with Illustrator and save as SVG it creates a <circle> element even though illustrator shows a four-segment Bézier. Only if you adjust the handles or anchors does it export as a <path> approximation of a circle.Halitosis
Here is the path cubic bezier link w3.org/TR/SVG/paths.html#PathDataCubicBezierCommands.Povertystricken
C
4

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";
}
Cloying answered 20/4, 2011 at 23:57 Comment(1)
This is a great answer! Just a small addition: This path is drawn East, North, West, South. If you want the circle to behave exactly like a <circle>, you have to change the direction to East, South, West, North: "M" + cx + "," + cy + "m" + (r) + ",0" + "a" + r + "," + r + " 0 1,1 " + (-r * 2) + ",0" + "a" + r + "," + r + " 0 1,1 " + (r * 2) + ",0" The advantage is that stroke-dashoffset and startOffset now work the same way.Procrustean
S
3

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).

Specialistic answered 20/4, 2011 at 23:57 Comment(0)
B
2

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)

Cubic Bezier curve

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" />
Benzol answered 20/4, 2011 at 23:57 Comment(1)
The bezier approximation of a circle is described at length here: spencermortensen.com/articles/bezier-circleOpenhearted
C
2

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.

Cacography answered 8/9, 2012 at 8:34 Comment(4)
A major limitation of svg is that shape elements can't be used for text paths. This is likely the primary motivation for everyone who visits this question.Batruk
@Batruk they can now. Firefox supports textPath against any shape. I suspect Chrome does too.Octillion
@RobertLongson - Can you provide an example or links to an example? Curious how it decides where the text path starts and whether it is on the outside or inside (etc) when it is drawn without a traditional starting point.Batruk
@Batruk See the SVG 2 specification e.g. w3.org/TR/SVG2/shapes.html#CircleElement, also the new textPath side attributeOctillion

© 2022 - 2024 — McMap. All rights reserved.