gridset

Create virtual grids and manipulate them. :)

Usage no npm install needed!

<script type="module">
  import gridset from 'https://cdn.skypack.dev/gridset';
</script>

README

👷🏼‍♂️ this is a work in progress

gridset

An imaginary grid to make positioning and moving things easier.

Table of contents

Install.

npm i gridset

Setup

import { Gridset } from 'gridset';

const grid = new Gridset({
  width: 200, // <-- width of the grid
  height: 200, // <-- height of the grid
  rows: 5, // <-- number of rows
  cols: 5, // <-- number of columns
});

You now have a Gridset. It looks like this:

┌─────┬─────┬─────┬─────┬─────┐
│ 0,0 │ 1,0 │ 2,0 │ 3,0 │ 4,0 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,1 │ 1,1 │ 2,1 │ 3,1 │ 4,1 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,2 │ 1,2 │ 2,2 │ 3,2 │ 4,2 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,3 │ 1,3 │ 2,3 │ 3,3 │ 4,3 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,4 │ 1,4 │ 2,4 │ 3,4 │ 4,4 │
└─────┴─────┴─────┴─────┴─────┘
Fun fact: this example was made with Gridset.

But Gridset does not actually render a grid. That part is up to you if you want to and it's easy.

Get all cells

There a few ways to get all of the cells of the gridset.

  • grid.cells
    • a 2D array arranged by column where each column is an array of its row cells
  • grid.cols
    • same as grid.cells
  • grid.rows
    • 2D array arranged by row where each row is an array of its column cells
  • grid.flatCells
    • a flat array arranged by column

Get a cell

The imaginary cells generated by Gridset can can be located by column and row indexes. So the first cell in our grid above is identified by .cell(0,0) - that's the cell at column 0, row 0.

When you retrieve a cell it looks like this:

{
  x:  x coordinate of the cell,
  y:  y coordinate of the cell,
  w:  width of the cell,
  h:  height of the cell,
  t:  top coordinate of the cell,
  l:  left coordinate of the cell,
  r:  right coordinate of the cell
  b:  bottom coordinate of the cell,
  cx: center x coordinate of the cell,
  cy: center y coordinate of the cell,
  ri: row index of the cell,
  ci: column index of the cell,
  _u:  () =>  one cell up,
  _lu: () =>  one cell left and up,
  _ru: () =>  one cell right and up,
  _d:  () =>  one cell down,
  _ld: () =>  one cell left and down,
  _rd: () =>  one cell right and down,
  _r:  () =>  one cell right,
  _l:  () =>  one cell left,
}

It is important to note that none of these are measured in pixels or inches or any other arbitrary unit. These are calculated values based on the parameters you used to initialize Gridset. Namely width, height, cols, and rows.

Get a column

You can select an entire column of cells from your Gridset by calling .col(columnIndex).

When you retrieve a column it looks like this:

{
  x:  x coordinate of the column,
  y:  y coordinate of the column,
  w:  width of the column,
  h:  height of the column,
  t:  top coordinate of the column,
  l:  left coordinate of the column,
  r:  right coordinate of the column
  b:  bottom coordinate of the column,
  cx: center x coordinate of the column,
  cy: center y coordinate of the column,
  ci: index of the column,
  cells: an array of the cells in the column
}

Get a row

You can select an entire row of cells from your Gridset by calling .row(rowIndex).

When you retrieve a row it looks like this:

{
  x:  x coordinate of the row,
  y:  y coordinate of the row,
  w:  width of the row,
  h:  height of the row,
  t:  top coordinate of the row,
  l:  left coordinate of the row,
  r:  right coordinate of the row
  b:  bottom coordinate of the row,
  cx: center x coordinate of the row,
  cy: center y coordinate of the row,
  ci: index of the row,
  cells: an array of the cells in the row
}

Get a diagonal

You can select a diagonal selection of cells from your Gridset by calling .diagonal(columnIndex, rowIndex).

If our grid looked like this

┌─────┬─────┬─────┬─────┬─────┐
│ 0,0 │ 1,0 │ 2,0 │ 3,0 │ 4,0 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,1 │ 1,1 │ 2,1 │ 3,1 │ 4,1 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,2 │ 1,2 │ 2,2 │ 3,2 │ 4,2 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,3 │ 1,3 │ 2,3 │ 3,3 │ 4,3 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,4 │ 1,4 │ 2,4 │ 3,4 │ 4,4 │
└─────┴─────┴─────┴─────┴─────┘

