{
const cols = 50;
const rows = 20;
const [cellWidth, cellHeight] = cm.measureText("M");
const width = cellWidth * cols;
const height = cellHeight * rows;
const fire = d3.range(cols * rows).map(() => 0);
const index = (x, y) => x + y * cols;
const app = cm.render({
width,
height,
loop: true,
styleBackground: "black",
frameRate: 15,
draw: () => {
const noise = cm.randomNoise(0, rows);
for (let y = 0; y < rows - 1; y++) {
for (let x = 0; x < cols; x++) {
const decay = d3.randomInt(0, 3)();
const spread = d3.randomInt(-1, 1)();
const i = Math.min(Math.max(0, x - spread), cols - 1);
const target = fire[index(i, y + 1)];
fire[index(x, y)] = Math.max(0, target - decay);
}
}
for (let x = 0; x < cols; x++) {
fire[index(x, rows - 1)] = noise(x / 10) | 0;
}
const color = d3.scaleSequential(d3.extent(fire), d3.interpolateViridis);
const fill = (d) => (d === 0 ? "transparent" : color(d));
const x = (_, i) => (i % cols) * cellWidth;
const y = (_, i) => ((i / cols) | 0) * cellHeight;
return [
cm.svg("rect", fire, {
x,
y,
fill,
stroke: fill,
width: cellWidth,
height: cellHeight
}),
cm.svg("text", fire, {
x,
y,
dy: "1.25em",
dx: "0.2em",
fontSize: 14,
fill: "white",
stroke: "white",
textContent: (d) => (d ? ch() : "")
})
];
}
});
invalidation.then(() => app.dispose());
return app.node();
}