Published
Edited
Dec 3, 2018
Fork of Layout Grid
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
draw = ctx => {
ctx.clearRect(0, 0, width, height);

let columns = Math.floor(width / column_target);
let gutter = lh;
let cstep = width / columns;
let column_offset = lh / 2;

if (show_grid) {
ctx.fillStyle = "#ddd";
for (let c = 0; c < columns; c++) {
ctx.fillRect(c * cstep + column_offset, 0, cstep - gutter, height);
}
}

let rows = Math.floor(height / lh);
let row_offset = (height - rows * lh) / 2;

if (show_grid) {
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
for (let r = 0; r < rows; r++) {
let y = r * lh + row_offset;
ctx.strokeRect(1, y, width - 2, lh);
}
}

ctx.textBaseline = "middle";
ctx.font = "normal 16px Inter UI";
ctx.fillStyle = "#000";

let cells = [];
for (let c = 0; c < columns; c++) {
for (let r = 0; r < rows; r++) {
cells.push(null);
}
}

function getIndex(column, row) {
return column * rows + row;
}

function getColumnRow(index) {
let row = index % rows;
let column = Math.floor(index / rows);
return [column, row];
}

function findEmptyCell(index) {
function _checkCell(index) {
let cell = cells[index];
if (cell === null) {
return index;
} else if (index >= cells.length - 1) {
return null;
} else {
return _checkCell(index + 1);
}
}
return _checkCell(index);
}

// Do meta first
let meta_cell_counter = 0;
for (let e = 0; e < meta_ast.children.length; e++) {
let elem = meta_ast.children[e];
if (elem.type === "paragraph") {
ctx.font = `normal ${font_size}px Inter UI`;
for (let n = 0; n < elem.children.length; n++) {
let node = elem.children[n];
if (node.type === "text") {
let lines = wrapText(ctx, node.value, lh, width - gutter, true);
for (let l = 0; l < lines.length; l++) {
meta_cell_counter = findEmptyCell(meta_cell_counter);
if (meta_cell_counter !== null) {
let row = meta_cell_counter % rows;
let x = column_offset;
if (l === 0) {
x = x + lh;
}
let y = row * lh + lh / 2 + row_offset;
ctx.fillText(lines[l][0], x, y);
for (let c = 0; c < columns; c++) {
let index = getIndex(c, row);
cells[index] = lines[l][0];
}
meta_cell_counter++;
}
}
}
}
} else if (elem.type === "heading") {
for (let n = 0; n < elem.children.length; n++) {
let temp_lh = lh;
let row_size = 1;
if (elem.depth === 1) {
ctx.font = `normal ${font_size * 2}px Inter UI`;
temp_lh = lh * 2;
row_size = 2;
} else {
ctx.font = `normal ${font_size}px Inter UI`;
}
let node = elem.children[n];
if (node.type === "text") {
let lines = wrapText(ctx, node.value, temp_lh, width - gutter);
for (let l = 0; l < lines.length; l++) {
meta_cell_counter = findEmptyCell(meta_cell_counter);
if (meta_cell_counter !== null) {
let row = meta_cell_counter % rows;
let x = column_offset;
let y = row * temp_lh + temp_lh / 2 + row_offset;
if (l === 0) {
ctx.beginPath();
ctx.moveTo(x, y - temp_lh / 2);
ctx.lineTo(width - column_offset, y - temp_lh / 2);
ctx.stroke();
}
ctx.fillText(lines[l][0], x, y);
for (let c = 0; c < columns; c++) {
for (let r = 0; r < row_size; r++) {
let index = getIndex(c, r + row);
cells[index] = lines[l][0];
}
}
for (let r = 0; r < row_size; r++) {
meta_cell_counter++;
}
}
}
}
}
}
}

let cell_counter = 0;
for (let e = 0; e < ast.children.length; e++) {
let elem = ast.children[e];
if (elem.type === "paragraph") {
ctx.font = `normal ${font_size}px Inter UI`;
for (let n = 0; n < elem.children.length; n++) {
let node = elem.children[n];
if (node.type === "text") {
let lines = wrapText(ctx, node.value, lh, cstep - gutter, true);
for (let l = 0; l < lines.length; l++) {
cell_counter = findEmptyCell(cell_counter);
if (cell_counter !== null) {
let column = Math.floor(cell_counter / rows);
let x = column * cstep + column_offset;
if (l === 0) {
x = x + lh;
}
let row = cell_counter % rows;
let y = row * lh + lh / 2 + row_offset;
ctx.fillText(lines[l][0], x, y);
cells[cell_counter] = lines[l][0];
cell_counter++;
}
}
}
}
} else if (elem.type === "heading") {
for (let n = 0; n < elem.children.length; n++) {
ctx.font = `normal ${font_size}px Inter UI`;
let node = elem.children[n];
if (node.type === "text") {
let lines = wrapText(ctx, node.value, lh, cstep - gutter);
for (let l = 0; l < lines.length; l++) {
cell_counter = findEmptyCell(cell_counter);
if (cell_counter !== null) {
let column = Math.floor(cell_counter / rows);
let x = column * cstep + column_offset;
let row = cell_counter % rows;
let y = row * lh + lh / 2 + row_offset;
if (l === 0) {
ctx.beginPath();
ctx.moveTo(x, y - lh / 2);
ctx.lineTo(x + cstep - gutter, y - lh / 2);
ctx.stroke();
}
ctx.fillText(lines[l][0], x, y);
cells[cell_counter] = lines[l][0];
cell_counter++;
}
}
}
}
}
}
console.log(cells);
}
Insert cell
function wrapText(ctx, text, lh, width, is_indented) {
let words = text.split(" ");
let lines = [];
let maxWidth = width;
let indent_width = width - lh;
let line = "";
let x = 0;
let y = 0;
for (let n = 0; n < words.length; n++) {
let testLine = n === 0 ? line + words[n] : line + " " + words[n];
let metrics = ctx.measureText(testLine);
let testWidth = metrics.width;
let active_width = maxWidth;
if (is_indented && lines.length === 0) active_width = indent_width;
if (testWidth > active_width && n > 0) {
lines.push([line, x, y]);
line = words[n];
y += lh;
} else {
line = testLine;
}
}
lines.push([line, x, y]);
return lines;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more