And we called .diagonal(1, 2) our result would be a flat array containing

 [
   .cell(0,0),
   .cell(1,2),
   .cell(2,3),
   .cell(3,4),
  ]

Illustrated here:

┌─────┬─────┬─────┬─────┬─────┐
│     │     │     │     │     │
├─────┼─────┼─────┼─────┼─────┤
│ 0,1 │     │     │     │     │
├─────┼─────┼─────┼─────┼─────┤
│     │ 1,2 │     │     │     │
├─────┼─────┼─────┼─────┼─────┤
│     │     │ 2,3 │     │     │
├─────┼─────┼─────┼─────┼─────┤
│     │     │     │ 3,4 │     │
└─────┴─────┴─────┴─────┴─────┘

Get an anti-diagonal

You can select a anti-diagonal selection of cells from your Gridset by calling .antidiagonal(columnIndex, rowIndex).

If our grid looked like this

┌─────┬─────┬─────┬─────┬─────┐
│ 0,0 │ 1,0 │ 2,0 │ 3,0 │ 4,0 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,1 │ 1,1 │ 2,1 │ 3,1 │ 4,1 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,2 │ 1,2 │ 2,2 │ 3,2 │ 4,2 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,3 │ 1,3 │ 2,3 │ 3,3 │ 4,3 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,4 │ 1,4 │ 2,4 │ 3,4 │ 4,4 │
└─────┴─────┴─────┴─────┴─────┘

And we called .antidiagonal(1, 2) our result would be a flat array containing

 [
   .cell(0,3),
   .cell(1,2),
   .cell(2,1),
   .cell(3,0),
  ]

Illustrated here:

┌─────┬─────┬─────┬─────┬─────┐
│     │     │     │ 3,0 │     │
├─────┼─────┼─────┼─────┼─────┤
│     │     │ 2,1 │     │     │
├─────┼─────┼─────┼─────┼─────┤
│     │ 1,2 │     │     │     │
├─────┼─────┼─────┼─────┼─────┤
│ 0,3 │     │     │     │     │
├─────┼─────┼─────┼─────┼─────┤
│     │     │     │     │     │
└─────┴─────┴─────┴─────┴─────┘

Get an area

An area is a group of cells defined by any two cells and composed of all the cells between them.

So an area made of cell 1,2 and cell 3,3 would look like this

┌─────┬─────┬─────┬─────┬─────┐
│ 0,0 │ 1,0 │ 2,0 │ 3,0 │ 4,0 │
├─────┼─────┼─────┼─────┼─────┤
│ 0,1 │ 1,1 │ 2,1 │ 3,1 │ 4,1 │
├─────┼─────┴─────┴─────┼─────┤
│ 0,2 │                 │ 4,2 │
├─────┤      area       ├─────┤
│ 0,3 │                 │ 4,3 │
├─────┼─────┬─────┬─────┼─────┤
│ 0,4 │ 1,4 │ 2,4 │ 3,4 │ 4,4 │
└─────┴─────┴─────┴─────┴─────┘
Fun fact: this example was made with Gridset.

You can select a group of cells from your Gridset by calling .area or .areaByCells. with cells that define two of the corners of the area you want to select in no particular order.

.areaByCells(cell1, cell2) takes two cell objects, but it's really only concerned about the ri and ci properties of each of them so if you don't already have the cells you can use .area({ci1: 1, ri1: 2, ci2: 3 ri2: 3}) and the result will be the same.

When you retrieve an area it looks like this:

{
  x:  x coordinate of the area,
  y:  y coordinate of the area,
  w:  width of the area,
  h:  height of the area,
  t:  top coordinate of the area,
  l:  left coordinate of the area,
  r:  right coordinate of the area
  b:  bottom coordinate of the area,
  cx: center x coordinate of the area,
  cy: center y coordinate of the area,
  cells: a 2d array of the cells in the area (sub-grid)
}

Set cell width and height.

Our grid is defined by width, height, number of columns and rows. Simple. But wait - there's more!

You can make the cells of the grid any size you want... even if they are bigger than the grid itself - which can get weird, but hey it's your grid.

Our 5 column grid looks like this

const grid = new Gridset({
  width: 200,
  height: 200,
  rows: 5,
  cols: 5,
});
 0        40       80      120      160      200
 └────────┴────────┴────────┴────────┴────────┘
 ┌────────┬────────┬────────┬────────┬────────┐
 │    0   │    1   │    2   │    3   │    4   │ <- Row 0
 └────────┴────────┴────────┴────────┴────────┘

