How to display an SVG circle using start and endAngle

I have rendered the svg circle using start and endAngle. It worked great. But when I draw a full circle (startAngle as 70 and endAngle as 70), the output is very different (except 0, 90, 180, 270). what did i do wrong for this code?

            function getPathArc(center, start, end, radius) {
                end -= this.isCompleteAngle(start, end) ? 0.0001 : 0;
                var degree = end - start;
                degree = degree < 0 ? (degree + 360) : degree;
                return this.getCirclePath(
                    center, getLocationFromAngle(start, radius, center),
                    getLocationFromAngle(end, radius, center), radius, (degree < 180) ? 0 : 1
                );
            }

            function getCirclePath(center, start, end, radius, clockWise) {
                return 'M ' + start.x + ' ' + start.y + ' A ' + radius + ' ' +
                    radius + ' 0 ' + clockWise + ' 1 ' + end.x + ' ' + end.y;
            }

            function getLocationFromAngle(degree, radius, center) {
                var radian = (degree * Math.PI) / 180;
                return {
                    x : Math.cos(radian) * radius + center.x,
                    y : Math.sin(radian) * radius + center.y
                }
            }

            function isCompleteAngle(startAngle, endAngle) {
                var totalAngle = endAngle - startAngle;
                totalAngle = totalAngle <= 0 ? (totalAngle + 360) : totalAngle;
                return Math.floor(totalAngle / 360) !== 0;
            }

      

Link example: https://jsfiddle.net/fNPvf/43106/

enter image description here

The green circle displays correctly because the start and end elements are 90, even if I change the angle to 0, 180, 270 and 360 it will work. but the actual problem is the red circle which I am using 70, even the problem will occur except for those angles (0, 90, 180, 270, 360).

how to fix this problem?

+3


source to share


2 answers


This difference between circles is due to numerical precision effects. Due to the way SVG arcs and floating point arithmetic work, minor changes in the start and end points can be exaggerated in the end arc. The larger the angle of your arc, the more the effect comes into play.

To draw an arc, the render must first determine the center of the circle. If you try to make a 360 degree arc, your start and end points will be nearly the same.

Consider the following illustration:

enter image description here

The green and red points are the start and end points of the arc. The gray point is the center of the circle that the renderer calculates to draw the arc.

The arc in the figure is approximately 359 degrees. Now imagine that you are moving the red and green points closer to each other. The calculation required to determine the center point will become increasingly susceptible to inaccuracies in start and end coordinates and in floating point arithmetic functions.



Add to this the fact that the functions sin()

and cos()

are only approximations of the sin and cos curves. Remember, browser Javascript engines must balance speed and accuracy.

Then add to that the fact that most (if not all) SVG rendering engines approach arcs using Bezier curves. But a bezier cannot perfectly represent a circular arc.

Hopefully you can now understand why you are getting results. Trying to represent large angles with one arc is a bad idea. My personal recommendation is to use at least three or four arcs for a full circle.

function getPathArc(center, start, end, radius) {
  if (end == start) end += 360;
  var degree = end - start;
  degree = degree < 0 ? (degree + 360) : degree;
  var points = [];
  points.push( getLocationFromAngle(start, radius, center) );
  points.push( getLocationFromAngle(start+degree/3, radius, center) );
  points.push( getLocationFromAngle(start+degree*2/3, radius, center) );
  points.push( getLocationFromAngle(end, radius, center) );
  return this.getCirclePath(points, radius, (degree < 180) ? 0 : 1);
}
			
function getCirclePath(points, radius, clockWise) {
  return ['M', points[0].x, points[0].y,
          'A', radius, radius, 0, 0, clockWise, points[1].x, points[1].y,
          'A', radius, radius, 0, 0, clockWise, points[2].x, points[2].y,
          'A', radius, radius, 0, 0, clockWise, points[3].x, points[3].y
         ].join(' ');
}
			
function getLocationFromAngle(degree, radius, center) {
  var radian = (degree * Math.PI) / 180;
  return {
    x : Math.cos(radian) * radius + center.x,
    y : Math.sin(radian) * radius + center.y
  }
}
			
document.getElementById("arc1").setAttribute("d", getPathArc({x:250,y:250}, 90, 90, 200));
document.getElementById("arc2").setAttribute("d", getPathArc({x:250,y:250}, 70, 70, 200));
      

<svg width="500" height="500">
  <path id="arc1" fill="none" stroke="green" stroke-width="8" />
  <path id="arc2" fill="none" stroke="red" stroke-width="3" />
</svg>
      

Run codeHide result


+3


source


