Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Circle approximation that is a regular polygon with N corners

I have to implement the idea of a circle approximation that is a regular polygon with N corners, whereas N is defined by the user.

For example, if N=3 I would have a triangle. With n=5 I would a shape that starts resembling a circle. As I increase N, I would get closer and closer to the shape of a circle.

This idea it's very similar to what was asked and answered on the on the follwing question/solution: Draw regular polygons inscribed in a circle , however, they used raphael.js and not D3.js.

What I tried to do:

var vis = d3.select("body").append("svg")
         .attr("width", 1000)
         .attr("height", 667);


var svg = d3.select('svg');
var originX = 200;
var originY = 200;
var outerCircleRadius = 60;


var outerCircle = svg.append("circle").attr({
    cx: originX,
    cy: originY,
    r: outerCircleRadius,
    fill: "none",
    stroke: "black"
});

var chairWidth = 10;

var chairOriginX = originX + ((outerCircleRadius) * Math.sin(0));
var chairOriginY = originY - ((outerCircleRadius) * Math.cos(0));



var chair = svg.append("rect").attr({
    x: chairOriginX - (chairWidth / 2),
    y: chairOriginY - (chairWidth / 2),
    width: chairWidth,
    opacity: 1,
    height: 20,
    fill: "none",
    stroke: "blue"
});

var n_number = 5
var n_angles = 360/n_number
var angle_start=0;
var angle_next;

console.log(chair.node().getBBox().x);
console.log(chair.node().getBBox().y);
chair.attr("transform", "rotate(" + (angle_start+n_angles+n_angles) + ", 200, 200)");



        var circle = svg.append("circle")
                    .attr("cx", 195)
                    .attr("cy", 135)
                    .attr("r", 50)
                    .attr("fill", "red");




var chairOriginX2 = originX + ((outerCircleRadius) * Math.sin(0));
var chairOriginY2 = originY - ((outerCircleRadius) * Math.cos(0));


var chair2 = svg.append("rect").attr({
    x: chairOriginX2 - (chairWidth / 2),
    y: chairOriginY2 - (chairWidth / 2),
    width: chairWidth,
    opacity: 1,
    height: 20,
    fill: "none",
    stroke: "blue"
});

console.log(chair2.node().getBBox().x);
console.log(chair2.node().getBBox().y);

My idea, that did not work, was trying to create a circle ("outerCircle") which I would slide within the circunference ("chair.attr("transform"...") of the circle, based on N, obtaining several different (x,y) coordinates. Then, I would feed (x,y) coordinates to a polygon.

I believe that my approach for this problem is wrong. Also, the part that I got stuck is that I am not being able to keep sliding whitin the circunference and storing each different (x,y) coordinate. I tried "console.log(chair2.node().getBBox().x);" but it is always storing the same coordinate, which it is of the origin.

like image 359
Fsoares Avatar asked Mar 05 '23 13:03

Fsoares


2 Answers

I've simplified your code for clarity. To get the x of a point on a circle you use the Math.cos(angle) and for the y you use the Math.sin(angle). This was your error. Now you can change the value of the n_number

var SVG_NS = 'http://www.w3.org/2000/svg';

var originX = 200;
var originY = 200;
var outerCircleRadius = 60;

var polygon = document.createElementNS(SVG_NS, 'polygon');
svg.appendChild(polygon);

let points="";

var n_number = 5;
var n_angles = 2*Math.PI/n_number
// building the value of the `points` attribute for the polygon
for(let i = 0; i < n_number; i++){
  let x = originX + outerCircleRadius * Math.cos(i*n_angles);
  let y = originY + outerCircleRadius * Math.sin(i*n_angles);
  points += ` ${x},${y} `;
}
// setting the value of the points attribute of the polygon
polygon.setAttributeNS(null,"points",points)
svg{border:1px solid;width:90vh;}

polygon{fill: none;
    stroke: blue}
<svg id="svg" viewBox = "100 100 200 200" >
  <circle cx="200" cy="200" r="60" fill="none" stroke="black" />  
</svg>

This is another demo where I'm using an input type range to change the n_numbervariable