Here the grid has been evenly divided into columns of 40 wide.

Let's add cellWidth.

const grid = new Gridset({
  width: 200,
  height: 200,
  rows: 5,
  cols: 5
  cellWidth: 60
});
 0           60      95      130     165      200
 └────────────┴───────┴───────┴───────┴───────┘
 ┌────────────¦───────¦───────¦───────¦───────┐
 │            ¦       ¦       ¦       ¦       │  <- Row 0
 └────────────¦───────¦───────¦───────¦───────┘
 │            ¦       ¦       ¦       ¦       ¦
 │    col0    ¦       │       ¦       ¦       ¦
 ├────────────┘       │       ¦       ¦       ¦
 ¦        │           │       ¦       ¦       ¦
 ¦        │   col1    │       ¦       ¦       ¦
 0        ├───────────┘       ¦       ¦       ¦
          ¦       │           ¦       ¦       ¦
          ¦       │   col2    ¦       ¦       ¦
         35       ├───────────┘       ¦       ¦
                  ¦       │           ¦       ¦
                  ¦       │    col3   ¦       ¦
                 70       ├───────────┘       ¦
                          ¦       │           ¦
                          ¦       │   col4    ¦
                         105      ├───────────┘
                                  ¦
                                  ¦
                                 140

Our grid still has the correct width while respecting the custom cellWidth. In order to achieve this our columns/cells now overlap. And it works the same way with setting cellHeight

const grid = new Gridset({
  width: 200,
  height: 200,
  rows: 5,
  cols: 5
  cellHeight: 60
});
0   ┐ ┌──────────────┐
    │ │              │
    │ │              │
    │ │   row0       │------┬---- 35
    │ │              │      │
60  ┤ │──────────────┘      │
    │ │   row1              │------┬---- 70
    │ │                     │      │
95  ┤ │─────────────────────┘      │
    │ │   row2                     │------┬---- 105
    │ │                            │      │
130 ┤ │────────────────────────────┘      │
    │ │   row3                            │------┬---- 140
    │ │                                   │      │
165 ┤ │───────────────────────────────────┘      │
    │ │   row4                                   │
    │ │                                          │
200 ┘ └──────────────────────────────────────────┘

Traversals

Look

Each individual cell you retrieve comes with methods to retrieve its adjacent cells.

{
  ...,
  _u:  () =>  one cell up,
  _lu: () =>  one cell left and up,
  _ru: () =>  one cell right and up,
  _d:  () =>  one cell down,
  _ld: () =>  one cell left and down,
  _rd: () =>  one cell right and down,
  _r:  () =>  one cell right,
  _l:  () =>  one cell left,
}

So if we started with .cell(1,2) .cell(1,2)._u() will return cell(1,1)

┌────────┬────────┬────────┬────────┐
│        │        │        │        │
│   0,0  │   1,0  │   2,0  │  3,0   │
│        │        │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,1  │   1,1  │   2,1  │  3,1   │
│        │    u   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,2  │   1,2  │   2,2  │  3,2   │
│        │    *   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,3  │   1,3  │   2,3  │  3,3   │
│        │        │        │        │
└────────┴────────┴────────┴────────┘

These methods can be chained together: cell(1,2)._u()._u()._r()

┌────────┬────────┬────────┬────────┐
│        │        │        │        │
│   0,0  │   1,0  │   2,0  <----------- land here.
│        │    u   │    r   │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,1  │   1,1  │   2,1  │  3,1   │
│        │    u   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,2  │   1,2  │   2,2  │  3,2   │
│        │    *   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,3  │   1,3  │   2,3  │  3,3   │
│        │        │        │        │
└────────┴────────┴────────┴────────┘

However if you run into the edge of the grid there will be no cell to retrieve in certain directions. For example, using the last example:

cell(1,2)._u()._u()._r()

If we added ._up() we would be outside the grid. By default you will be returned the same cell that you attempted to look from. In this case you will be returned cell(2,0)

                  +--------+
                  |        |
                  |   u    | <-- out of grid.
                  |        |
