5

JS fiddle

I have a coordinates array populated by mouse clicks on a canvas.

var pointsArray = [];

This array is pushed x and y values using a click event.

pointsArray.push({x: xVal, y: yVal});

I iterate the points array and draw a line between the current point, and the previous point.

function drawPolygon(points) {
    //check arguments for null values
    if(!points)
        return false;

    var i;
    for(i = 0; i < points.length; i++)
        drawLine(points[i-1], points[i]);

    //draw the final line
    drawLine(points[i-1], points[0]);
}

drawLine looks like this:

function drawLine(point1, point2) {
    //check arguments for null values
    if(!point1 || !point2)
        return false;

    context.beginPath();
    context.moveTo(point1.x, point1.y);
    context.lineTo(point2.x, point2.y);
    context.stroke();
}

Unfortunately, depending on what order the users click, I can have the lines intersect, which I don't want: https://i.stack.imgur.com/NefX5.png How would I solve for this? My first instinct tells me to order the points top-to-bottom, left-to-right in the array then draw.

Kyle
  • 5,407
  • 6
  • 32
  • 47
  • just to clarify, so the lines are not really drawn on basis of user click sequence, right? – Dinesh Apr 13 '15 at 17:02
  • Correct. Must form a single polygon only, **not** based on click sequence. – Kyle Apr 13 '15 at 17:04
  • Let's suppose that you have (0,0), (0,1), (1,1), (1,0). If you click on those points in this order, you will see a square. But if you click on them in the following order (0,0), (1,1), (1,0), (0,1), which is the expected result in this case? The same square? Or (1,0) will be skipped because of intersection and the final result will be a triangle (0,0), (1,1), (0,1) ? And can you post a fiddle? Probably it's easier trying to experiment... – ROMANIA_engineer Apr 13 '15 at 17:16
  • It should be the same square. No points are "deleted" when `drawPolygon` is called. – Kyle Apr 13 '15 at 17:18
  • Is it a convex polygon? Having x distinct points (x>=3), you can have many concave polygons using a given set of points... (imagine that you have a hexagon and you "remove" a slice -> there are 6 slices that can be "removed". Edit: Oh, I see. If it is concave, the order will be the "clicking" order. – ROMANIA_engineer Apr 13 '15 at 17:31
  • 1
    @helpYou https://jsfiddle.net/8jpk4gr2/ – Kyle Apr 13 '15 at 17:33

2 Answers2

12

Step 1: Find center of polygon using average position of points

This function will find the center given all the points in the drawing, independent of order:

function findCenter(points) {

  var x = 0, y = 0, i, len = points.length;

  for (i = 0; i < len; i++) {
    x += points[i].x;
    y += points[i].y;
  }
  return {x: x / len, y: y / len};   // return average position
}

Demo showing center point in polygon

/**
 * Created by knguyen on 4/13/2015.
 */
var pointsArray = [];
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");

function Point(x, y) {
    this.x = x;
    this.y = y;
}

function drawDot(e) {
    var position = getMousePosition(canvas, e);
    posx = position.x;
    posy = position.y;

    storeCoordinate(posx, posy);

    context.fillStyle = "#F00";
    context.fillRect(posx, posy, 6, 6);
}

function getMousePosition(c, e) {
    var rect = canvas.getBoundingClientRect();
    return {x: e.clientX - rect.left, y: e.clientY - rect.top}
}
function storeCoordinate(xVal, yVal) {pointsArray.push(new Point(xVal, yVal))}

$("#solve").click(
    function() {
      var p = findCenter(pointsArray);
      context.fillStyle = "green";
      context.fillRect(p.x, p.y, 4, 4);
    }
);

function findCenter(points) {

  var x = 0, y = 0, i, len = points.length;

  for (i = 0; i < len; i++) {
    x += points[i].x;
    y += points[i].y;
  }
  return {x: x / len, y: y / len};   // return average position
}
#myCanvas {border: 1px solid #000}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="myCanvas" width="400" height="300" onclick="drawDot(event)"></canvas>
<div>
  <button type="button" class="btn btn-default" id="solve">Show center point</button>
</div>

Step 2: Sort points based on angle

  • Extend the point object to also take an angle argument.
  • Iterate trough the point array
  • Calculate the angle relative to the center point
  • Sort array based on angle

To find the angles just calculate the angle relative to the center point.

Here is how:

function findAngles(c, points) {

  var i, len = points.length, p, dx, dy;

  for (i = 0; i < len; i++) {
    p = points[i];
    dx = p.x - c.x;
    dy = p.y - c.y;
    p.angle = Math.atan2(dy, dx);
  }
}

Then you have to sort the points based on angle using a custom sort function. Just use the standard sort() method on the array and supply your own function which will use the angle property of the point object:

pointsArray.sort(function(a, b) {
  if (a.angle > b.angle) return 1;
  else if (a.angle < b.angle) return -1;
  return 0;
});

Then draw a line between all the points.

Working Demo

var pointsArray = [];
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");

function Point(x, y) {
    this.x = x;
    this.y = y;
    this.angle = 0;
}

canvas.onclick = drawDot;
function drawDot(e) {
    var position = getMousePosition(canvas, e);
    posx = position.x;
    posy = position.y;
    storeCoordinate(posx, posy);
    context.fillStyle = "#F00";
    context.fillRect(posx-3, posy-3, 6, 6);
}

function getMousePosition(c, e) {
    var rect = canvas.getBoundingClientRect();
    return {x: e.clientX - rect.left, y: e.clientY - rect.top}
}
function storeCoordinate(xVal, yVal) {pointsArray.push(new Point(xVal, yVal))}

$("#solve").click(
    function() {

      // find center
      var cent = findCenter(pointsArray);
      context.fillStyle = "green";
      context.fillRect(cent.x-3, cent.y-3, 6, 6);
      
      // find angles
      findAngles(cent, pointsArray);
      
      // sort based on angle using custom sort
      pointsArray.sort(function(a, b) {
        return (a.angle >= b.angle) ? 1 : -1
      });
            
      // draw lines
      context.beginPath();
      context.moveTo(pointsArray[0].x, pointsArray[0].y);
      for(var i = 0; i < pointsArray.length; i++) {
        context.lineTo(pointsArray[i].x, pointsArray[i].y);
      }
      context.strokeStyle = "#00f";
      context.closePath();
      context.stroke();
    }
);

function findCenter(points) {
  var x = 0, y = 0, i, len = points.length;
  for (i = 0; i < len; i++) {
    x += points[i].x;
    y += points[i].y;
  }
  return {x: x / len, y: y / len};   // return average position
}

function findAngles(c, points) {
  var i, len = points.length, p, dx, dy;
  for (i = 0; i < len; i++) {
    p = points[i];
    dx = p.x - c.x;
    dy = p.y - c.y;
    p.angle = Math.atan2(dy, dx);
  }
}

$("#reset").click(
  function() {
      context.clearRect(0, 0, canvas.width, canvas.height); //clear the canvas
      pointsArray = []; //clear the array
  }
);
#myCanvas {border: 1px solid #000}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="myCanvas" width="400" height="300"></canvas>
<div><button id="solve">Draw Polygon</button><button id="reset">Reset</button></div>
  • @SimonPlus what happens/how do you plot the points? Some tests like [this](https://i.imgur.com/zdfjYI2.png) and [this](https://i.imgur.com/6Nh3Vl8.png) seem to work ok? (the points where drawn in a random way). –  Apr 13 '15 at 19:45
  • @SimonPlus yeah, there are some cases the centroid algorithm cannot center properly. I do mention this in the answer, it's not included in the demo, but with code presented I think you will be able to do the extra pass. I can add an example using only average and add that as a demo at the end... give me a couple of mins. –  Apr 13 '15 at 19:53
  • @SimonPlus ok, I cleaned up the answer to focus on this solution (for future readers, the centroid approach is available in the edit history if anyone should be curious). –  Apr 13 '15 at 20:04
0

Polygons are said to be defined clockwise or counter-clockwise manner.

To sort your "random" clicks into clockwise order:

  1. Find the "center" of your polygon. This is the arithmetic mean of the x's and y's.

  2. Calculate all the angles from the centerpoint to each of your user's points. You can do this using Math.atan2(differenceInYs,differenceInXs);

  3. Sort the points in ascending order by their angles calculated in #2.

Your points now form a clockwise polygon.

markE
  • 102,905
  • 11
  • 164
  • 176