var SVG_NS = 'http://www.w3.org/2000/svg';

var originX = 200;
var originY = 200;
var outerCircleRadius = 60;

var polygon = document.createElementNS(SVG_NS, 'polygon');
svg.appendChild(polygon);

let points="";

var n_number = 5;

setPoints(n_number);


theRange.addEventListener("input", ()=>{
  n_number = theRange.value;
  setPoints(n_number)
});


function setPoints(n_number){
var n_angles = 2*Math.PI/n_number;
  points = ""
// building the value of the `points` attribute for the polygon
for(let i = 0; i < n_number; i++){
  let x = originX + outerCircleRadius * Math.cos(i*n_angles);
  let y = originY + outerCircleRadius * Math.sin(i*n_angles);
  points += ` ${x},${y} `;
}
// setting the value of the points attribute of the polygon
polygon.setAttributeNS(null,"points",points);
}
svg{border:1px solid; width:90vh;}

polygon{fill: none;
    stroke: blue}
<p><input type="range" min="3" max="50" value="5" id="theRange" /></p>
<svg id="svg" viewBox = "100 100 200 200" >
  <circle cx="200" cy="200" r="60" fill="none" stroke="black" />  
</svg>
like image 83
enxaneta Avatar answered Apr 30 '23 02:04

enxaneta


The answer provided by enxaneta is perfectly fine and is certainly the classical approach to this. However, I often favor letting the browser do the trigonometry instead of doing it on my own. Typical examples include my answer to "Complex circle diagram" or the one to "SVG marker - can I set length and angle?". I am not even sure if they outperform the more classical ones but I like them for their simplicity nonetheless.


My solution focuses on the SVGGeometryElement and its methods .getTotalLength() and .getPointAtLength(). Since the SVGCircleElement interface extends that interface those methods are available for an SVG circle having the following meanings:

  1. .getTotalLength(): The circumference of the circle.

  2. .getPointAtLength(): The point in x-/y-coordinates on the circle at the given length. The measurement per definition begins at the 3 o'clock position and progresses clockwise.

Given these explanations it becomes apparent that you can divide the circle's total length, i.e. its circumference, by the number of points for your approximation. This gives you the step distance along the circle to the next point. By summing up these distances you can use the second method to obtain the x-/y-coordinates for each point.

The coding could be done along the following lines:

// Calculate step length as circumference / number of points.
const step = circleElement.getTotalLength() / count; 

// Build an array of points on the circle.
const data = Array.from({length: count}, (_, i) => {
  const point = circleElement.getPointAtLength(i * step);   // Get coordinates of next point.
  return `${point.x},${point.y}`; 
});
polygon.attr("points", data.join(" "));

Slick and easy! No trigonometry involved.

Finally, a complete working demo:

// Just setup, not related to problem.
const svg = d3.select("body")
  .append("svg")
    .attr("width", 500)
    .attr("height", 500);
    
const circle = svg.append("circle")
    .attr("cx", "150")
    .attr("cy", "150")
    .attr("r", "100")
    .attr("fill", "none")
    .attr("stroke", "black");
    
const polygon = svg.append("polygon")
    .attr("fill", "none")
    .attr("stroke", "blue");
    
const circleElement = circle.node();
const ranger = d3.select("#ranger").on("input", update);
const label = d3.select("label");

// This function contains all the relevant logic.
function update() {
  let count = ranger.node().value;
  label.text(count);
  // Calculate step length as circumference / number of points.
  const step = circleElement.getTotalLength() / count; 
  // Build an array of all points on the circle.
  const data = Array.from({length: count}, (_, i) => {
    const point = circleElement.getPointAtLength(i * step);   // Get coordinates of next point.
    return `${point.x},${point.y}`; 
  });
  polygon.attr("points", data.join(" "));
}

update();
<script src="https://d3js.org/d3.v5.js"></script>
<p>
  <input id="ranger" type="range" min="3" max="15" value="5">
  <label for="ranger"></label>
</p>
like image 38
altocumulus Avatar answered Apr 30 '23 03:04

altocumulus