┌────────┬────────┬────────┬────────┐
│        │        │        │        │
│   0,0  │   1,0  │   2,0 <----------- so you get this cell instead.
│        │    u   │    r   │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,1  │   1,1  │   2,1  │  3,1   │
│        │    u   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,2  │   1,2  │   2,2  │  3,2   │
│        │    *   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,3  │   1,3  │   2,3  │  3,3   │
│        │        │        │        │
└────────┴────────┴────────┴────────┘

Gridset can provide an alternative result which can be helpful in some cases.

Look in cycle mode

Using look with a mode of cycle will use the current position and direction to return the cell as though you came through the other side of the grid whenever you went off the grid

const mode = 'cycle';
cell(1, 2)._u()._u()._r()._u(mode);
// OR because it can be difficult to know
// which "look" will go off the grid.
cell(1, 2)._u(mode)._u(mode)._r(mode)._u(mode);
                  +--------+
                  |        |
                  |   u3   | <-- out of grid.
                  |        |
┌────────┬────────┬────────┬────────┐
│        │        │        │        │
│   0,0  │   1,0  │   2,0  │  3,0   │
│        │    u2  │    r   │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,1  │   1,1  │   2,1  │  3,1   │
│        │    u   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,2  │   1,2  │   2,2  │  3,2   │
│        │    *   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,3  │   1,3  │   2,3 <----------- so you get this cell instead.
│        │        │        │        │
└────────┴────────┴────────┴────────┘

Cycle up out of grid

const mode = 'cycle';
cell(1, 2)._u(mode)._u(mode)._u(mode);
         +--------+
         |        |
         |    u   | <-- out of grid.
         |        |
┌────────┬────────┬────────┬────────┐
│        │        │        │        │
│   0,0  │   1,0  │   2,0  │  3,0   │
│        │    u   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,1  │   1,1  │   2,1  │  3,1   │
│        │    u   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,2  │   1,2  │   2,2  │  3,2   │
│        │    *   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,3  │   1,3 <---------------------- so you get this cell instead.
│        │        │        │        │
└────────┴────────┴────────┴────────┘

Cycle down out of grid

const mode = 'cycle';
cell(1, 2)._d(mode)._d(mode);
┌────────┬────────┬────────┬────────┐
│        │        │        │        │
│   0,0  │   1,0 <--------------------- so you get this cell instead.
│        │        │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,1  │   1,1  │   2,1  │  3,1   │
│        │        │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,2  │   1,2  │   2,2  │  3,2   │
│        │    *   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,3  │   1,3  │   2,3  │  3,3   │
│        │    d   │        │        │
└────────┴────────┴────────┴────────┘
         |        |
         |    d   | <-- out of grid.
         |        |
         +--------+

Cycle right out of grid

const mode = 'cycle';
cell(1, 2)._r(mode)._r(mode)._r(mode);
┌────────┬────────┬────────┬────────┐
│        │        │        │        │
│   0,0  │   1,0  │   2,0  │  3,0   │
│        │        │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,1  │   1,1  │   2,1  │  3,1   │
│        │        │        │        │
├────────┼────────┼────────┼────────┤---------+
│        │        │        │        │         |
│   0,2  │   1,2  │   2,2  │  3,2   │    r    | <-- out of grid.
│    ⇡   │    *   │    r   │   r    │         |
├────│───┼────────┼────────┼────────┤---------+
│    │   │        │        │        │
│    └----------------------------------- so you get this cell instead.
│        │        │        │        │
└────────┴────────┴────────┴────────┘

Cycle left out of grid

const mode = 'cycle';
cell(1, 2)._l(mode)._l(mode);
        ┌────────┬────────┬────────┬────────┐
        │        │        │        │        │
        │   0,0  │   1,0  │   2,0  │  3,0   │
        │        │        │        │        │
        ├────────┼────────┼────────┼────────┤
out of  │        │        │        │        │
grid    │   0,1  │   1,1  │   2,1  │  3,1   │
  |     │        │        │        │        │
+-------├────────┼────────┼────────┼────────┤
|       │        │        │        │        │
|   l   │   0,2  │   1,2  │   2,2  │  3,2 <----- so you get this cell instead.
|       │    l   │    *   │        │        │
+-------├────────┼────────┼────────┼────────┤
        │        │        │        │        │
        │   0,3  │   1,3  │   2,3  │  3,3   │
        │        │        │        │        │
        └────────┴────────┴────────┴────────┘

Cycle diagonally out of grid

