GreenTriangle
GreenTriangle

Reputation: 2460

How can I address cells on a canvas grid properly?

I want to use the Canvas API to create a square grid where each cell has a 1px border on its bottom and a 1px border on its right. The entire grid would then have a 1px border drawn around it, so it'd look like this:

Grid example

Once the grid exists, I want to write a function that will highlight a given cell. So for example calling highlightCell(1, 3, 'green') would result in this:

Highlight example

I thought this would be simple enough, but I'm having a devil of a time working it out. My problem is that when I account for having to straddle pixel lines -- drawing from eg 3.5 to 7.5, rather than 3 to 7, to not get blurry lines -- the math to figure out coordinates doesn't seem to work the way I expect at the edges, and I get results like this, where the highlight isn't placed correctly.

Bad canvas

My math is:

I'd appreciate someone explaining what I've done wrong because I'm sure it's something dumb but I just can't see it.

Here's the JS Fiddle of my attempt.

Upvotes: 1

Views: 5977

Answers (1)

Olian04
Olian04

Reputation: 6872

I would do it by separating the logic and the drawing. For example by having a state object and a drawFrame function; like this:

// setup state
const state = {
  __arr: [],
  __width: 20,
  __height: 20,
  __cell: {
    width: 20,
    height: 20,
  },
  __onUpdate: () => {},
  __calcIndex(x, y) {
    const index = x + y * this.__width;
    if (index >= this.__arr.length || index < 0) {
      throw new Error('Invalid index!');
    }
    return index;
  },
  init(onUpdate) {
    this.__arr = Array(this.__width * this.__height).fill(0);
    this.__onUpdate = onUpdate;
  },
  get(x, y) {
    const index = this.__calcIndex(x, y);
    return this.__arr[index];
  },
  set(x, y, value) {
    const index = this.__calcIndex(x, y);
    this.__arr[index] = value;
    this.__onUpdate();
  },
};

// setup drawing logic
const canvas = document.createElement('canvas');
document.body.append(canvas);
const ctx = canvas.getContext('2d');

const drawFrame = () => {
  const cell = state.__cell;
  ctx.lineWidth = 1;
  ctx.strokeStyle = 'black';
  ctx.fillStyle = 'orangered';
  for (let x = 0; x < state.__width; x++) {
    for (let y = 0; y < state.__width; y++) {
      ctx.strokeRect(cell.width * x, cell.height * y, cell.width, cell.height);
      if (state.get(x, y) !== 0) {
        ctx.fillRect(1 + cell.width * x, 1 + cell.height * y, cell.width-1, cell.height-1);
      }
    }
  }
}

state.init(drawFrame);
canvas.width = state.__width * state.__cell.width;
canvas.height = state.__height * state.__cell.height;
drawFrame();

state.set(2, 4, 1);
state.set(3, 5, 1);
state.set(2, 6, 1);
state.set(7, 4, 1);

Upvotes: 1

Related Questions