Files
brk/website_next/heatmap/pack.js
T
2026-07-06 15:54:25 +02:00

90 lines
2.0 KiB
JavaScript

/**
* @param {readonly PackCell[]} cells
* @param {number} columns
* @param {number} [rows]
* @returns {PackLayout[] | null}
*/
export function packCells(cells, columns, rows) {
const occupied = /** @type {boolean[][]} */ ([]);
const layouts = [];
let usedRows = 0;
for (const cell of cells) {
const span = Math.min(cell.span, columns);
const position = findPosition(occupied, columns, span, rows);
if (position === null) return null;
fillCells(occupied, position.x, position.y, span);
usedRows = Math.max(usedRows, position.y + span);
layouts.push({ ...position, span, rows: rows ?? usedRows });
}
return layouts;
}
/**
* @param {boolean[][]} occupied
* @param {number} columns
* @param {number} span
* @param {number} [rows]
* @returns {{ x: number, y: number } | null}
*/
function findPosition(occupied, columns, span, rows) {
const lastRow = rows === undefined ? Infinity : rows - span;
for (let y = 0; y <= lastRow; y += 1) {
for (let x = 0; x <= columns - span; x += 1) {
if (canPlace(occupied, x, y, span)) return { x, y };
}
}
return null;
}
/**
* @param {boolean[][]} occupied
* @param {number} x
* @param {number} y
* @param {number} span
*/
function canPlace(occupied, x, y, span) {
for (let row = y; row < y + span; row += 1) {
for (let column = x; column < x + span; column += 1) {
if (occupied[row]?.[column]) return false;
}
}
return true;
}
/**
* @param {boolean[][]} occupied
* @param {number} x
* @param {number} y
* @param {number} span
*/
function fillCells(occupied, x, y, span) {
for (let row = y; row < y + span; row += 1) {
occupied[row] ??= [];
for (let column = x; column < x + span; column += 1) {
occupied[row][column] = true;
}
}
}
/**
* @typedef {Object} PackCell
* @property {number} span
*/
/**
* @typedef {Object} PackLayout
* @property {number} x
* @property {number} y
* @property {number} span
* @property {number} rows
*/