const mode = 'cycle';
cell(1, 2)._rd(mode)._rd(mode);
┌────────┬────────┬────────┬────────┐
│        │        │        │        │
│   0,0  │   1,0  │   2,0  │  3,0   │
│        │        │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,1 <-------------------------------- so you get this cell instead.
│        │        │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,2  │   1,2  │   2,2  │  3,2   │
│        │    *   │        │        │
├────────┼────────┼────────┼────────┤
│        │        │        │        │
│   0,3  │   1,3  │   2,3  │  3,3   │
│        │        │    rd  │        │
└────────┴────────┴────────┴────────┘
                           |        |
                           |   rd   | <-- out of grid.
                           |        |
                           +--------+

Cycle Generators

The following cycle generators work similarly to cell cycling. When a cycle reaches the the last cell the next value will be on the opposite side of the cycled area.

Cycle Cells

Cycle any flat array of cells. cycle accepts the following arguments, all arguments are optional.

  • cells
    • this is a flat array of cells to cycle through
    • the default is grid.flatCells
  • dir
    • f (forwards) or r (reverse)
    • the default is f
  • startingIndex
    • where in the array to begin the cycle
    • the default is 0

Once your cycle generator is instantiated you can retrieve the next cell in the cycle by calling: [generator].next().value

const cycle = grid.cycle(arrayOfCells, [dir, startingIndex])
const firstCell = cycle.next().value
const secondCell = cycle.next().value
const thirdCell = cycle.next().value
// ... and so on forever.

Cycle Helpers

Cycle helper methods allow you to easily cycle identifiable areas of the grid like columns or rows.

const cycle = grid.cycle____(args)
const firstCell = cycle.next().value
const secondCell = cycle.next().value
const thirdCell = cycle.next().value
// ... and so on forever.

Each cycle helper method accepts an optional dir argument of f (forwards) or r (reverse). The default for dir is f. Each cycle method accepts an optional startingIndex argument representing where in the array of cells to begin the cycle. The default for startingIndex is 0

Cycle a row

const cycle = grid.cycleRow(rowIndex, [dir, startingIndex])
const firstCell = cycle.next().value

Cycle a column

const cycle = grid.cycleCol(columnIndex, [dir, startingIndex])
const firstCell = cycle.next().value

Cycle a diagonal or anti-diagonal

const cycle = grid.cycleDiagonal(columnIndex, rowIndex, [dir, startingIndex])
const firstCell = cycle.next().value
const cycle = grid.cycleAntidiagonal(columnIndex, rowIndex, [dir, startingIndex])
const firstCell = cycle.next().value

Scan generators

The following scan generators cycle through cells and when reaching the last or first cell will begin cycling in the reverse direction. You can think of it as a linearly and infinite bounce.

Scan Cells

.scanCells allows you to scan any flat array of cells.

Scan Helpers

Scan helper methods allow you to easily scan identifiable areas of the grid like columns or rows.

const scan = grid.scan____(args)
const firstCell = scan.next().value
const secondCell = scan.next().value
const thirdCell = scan.next().value
// ... and so on forever.

Each scan helper method accepts an optional dir argument of f (forwards) or r (reverse). The default for dir is f. Each scan method accepts an optional startingIndex argument representing where in the array of cells to begin the scan. The default for startingIndex is 0

Scan a row

const scan = grid.scanRow(rowIndex, [dir, startingIndex])
const firstCell = scan.next().value

Scan a column

const scan = grid.scanCol(colIndex, [dir, startingIndex])
const firstCell = scan.next().value

Scan a diagonal or anti-diagonal

const scan = grid.scanDiagonal(colIndex, rowIndex, [dir, startingIndex])
const firstCell = scan.next().value
const scan = grid.scanAntidiagonal(colIndex, rowIndex, [dir, startingIndex])
const firstCell = scan.next().value

Bounce an area

const bounce = grid.bounce(
  area, // default = grid
  sx,    // default = 0,
  sy     // default = 0
 )
┌─────┬─────┬─────┬─────┬─────┐
│  0  │     │     │     │     │
├─────┼─────┼─────┼─────┼─────┤
│     │  1  │     │     │     │
├─────┼─────┼─────┼─────┼─────┤
│  8  │     │  2  │     │     │
├─────┼─────┼─────┼─────┼─────┤
│     │  7  │     │  3  │     │
├─────┼─────┼─────┼─────┼─────┤
│     │     │  6  │     │  4  │
├─────┼─────┼─────┼─────┼─────┤
│     │     │     │  5  │     │
└─────┴─────┴─────┴─────┴─────┘