Published
Edited
Sep 24, 2021
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
map = {
const context = DOM.context2d(width, height);

function render(projection, color) {
const path = d3.geoPath(projection, context);
context.fillStyle = context.strokeStyle = color;
context.save();
context.beginPath(), path(outline), context.clip();
context.beginPath(),
path(graticule),
(context.globalAlpha = 0.3),
context.stroke();
context.beginPath(),
path(land),
(context.globalAlpha = 1.0),
context.fill();
context.restore();
context.beginPath(), path(outline), context.stroke();
}

context.fillStyle = "#fff";
context.fillRect(0, 0, width, height);

context.save();
render(projection, "#000");
context.restore();

return context.canvas;
}
Insert cell
Insert cell
smallDiscreteMap = {
const context = DOM.context2d(brickWidth, brickHeight, 1);

function render(projection, color) {
const path = d3.geoPath(projection, context);

context.save();
context.beginPath(), path(outline), context.clip();
context.fillStyle = "#fff";
context.fillRect(0, 0, brickWidth, brickHeight);

context.fillStyle = context.strokeStyle = color;
context.beginPath(), path(land), context.fill();

context.restore();
}

render(brickProjection, "#000");

return context.canvas;
}
Insert cell
Insert cell
discreteMap = {
const context = DOM.context2d(brickWidth, brickHeight, 1);
context.drawImage(smallDiscreteMap, 0, 0);

context.canvas.style =
"width: 100%; image-rendering: pixelated; image-rendering: crisp-edges; background: #eee;";

return context.canvas;
}
Insert cell
Insert cell
binaryMap = {
const context = DOM.context2d(brickWidth, brickHeight, 1);
context.drawImage(smallDiscreteMap, 0, 0);

const imageData = context.getImageData(0, 0, brickWidth, brickHeight);
const { data } = imageData;

for (let i = 0; i < data.length / 4; i += 1) {
const alpha = data[4 * i + 3];
if (alpha < 255) {
data[4 * i + 3] = 0;
} else {
const value = data[4 * i] >= 192 ? 255 : 0;
for (let j = 0; j < 3; j += 1) {
data[4 * i + j] = value;
}
}
}

context.putImageData(imageData, 0, 0);

context.canvas.style =
"width: 100%; image-rendering: pixelated; image-rendering: crisp-edges; background: #eee;";

return context.canvas;
}
Insert cell
Insert cell
{
const gradient = DOM.uid("gradient");
return svg`
<svg width="50" height="50" viewBox="0, 0, 80, 80">
<defs>
<linearGradient id="${gradient.id}" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color: #fff; stop-opacity: 0.2;"/>
<stop offset="100%" style="stop-color: #000; stop-opacity: 0.2;"/>
</linearGradient>
</defs>
<rect x="1" y="1" width="78" height="78" fill="${colors.blue}"/>
<circle cx="39" cy="39" r="24" fill="#fff"/>
<circle cx="41" cy="41" r="24" fill="#000"/>
<circle cx="40" cy="40" r="24" fill="${colors.blue}"/>
<circle cx="40" cy="40" r="24" fill="${gradient}"/>
</svg>`;
}
Insert cell
Insert cell
{
const svg = renderBricks([
{ x: 0, y: 0, width: 1, height: 1, color: "blue" }
]);
return html`<div style="width: 200px;">${svg}</div>`;
}
Insert cell
Insert cell
{
const svg = renderBricks([
{ x: 0, y: 0, width: 4, height: 2, color: "blue" },
{ x: 2, y: 2, width: 4, height: 2, color: "green" }
]);
svg.style.width = "300px";
return svg;
}
Insert cell
Insert cell
brickSizes = [
{ width: 4, height: 2 },
{ width: 2, height: 4 },
{ width: 2, height: 2 },
{ width: 4, height: 1 },
{ width: 1, height: 4 },
{ width: 3, height: 1 },
{ width: 1, height: 3 },
{ width: 2, height: 1 },
{ width: 1, height: 2 },
{ width: 1, height: 1 }
]
Insert cell
Insert cell
bricks = {
const bricks = [];

const context = binaryMap.getContext("2d");
const imageData = context.getImageData(0, 0, brickWidth, brickHeight);
const getColor = (x, y) => {
const offset = 4 * (brickWidth * y + x);
const alpha = imageData.data[offset + 3];
if (alpha === 0) return;
return imageData.data[offset] === 0 ? "green" : "blue";
};

const blocked = Array.from({ length: brickWidth }, () =>
Array.from({ length: brickHeight }, () => false)
);

const brickMap = {};

brickSizes.forEach(({ width, height }) => {
for (let y0 = 0; y0 <= brickHeight - height; y0 += 1) {
for (let x0 = 0; x0 <= brickWidth - width; x0 += 1) {
const color = getColor(x0, y0);
if (!color) continue;

let sameColor = true;
let unblocked = true;
for (let y1 = 0; y1 < height; y1 += 1) {
for (let x1 = 0; x1 < width; x1 += 1) {
const x = x0 + x1;
const y = y0 + y1;
if (blocked[x][y]) {
unblocked = false;
}
if (getColor(x, y) !== color) {
sameColor = false;
}
}
}
let hasOffset = true;
if (improveAlignment && width > height) {
const neighbor = brickMap[`${x0}|${y0 - 2}`];
if (
neighbor &&
neighbor.width === width &&
neighbor.height === height
) {
hasOffset = false;
}
}
if (sameColor && unblocked && hasOffset) {
bricks.push({ x: x0, y: y0, width, height, color });
brickMap[`${x0}|${y0}`] = { width, height };
for (let y1 = 0; y1 < height; y1 += 1) {
for (let x1 = 0; x1 < width; x1 += 1) {
blocked[x0 + x1][y0 + y1] = true;
}
}
}
}
}
});

return bricks;
}
Insert cell
Insert cell
Insert cell
{
replay;
await visibility();

const bricksPerSecond = 30;
const gradient = DOM.uid("gradient");

const padding = paddingRatio * brickWidth * unitSize;
const width = brickWidth * unitSize + 2 * padding;
const height = brickHeight * unitSize + 2 * padding;
const container = svg`
<svg viewBox="${-padding}, ${-padding}, ${width}, ${height}"
style="width: 100%; height: 100%; display: block;">
<defs>
<linearGradient id="${gradient.id}" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color: #fff; stop-opacity: 0.2;"/>
<stop offset="100%" style="stop-color: #000; stop-opacity: 0.2;"/>
</linearGradient>
</defs>
<rect x="${-padding}" y="${-padding}"
width="${width}" height="${height}"
fill="#eee"/>
</svg>`;

yield container;

for (const { x, y, width, height, color } of bricks) {
const dotsSvg = Array.from({ length: width * height }).map((_, i) => {
const dotX = i % width;
const dotY = Math.floor(i / width);
const cx = (dotX + 0.5) * unitSize;
const cy = (dotY + 0.5) * unitSize;
return svg`
<circle cx="${cx - 1}" cy="${cy - 1}" r="${brickRadius}" fill="#fff"/>
<circle cx="${cx + 1}" cy="${cy + 1}" r="${brickRadius}" fill="#000"/>
<circle cx="${cx}" cy="${cy}" r="${brickRadius}" fill="${
colors[color]
}"/>
<circle cx="${cx}" cy="${cy}" r="${brickRadius}" fill="${gradient}"/>`;
});

await Promises.tick(1000 / bricksPerSecond);
container.append(svg`
<g transform="translate(${x * unitSize}, ${y * unitSize})">
<rect x="${brickBorder}" y="${brickBorder}"
width="${width * unitSize - 2 * brickBorder}"
height="${height * unitSize - 2 * brickBorder}"
fill="${colors[color]}"/>
${dotsSvg}
</g>`);
}
}
Insert cell
Insert cell
parts = {
const parts = {};

bricks.forEach(({ width, height, color }) => {
if (height > width) {
[width, height] = [height, width];
}
const key = `${width}x${height}|${color}`;
const part = parts[key] || { width, height, color, count: 0 };
part.count += 1;
parts[key] = part;
});

const sortedParts = Object.values(parts);
sortedParts.sort((a, b) => {
if (a.width !== b.width) {
return b.width - a.width;
}
if (a.height !== b.height) {
return b.height - a.height;
}
return a.color.localeCompare(b.color);
});

return sortedParts;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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