I propose a solution that works, but whose exact mechanism I cannot explain (Edit: explained in @LeBeau's answer )

This is the culprit in your code:

end -= this.isCompleteAngle(start, end) ? 0.0001 : 0;

      

We can clearly see that if we increase the value when triple true

, i.e. increasing the amount of subtraction of the variable end

, the bizarre effect disappears. Show it.

With very little subtraction, just 0.00001

to make the weirder effect more visible:

function getPathArc(center, start, end, radius) {
  end -= this.isCompleteAngle(start, end) ? 0.00001 : 0;
  var degree = end - start;
  degree = degree < 0 ? (degree + 360) : degree;
  return this.getCirclePath(
    center, getLocationFromAngle(start, radius, center),
    getLocationFromAngle(end, radius, center), radius, (degree < 180) ? 0 : 1
  );
}

function getCirclePath(center, start, end, radius, clockWise) {

  return 'M ' + start.x + ' ' + start.y + ' A ' + radius + ' ' +
    radius + ' 0 ' + clockWise + ' 1 ' + end.x + ' ' + end.y;
}

function getLocationFromAngle(degree, radius, center) {
  var radian = (degree * Math.PI) / 180;
  return {
    x: Math.cos(radian) * radius + center.x,
    y: Math.sin(radian) * radius + center.y
  }
}

function isCompleteAngle(startAngle, endAngle) {
  var totalAngle = endAngle - startAngle;
  totalAngle = totalAngle <= 0 ? (totalAngle + 360) : totalAngle;
  return Math.floor(totalAngle / 360) !== 0;
}

window.onload = function() {
  document.getElementById("arc1").setAttribute("d", getPathArc({
    x: 250,
    y: 250
  }, 90, 90, 200));
  document.getElementById("arc2").setAttribute("d", getPathArc({
    x: 250,
    y: 250
  }, 70, 70, 200));
};
      

<svg width="500" height="500">
  <path id="arc1" fill="none" stroke="green" stroke-width="2" />
  <path id="arc2" fill="none" stroke="red" stroke-width="2" />
</svg>
      

Run codeHide result


Now with a lot of subtraction 0.01

:

function getPathArc(center, start, end, radius) {
  end -= this.isCompleteAngle(start, end) ? 0.01 : 0;
  var degree = end - start;
  degree = degree < 0 ? (degree + 360) : degree;
  return this.getCirclePath(
    center, getLocationFromAngle(start, radius, center),
    getLocationFromAngle(end, radius, center), radius, (degree < 180) ? 0 : 1
  );
}

function getCirclePath(center, start, end, radius, clockWise) {

  return 'M ' + start.x + ' ' + start.y + ' A ' + radius + ' ' +
    radius + ' 0 ' + clockWise + ' 1 ' + end.x + ' ' + end.y;
}

function getLocationFromAngle(degree, radius, center) {
  var radian = (degree * Math.PI) / 180;
  return {
    x: Math.cos(radian) * radius + center.x,
    y: Math.sin(radian) * radius + center.y
  }
}

function isCompleteAngle(startAngle, endAngle) {
  var totalAngle = endAngle - startAngle;
  totalAngle = totalAngle <= 0 ? (totalAngle + 360) : totalAngle;
  return Math.floor(totalAngle / 360) !== 0;
}

window.onload = function() {
  document.getElementById("arc1").setAttribute("d", getPathArc({
    x: 250,
    y: 250
  }, 90, 90, 200));
  document.getElementById("arc2").setAttribute("d", getPathArc({
    x: 250,
    y: 250
  }, 70, 70, 200));
};
      

<svg width="500" height="500">
  <path id="arc1" fill="none" stroke="green" stroke-width="2" />
  <path id="arc2" fill="none" stroke="red" stroke-width="2" />
</svg>
      

Run codeHide result


You see? The two arcs overlap if the subtraction is greater.

Hence, the solution increases the difference in end -= someValue

.



Further research:

During research, I console.log

edited each variable. They are almost the same.

Then I copied both elements in both fragments. To my surprise, they are almost the same. Look, these are the two paths in the first snippet:

<path id="arc1" fill="none" stroke="green" stroke-width="2" 
    d="M 250 450 A 200 200 0 1 1 250.0349065848627 449.9999969538258"></path>
<path id="arc2" fill="none" stroke="red" stroke-width="2" 
    d="M 318.40402866513375 437.93852415718163 A 200 200 0 1 1 318.40406146659313 437.9385122184236"></path>

      

Now there are two paths created by the second snippet:

<path id="arc1" fill="none" stroke="green" stroke-width="2" 
    d="M 250 450 A 200 200 0 1 1 250.0000349065851 449.99999999999693"></path>
<path id="arc2" fill="none" stroke="red" stroke-width="2" 
    d="M 318.40402866513375 437.93852415718163 A 200 200 0 1 1 318.4368290834931 437.92658253955653"></path>

      

As you can see, the red paths are almost identical except for the start and end x-axis values. It seems that the more similar they are, the greater the effect.

To test this, let's see what happens if the values ​​are exactly the same (the black circle marks the beginning):

<svg width="500" height="700">
  <path id="arc1" fill="none" stroke="green" stroke-width="2" d="M 250 450 A 200 200 0 1 1 250.0349065848627 449.9999969538258"></path>
  <path id="arc2" fill="none" stroke="red" stroke-width="2" d="M 318.4368290834931 437.93852415718163 A 200 200 0 1 1 318.4368290834931 437.92658253955653"></path>
  <circle cx="318.4368290834931" cy="437.93852415718163" r="4"></circle>
</svg>
      

Run codeHide result


As I said, I cannot explain it. But I'm sure one of the SVG specialists will be there soon.

+1


source







All Articles