Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript: Automatically order a variable multi-dimensional array diagonally

This is more like a math related question.

I'm trying to create a cute fading effect with jQuery, by splitting a element in a certain number of blocks, then fading each of them, but delay the fading effect based on another array.

So to create the blocks table I have two variables:

var rows = 4,
    cols = 10;

This will divide the element into blocks like:

              0   1   2   3   4   5   6   7   8   9
             10  11  12  13  14  15  16  17  18  19
             20  21  22  23  24  25  26  27  28  29
             30  31  32  33  34  35  36  37  38  39

Then I'm creating another array which decides how the blocks will animate. For example, for a left-to-right diagonal animation this array would look like:

order = [0, 10, 1, 20, 11, 2, 30, 21, 12, 3, 31, 22, 13, 4, 32, 23, 14, 5, 33, 24, 15, 6, 34, 25, 16, 7, 35, 26, 17, 8, 36, 27, 18, 9, 37, 28, 19, 38, 29, 39];

and for this specific case it works :D

My question is how could I create the order array automatically, not manually, based on the number of blocks (rows x columns), which can change ?

Thank you

like image 207
Alexandra Avatar asked Apr 29 '11 14:04

Alexandra


3 Answers

This will do it:

var arr = [];

var rows = 4;
var cols = 10;

for(var i = 0; i < rows + cols - 1; i++){

    for(var j = Math.min(rows, i + 1) - 1; j >= Math.max(0, i - cols + 1); j--){
        arr.push((j * cols) + i - j);
    }  

}

fiddle: http://jsfiddle.net/BmXpy/

EDIT: Here's my attempt at explaining how I came up with this. IMPORTANT, use the table of numbers above to visualize and if needed, print it and draw out the diagonals.

First, think about what we want, it's basically diagonals. In the example above the first diagonal is 0, then 10, 1, then 20, 11, 2, then 30, 21, 12, 3, etc. Now if you think about how many of those diagonals there are, it is rows + cols - 1. That is where we get the first loop:

for(var i = 0; i < rows + cols - 1; i++){

Now, ignore for a second the boundries. In the general case (the whole center), each of these diagonals is "rows" long. And since we want to go bottom up, we want a reverse loop. That would look like this for the inner loop:

for(var j = rows - 1; j >= 0; j--){

Now, we must deal with both boundries (left and right).

For the left boundry, if we look at the number of diagonals which are less than "rows" long, we will see that it is rows - 1. And for these diagonals we'll see that the length of each is i + 1. The following inner loop will handle the general case and the left boundry:

for(var j = Math.min(rows, i + 1) - 1; j >= 0; j--){

You will see that for diagonal 0, this will run once, for 1 it will run twice, etc. And for the general case (i >= rows) it will run "rows" times.

Now, the right boundry. If we look at which diagonals on the right are shorter than "rows", we will see it is all diagonals greater than "cols" (in the example where cols is 10, 0 indexed, that is row 10 and beyond). Replacing j >= 0 with j >= Math.max(0, i - cols + 1) will run to 0 for the general case and the left boundry but shorten for the right boundry. We get this:

for(var j = Math.min(rows, i + 1) - 1; j >= Math.max(0, i - cols + 1); j--){

And finally, the actual calculation of the number in each location. i represents the diagonal and j represents the index of the number on the diagonal j = 0 is the top number if you're looking at the posted table of numbers. For j = 0 (top row of numbers) the number is simply i, for each row below the top, we need to multiply the number by "cols" in order to get the number directly below the first row number, then the number needs to be adjusted to the left. This is done by subtracting j which is the row number. So for j = 1 (the 2nd row) we need to move the number left by one (subtract 1). So we have i for the horizontal position on the first row, + (j * cols) to move it down to the appropriate row and then -j to adjust it to diagonal (if you have drawn the diagonals, trace this out for one of them to get a good visual). We get this:

(j * cols) + i - j

Put it all together and you get my final code above. Hope that made some sense.

like image 178
James Montagne Avatar answered Nov 08 '22 12:11

James Montagne


Yet another way to do it.

function diagonalized_block (rows, cols) {
    blocks = [];
    for (var i = 0; i < rows * cols; i++) {
        blocks.push(i);
    }
    blocks.sort(function (a, b) {return a/cols + a%cols - b/cols - b%cols});
    return blocks;
}

The idea is simple. Write the obvious array of all of the blocks, and then sort by a function of the block that is ascending in exactly the order you want. That function is x/cols + x%cols. Which is row + ascending error on diagonal + col. So you sort things first by which diagonal they are on, and then ascending on the diagonal.

For other patterns you'll have to find other functions that happen to sort correctly. (Or write a more complicated sort function that does exactly what you want it to do.)

like image 38
btilly Avatar answered Nov 08 '22 11:11

btilly


Here there is another code http://jsfiddle.net/paska/FhLnu/3/

var rows = 4,
    cols = 10,
    getValue = function(x, y) {
        return (y * cols) + x;
    },
    addValue = function(x, y) {
        $("#arr").append(getValue(x, y) + ", ");
    },
    max = rows + cols - 1,
    updateRow = 0;

for(var i = 0; i < max; i++) {
    if(i >= rows)
        updateRow++;
    for(var x = 0; x <= i; x++) {
        //$("#arr").append(x + " " + (i - x) + "<br />");
        var row = (i - x),
            col = x;
        if(updateRow) {
            row = row - updateRow;
            col = col + updateRow;
        }
        if(col >= cols)
            break;
        if(row >= 0 && col >= 0)
            addValue(col, row);
    }
}

I know @kingjiv code works, I just wanted to add another way of doing it.

like image 1
Diego Avatar answered Nov 08 '22 13:11

Diego