2

I have an array of objects that represent the position of div elements on a page. The array is unsorted and I need to sort them so they are arranged in order from left to right and then top to bottom.

The array "items" is:

[{
    "id": "Box 2",
    "x": 354,
    "y": 6
},
{
    "id": "Box 3",
    "x": 15,
    "y": 147
},
{
    "id": "Box 1",
    "x": 12,
    "y": 12
},
{
    "id": "Box 4",
    "x": 315,
    "y": 146
}]

I've tried sorting by both x:

items.sort(function(a, b){
if (a.x == b.x) return a.y - b.y;
    return a.x - b.x || a.y - b.y;
});

and/or sorting by y:

items.sort(function(a, b){
    if (a.y == b.y) return a.x - b.x;
    return a.y - b.y;
});

The items are sorted by x or y respectively, but I want them to be arranged so that the array is sorted box1, box2, box3, box4:

Sorted by x,y

RomanPerekhrest
  • 73,078
  • 4
  • 37
  • 76
B Adams
  • 77
  • 1
  • 5
  • Shouldn't you also account for the width and height of your boxes? – Christophe Marois Mar 14 '16 at 19:10
  • Possible duplicate of [Javascript Sort key value pair object based on value](http://stackoverflow.com/questions/14208651/javascript-sort-key-value-pair-object-based-on-value) – Claudio King Mar 14 '16 at 19:14
  • The boxes are created in the UI by dropping them on a page, so they will be positioned by the user. If they overlap, that it what the user intended. Each div (i.e. box) is using position absolute, so the internal won't affect the layout, however I need to sort them from left-to-right and then top-bottom so I end up with an array order that looks like the image. – B Adams Mar 14 '16 at 19:16
  • if you want first left to right, why box2 is before box3? the order will be: box1, box3, box4, box2 – Ziv Weissman Mar 14 '16 at 19:19
  • It's impossible, because the array is containing the points, not boxes. You can sort by points then result will be box2, box1, box3, box4 – isvforall Mar 14 '16 at 20:33

2 Answers2

5

I think I understand now what you are trying to achieve,

I am not sure it is possible with .sort option, I might be wrong.

This is working code that will do as you wanted, based on double index compare and flag to mark already added boxes.

var arranged = [];
var items = [{
  "id": "Box 2",
  "x": 354,
  "y": 6
}, {
  "id": "Box 3",
  "x": 15,
  "y": 147
}, {
  "id": "Box 1",
  "x": 12,
  "y": 12
}, {
  "id": "Box 4",
  "x": 315,
  "y": 146
}]

items.sort(function(a, b) {
  //sort by x, secondary by y
  return a.x == b.x ? a.y - b.y : a.x - b.x;
});
console.log(items);


for (var i = 0; i < items.length; i++) {

  //check if was already added
  if (typeof(items[i].wasAdded) == "undefined") {
    arranged.push(items[i]);
    items[i].wasAdded = "true";

    for (j = i + 1; j < items.length; j++) {
      if (items[i].y > items[j].y && typeof(items[j].wasAdded) == "undefined") {
        arranged.push(items[j]);
        items[j].wasAdded = "true";
      }
    }
  }
}
console.log(arranged);

fiddle example

Ziv Weissman
  • 4,045
  • 3
  • 24
  • 54
1

Arrived here while trying to solve a very similar problem. Here's my conclusion:

Unless your items are in a grid, you might not actually want to sort them in the way you described, top to bottom and then left to right. Instead, you probably want to sort them according to their distance from the top-left corner.

boxes.sort((a, b) => Math.hypot(a.x, a.y) - Math.hypot(b.x, b.y))

This is especially true if you're sorting boxes that overlap and you're trying to work out a consistent pattern for the z-index.

boxes
  .sort((a, b) => Math.hypot(a.x, a.y) - Math.hypot(b.x, b.y))
  .forEach((box, i) => box.z = i)
Steve Ruiz
  • 21
  • 1
  • 1