Published
Edited
Jan 24, 2020
15 forks
66 stars
Insert cell
Insert cell
Insert cell
graphic = {
replay;
const quads = new TinyQueue([new Quad(0, 0, width, width)], (a, b) => b.score - a.score);
const context = DOM.context2d(width, width);
context.canvas.style.width = "100%";
for (let i = 0; true; ++i) {
const q = quads.pop();
if (q === undefined || q.score < 50) break;
mutable score = q.score;
for (const s of q.split()) {
if (q.w >= 4) quads.push(s);
context.fillStyle = s.color;
context.fillRect(s.x, s.y, s.w, s.h);
context.strokeRect(s.x, s.y, s.w, s.h);
}
if (i % 15 === 0) yield context.canvas;
}
}
Insert cell
mutable score = null
Insert cell
width = 1024
Insert cell
area_power = 0.25
Insert cell
class Quad {
constructor(x, y, w, h) {
const [r, g, b, error] = colorFromHistogram(computeHistogram(x, y, w, h));
this.x = x, this.y = y, this.w = w, this.h = h;
this.color = `#${(0x1000000 + (r << 16) + (g << 8) + b).toString(16).substring(1)}`;
this.score = error * Math.pow(w * h, area_power);
}
split() {
const dx = this.w / 2, x1 = this.x, x2 = this.x + dx;
const dy = this.h / 2, y1 = this.y, y2 = this.y + dy;
return [
new Quad(x1, y1, dx, dy),
new Quad(x2, y1, dx, dy),
new Quad(x1, y2, dx, dy),
new Quad(x2, y2, dx, dy)
];
}
}
Insert cell
function computeHistogram(x, y, w, h) {
const {data} = imageContext.getImageData(x, y, w, h);
const histogram = new Uint32Array(1024);
for (let i = 0, n = data.length; i < n; i += 4) {
++histogram[0 * 256 + data[i + 0]];
++histogram[1 * 256 + data[i + 1]];
++histogram[2 * 256 + data[i + 2]];
++histogram[3 * 256 + data[i + 3]];
}
return histogram;
}
Insert cell
function weightedAverage(histogram) {
let total = 0;
let value = 0;
for (let i = 0; i < 256; ++i) total += histogram[i], value += histogram[i] * i;
value /= total;
let error = 0;
for (let i = 0; i < 256; ++i) error += (value - i) ** 2 * histogram[i];
return [value, Math.sqrt(error / total)];
}
Insert cell
function colorFromHistogram(histogram) {
const [r, re] = weightedAverage(histogram.subarray(0, 256));
const [g, ge] = weightedAverage(histogram.subarray(256, 512));
const [b, be] = weightedAverage(histogram.subarray(512, 768));
return [
Math.round(r),
Math.round(g),
Math.round(b),
re * 0.2989 + ge * 0.5870 + be * 0.1140
];
}
Insert cell
imageContext = FileAttachment("owl.jpg").image().then(image => {
const context = DOM.context2d(width, width, 1);
context.drawImage(image, 0, 0, width, width);
return context;
})
Insert cell
TinyQueue = require("tinyqueue@2")
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