Published
Edited
Aug 28, 2021
2 forks
Importers
13 stars
Insert cell
Insert cell
Insert cell
rows = Array.from({length:10}).map((d,i)=>`row ${i}`)
Insert cell
cols = Array.from({length:10}).map((d,i)=>`col ${i}`)
Insert cell
values = rows.map(row=>cols.map(col=>Math.random().toFixed(2)))
Insert cell
Insert cell
scaleColor = (d, row, colum) => d3.interpolateBlues(d3.scaleLinear()(d))
Insert cell
viewof selected = clickableTable(rows, cols, values, width, 300, scaleColor)
Insert cell
Insert cell
selected
Insert cell
Insert cell
Insert cell
clickableTable(rows, cols, values)
Insert cell
Insert cell
clickableTable(rows, cols, values, width, 300, (d, row, col) =>
row % 2 === 0 ? "#eee" : "white"
)
Insert cell
clickableTable(rows, cols, values, width, 300, (d, row, col) =>
row ===col ? "#eee" : "white"
)
Insert cell
Insert cell
clickableTable(rows, cols, values, width, 300, (d, row, col) =>
d > 0.5 ? "#8BC34A" : "white"
)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function clickableTable(
rows,
cols,
values,
w = width,
h = 300,
scaleColor = (d) => "white",
drawText = "auto" // true, false, "auto"
) {
// Initial value, can be interpreted as a selection of all or none of the cells, as you like
let selectedCell = { row: -1, col: -1 };

// Small margin, makes highlight boxes look better
const W = w - 2;
const H = h - 2;
const cellHeight = H / (rows.length + 1);
const cellWidth = W / (cols.length + 1);

if (drawText === "auto") {
drawText = Math.min(cellWidth, cellHeight) > 25;
}

const context = DOM.context2d(w, h);
const canvas = context.canvas;

// Dispatch event for the initial value
canvas.value = selectedCell;
canvas.dispatchEvent(new CustomEvent("input"));

function drawTable() {
// White background, better when saving as file or with dark themes
context.fillStyle = "white";
context.fillRect(0, 0, w, h);

context.fillStyle = "black";
context.strokeStyle = "black";
context.textAlign = "center";
context.textBaseline = "middle";

// Labels
if (drawText === true) {
for (const [r, row] of rows.entries()) {
const x = 0;
const y = (r + 1) * cellHeight;
context.fillText(row, x + cellWidth / 2, y + cellHeight / 2);
}
for (const [c, col] of cols.entries()) {
const x = (c + 1) * cellWidth;
const y = 0;
context.fillText(col, x + cellWidth / 2, y + cellHeight / 2);
}
}

// Cells
for (const [r, row] of rows.entries()) {
for (const [c, col] of cols.entries()) {
const x = (c + 1) * cellWidth;
const y = (r + 1) * cellHeight;
const fillColor = scaleColor(values[r][c], r, c);
context.fillStyle = fillColor;
context.fillRect(x, y, cellWidth, cellHeight);
if (drawText === true) {
// Make text white or black, depending on fill color's lightness
if (getColorLightness(fillColor) < 50) {
context.fillStyle = "white";
} else {
context.fillStyle = "black";
}
context.fillText(values[r][c], x + cellWidth / 2, y + cellHeight / 2);
}
}
}
}
drawTable();

// Onclick handler
canvas.onmouseup = (event) => {
event.preventDefault();
const row = Math.floor(event.offsetY / cellHeight) - 1;
const col = Math.floor(event.offsetX / cellWidth) - 1;
selectedCell = { row, col };
// Highlight selected cell
drawTable();
const x = (col + 1) * cellWidth;
const y = (row + 1) * cellHeight;
context.strokeRect(x, y, cellWidth, cellHeight);
// Dispatch event
canvas.value = selectedCell;
canvas.dispatchEvent(new CustomEvent("input"));
};

return canvas;
}
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