skip to Main Content

I have placed 4 smaller circles on a big circle at 0, 90, 180, 270 degree angle. I expect javascript to return exact same y for red and blue circle (as the shift is horizontal, y is the same but x changes) and exact same x for green and pink circle (as the shift is vertical, x is the same but y changes).

While the below the javascript returns the same x but returns different y (red = 237.76900370542438 and blue = 237.7690037054244). Moving forward, it is messing up all the calculations since they are not the same.

I don’t know what is the root cause of this but is there a way to solve it? I need them to be exact same.

const angle = [0, 90, 180, 270];
const angleCoord = [];

angle.forEach(
    (a, i) => {
        const element = d3.select('.circ1').node();
        const cx = parseFloat(element.getAttribute('cx'));
        const cy = parseFloat(element.getAttribute('cy'));
        const r = parseFloat(element.getAttribute('r'));
        const x = cx + Math.cos(a * Math.PI / 180) * r;
        const y = cy + Math.sin(a * Math.PI / 180) * r;
        angleCoord.push({
            angle: a,
            x: x,
            y: y
        })
    }
);

const color = ['red', 'green', 'blue', 'pink'];

d3.select('svg').append('g')
    .attr('class', 'circles')
    .selectAll('circle')
    .data(angleCoord)
    .join('circle')
    .attr('class', (_, i) => color[i])
    .attr('cx', d => d.x)
    .attr('cy', d => d.y)
    .attr('r', '4')
    .attr('fill', (_, i) => color[i]);
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
  </head>
  <script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
  <script type="text/javascript" src='https://cdn.jsdelivr.net/npm/[email protected]/bignumber.min.js'></script>
  <body>
    <div id="container" class="svg-container"></div>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1536 720">
      <circle class="circ1" cx="384" cy="237.76900370542438" r="122.23099629457562" fill="none" stroke="black"></circle>
    </svg>
    <script src="prod3.js"></script>
  </body>
</html>

2

Answers


  1. Chosen as BEST ANSWER

    Thanks @Wyck for sharing the link which led to another link.

    I was able to adapt the suggestions from this link and use it in the above case to resolve the issue by letting js take care of it within the code.

    const angle = [0, 90, 180, 270];
    const angleCoord = [];
    const color = ['red', 'green', 'blue', 'pink'];
    
    function round(value, precision) {
      const power = Math.pow(10, precision)
      return Math.round((value*power)+(Number.EPSILON*power)) / power
    }
    
    angle.forEach(
        (a, i) => {
            const element = d3.select('.circ1').node();
            const cx = round(parseFloat(element.getAttribute('cx')),15);
            const cy = round(parseFloat(element.getAttribute('cy')),15);
            const r = round(parseFloat(element.getAttribute('r')),15);
            const _sin = round(Math.sin(a * Math.PI / 180),15);
            const _cos = round(Math.cos(a * Math.PI / 180),15);
            const x = cx + _cos * r;
            const y = cy + _sin * r;
            
            console.log(`${color[i]}`,cx,cy,r,_sin,_cos,x,y,);
            
            angleCoord.push({
                angle: a,
                x: x,
                y: y
            })
        }
    );
    
    d3.select('svg').append('g')
        .attr('class', 'circles')
        .selectAll('circle')
        .data(angleCoord)
        .join('circle')
        .attr('class', (_, i) => color[i])
        .attr('cx', d => d.x)
        .attr('cy', d => d.y)
        .attr('r', '4')
        .attr('fill', (_, i) => color[i]);
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
      </head>
      <script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
      <script type="text/javascript" src='https://cdn.jsdelivr.net/npm/[email protected]/bignumber.min.js'></script>
      <body>
        <div id="container" class="svg-container"></div>
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1536 720">
          <circle class="circ1" cx="384" cy="237.76900370542438" r="122.23099629457562" fill="none" stroke="black"></circle>
        </svg>
        <script src="prod3.js"></script>
      </body>
    </html>


  2. The accuracy of sin 180 is about as accurate as the accuracy of PI/2 plus or minus some infinitesimal error.

    Consider using the "exact" values for sin and cos of the multiples of 90° angles in degrees if you want "exact" values. e.g.:

    const lookupTable = {
      0: { sin: 0, cos: 1 },
      90: { sin: 1, cos: 0 },
      180: { sin: 0, cos: -1 },
      270: { sin: -1, cos: 0 }
    };
    
    const exactSin = degrees => lookupTable[degrees].sin;
    const exactCos = degrees => lookupTable[degrees].cos;
    
    const angle = [0, 90, 180, 270];
    const angleCoord = [];
    
    const lookupTable = {
      0: { sin: 0, cos: 1 },
      90: { sin: 1, cos: 0 },
      180: { sin: 0, cos: -1 },
      270: { sin: -1, cos: 0 }
    };
    
    const exactSin = (degrees) => lookupTable[degrees].sin;
    const exactCos = (degrees) => lookupTable[degrees].cos;
    
    angle.forEach(
        (a, i) => {
            const element = d3.select('.circ1').node();
            const cx = parseFloat(element.getAttribute('cx'));
            const cy = parseFloat(element.getAttribute('cy'));
            const r = parseFloat(element.getAttribute('r'));
            const x = cx + exactCos(a) * r;
            const y = cy + exactSin(a) * r;
    console.log({x, y});
            angleCoord.push({
                angle: a,
                x: x,
                y: y
            })
        }
    );
    
    const color = ['red', 'green', 'blue', 'pink'];
    
    d3.select('svg').append('g')
        .attr('class', 'circles')
        .selectAll('circle')
        .data(angleCoord)
        .join('circle')
        .attr('class', (_, i) => color[i])
        .attr('cx', d => d.x)
        .attr('cy', d => d.y)
        .attr('r', '4')
        .attr('fill', (_, i) => color[i]);
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
      </head>
      <script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>
      <script type="text/javascript" src='https://cdn.jsdelivr.net/npm/[email protected]/bignumber.min.js'></script>
      <body>
        <div id="container" class="svg-container"></div>
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1536 720">
          <circle class="circ1" cx="384" cy="237.76900370542438" r="122.23099629457562" fill="none" stroke="black"></circle>
        </svg>
        <script src="prod3.js"></script>
      </body>
    </html>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search