Published
Edited
Feb 21, 2022
3 forks
Importers
13 stars
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
function constrain(x, a, b) {
return x > b ? b : x < a ? a : x;
}
Insert cell
/**
* @param {Uint8Array} pixels
* @param {number} width
* @param {number} height
* @param {number} radius
*/
function quadBlurRGBA (pixels, width, height, radius) {
"use strict";
if (!(pixels && pixels.length && radius > 0 && width > 0 && height > 0)) return;
if (radius <= 1) {
return stackBlurRGBA(pixels, width, height, radius);
}
// ## Widths
// ▂▂▃▃▄▃▃▂▂
// ▃▆█████████████▆▃
// ▄███████████████████▄
// ▗███████████████████████▖
// ▗█████████████████████████▖
// ▗███████████████████████████▖
// ▃▅█████████████████████████████▅▃
// ▁▁▂▃▅▇███████████████████████████████████▇▅▃▂▁▁
//
// <────────radius───────>*<───────radius────────>
// <──────────────────blurWidth──────────────────>
// <──────stackWidth──────>
// <─accWidth─>
//
// blurWidth = radius * 2 + 1
// stackWidth = radius + 1
// accWidth = Math.floor((stackWidth+1) / 2)
//
// ## Accumulators and range of pixels they cover
//
// ### Linear Accumulators
//
// <──────────> stackLeftOut
//
// <──────────> stackLeftIn
//
// stackRightOut <──────────>
//
// stackRightIn <──────────>
//
// ### Stack accumulators
//
// <───────stackLeft──────>
//
// <──────stackRight──────>
//
// ### Quadratic stack accumulator
//
// <──────────────────quadStack──────────────────>
//
// Indices to keep track of and what they are for:
// 1 23 45 67 8
//
// 1) stackLeftOut leftmost pixel
// 2) stackLeftOut rightmost pixel
// 3) stackLeftIn leftmost pixel
// 4) stackRightOut leftmost pixel
// 5) stackLeftIn rightmost pixel,
// equals central pixel position.
// 6) stackRightOut rightmost pixel
// 7) stackRightIn leftmost pixel
// 8) stackRightIn rightmost pixel

const stackWidth = radius + 1;
const accWidth = (stackWidth + 1) / 2 | 0;
const stackLeftOut = [0, 0, 0, 0]
const stackLeftIn = [0, 0, 0, 0]
const stackRightOut = [0, 0, 0, 0]
const stackRightIn = [0, 0, 0, 0]
const stackLeft = [0, 0, 0, 0]
const stackRight = [0, 0, 0, 0]
const quadStack = [0, 0, 0, 0]
const quadBuffer = new Float64Array(pixels.length);
const quadWeight = accWidth * (stackWidth - accWidth) * stackWidth;

// blur rows
for (let y = 0; y < height; y++) {
const yw = y * width * 4;

// Initiate on leftmost pixel.
stackLeftOut[0] = stackLeftIn[0] = stackRightOut[0] = stackRightIn[0] = pixels[yw+0] * accWidth;
stackLeftOut[1] = stackLeftIn[1] = stackRightOut[1] = stackRightIn[1] = pixels[yw+1] * accWidth;
stackLeftOut[2] = stackLeftIn[2] = stackRightOut[2] = stackRightIn[2] = pixels[yw+2] * accWidth;
stackLeftOut[3] = stackLeftIn[3] = stackRightOut[3] = stackRightIn[3] = pixels[yw+3] * accWidth;
// left and right stacks are initually just accumulators times their width
stackLeft[0] = stackRight[0] = stackLeftOut[0] * (stackWidth - accWidth);
stackLeft[1] = stackRight[1] = stackLeftOut[1] * (stackWidth - accWidth);
stackLeft[2] = stackRight[2] = stackLeftOut[2] * (stackWidth - accWidth);
stackLeft[3] = stackRight[3] = stackLeftOut[3] * (stackWidth - accWidth);
quadStack[0] = stackLeft[0] * stackWidth;
quadStack[1] = stackLeft[1] * stackWidth;
quadStack[2] = stackLeft[2] * stackWidth;
quadStack[3] = stackLeft[3] * stackWidth;
const ywMax = yw + width * 4 - 4;
for(let x = 1-radius; x < 0; x++) {
const idx = x*4 + yw;
const idxStackRightOutLP = constrain(idx, yw, ywMax)
const idxStackRightOutRP = constrain(idx + accWidth * 4, yw, ywMax);
const idxStackRightInLP = constrain(idx + (radius - accWidth) * 4, yw, ywMax);
const idxStackRightInRP = constrain(idx + radius * 4, yw, ywMax);
stackRightOut[0] += pixels[idxStackRightOutRP+0] - pixels[idxStackRightOutLP+0];
stackRightOut[1] += pixels[idxStackRightOutRP+1] - pixels[idxStackRightOutLP+1];
stackRightOut[2] += pixels[idxStackRightOutRP+2] - pixels[idxStackRightOutLP+2];
stackRightOut[3] += pixels[idxStackRightOutRP+3] - pixels[idxStackRightOutLP+3];
stackRightIn[0] += pixels[idxStackRightInRP+0] - pixels[idxStackRightInLP+0];
stackRightIn[1] += pixels[idxStackRightInRP+1] - pixels[idxStackRightInLP+1];
stackRightIn[2] += pixels[idxStackRightInRP+2] - pixels[idxStackRightInLP+2];
stackRightIn[3] += pixels[idxStackRightInRP+3] - pixels[idxStackRightInLP+3];
stackRight[0] += stackRightIn[0] - stackRightOut[0];
stackRight[1] += stackRightIn[1] - stackRightOut[1];
stackRight[2] += stackRightIn[2] - stackRightOut[2];
stackRight[3] += stackRightIn[3] - stackRightOut[3];
quadStack[0] += stackRight[0] - stackLeft[0];
quadStack[1] += stackRight[1] - stackLeft[1];
quadStack[2] += stackRight[2] - stackLeft[2];
quadStack[3] += stackRight[3] - stackLeft[3];
}

for (let x = 0; x < width; x++) {
const idx = x*4 + yw;
const idxStackLeftOutLP = constrain(idx - radius * 4, yw, ywMax);
const idxStackLeftOutRP = constrain(idx - (radius - accWidth) * 4, yw, ywMax);
const idxStackLeftInLP = constrain(idx - accWidth * 4, yw, ywMax);
const idxStackLeftInRP = constrain(idx, yw, ywMax);
const idxStackRightOutLP = constrain(idx, yw, ywMax);
const idxStackRightOutRP = constrain(idx + accWidth * 4, yw, ywMax);
const idxStackRightInLP = constrain(idx + (radius - accWidth) * 4, yw, ywMax);
const idxStackRightInRP = constrain(idx + radius * 4, yw, ywMax);
stackLeftOut[0] += pixels[idxStackLeftOutRP + 0] - pixels[idxStackLeftOutLP + 0];
stackLeftOut[1] += pixels[idxStackLeftOutRP + 1] - pixels[idxStackLeftOutLP + 1];
stackLeftOut[2] += pixels[idxStackLeftOutRP + 2] - pixels[idxStackLeftOutLP + 2];
stackLeftOut[3] += pixels[idxStackLeftOutRP + 3] - pixels[idxStackLeftOutLP + 3];
stackLeftIn[0] += pixels[idxStackLeftInRP + 0] - pixels[idxStackLeftInLP + 0];
stackLeftIn[1] += pixels[idxStackLeftInRP + 1] - pixels[idxStackLeftInLP + 1];
stackLeftIn[2] += pixels[idxStackLeftInRP + 2] - pixels[idxStackLeftInLP + 2];
stackLeftIn[3] += pixels[idxStackLeftInRP + 3] - pixels[idxStackLeftInLP + 3];
stackRightOut[0] += pixels[idxStackRightOutRP + 0] - pixels[idxStackRightOutLP + 0];
stackRightOut[1] += pixels[idxStackRightOutRP + 1] - pixels[idxStackRightOutLP + 1];
stackRightOut[2] += pixels[idxStackRightOutRP + 2] - pixels[idxStackRightOutLP + 2];
stackRightOut[3] += pixels[idxStackRightOutRP + 3] - pixels[idxStackRightOutLP + 3];
stackRightIn[0] += pixels[idxStackRightInRP + 0] - pixels[idxStackRightInLP + 0];
stackRightIn[1] += pixels[idxStackRightInRP + 1] - pixels[idxStackRightInLP + 1];
stackRightIn[2] += pixels[idxStackRightInRP + 2] - pixels[idxStackRightInLP + 2];
stackRightIn[3] += pixels[idxStackRightInRP + 3] - pixels[idxStackRightInLP + 3];

stackLeft[0] += stackLeftIn[0] - stackLeftOut[0];
stackLeft[1] += stackLeftIn[1] - stackLeftOut[1];
stackLeft[2] += stackLeftIn[2] - stackLeftOut[2];
stackLeft[3] += stackLeftIn[3] - stackLeftOut[3];
stackRight[0] += stackRightIn[0] - stackRightOut[0];
stackRight[1] += stackRightIn[1] - stackRightOut[1];
stackRight[2] += stackRightIn[2] - stackRightOut[2];
stackRight[3] += stackRightIn[3] - stackRightOut[3];

quadStack[0] += stackRight[0] - stackLeft[0];
quadStack[1] += stackRight[1] - stackLeft[1];
quadStack[2] += stackRight[2] - stackLeft[2];
quadStack[3] += stackRight[3] - stackLeft[3];
quadBuffer[idx + 0] = quadStack[0];
quadBuffer[idx + 1] = quadStack[1];
quadBuffer[idx + 2] = quadStack[2];
quadBuffer[idx + 3] = quadStack[3];
}
}
// blur columns
for (let x = 0; x < width; x++) {
const x4 = x * 4;

// Initiate on top pixel.
stackLeftOut[0] = stackLeftIn[0] = stackRightOut[0] = stackRightIn[0] = quadBuffer[x4] * accWidth;
stackLeftOut[1] = stackLeftIn[1] = stackRightOut[1] = stackRightIn[1] = quadBuffer[x4+1] * accWidth;
stackLeftOut[2] = stackLeftIn[2] = stackRightOut[2] = stackRightIn[2] = quadBuffer[x4+2] * accWidth;
stackLeftOut[3] = stackLeftIn[3] = stackRightOut[3] = stackRightIn[3] = quadBuffer[x4+3] * accWidth;
// left and right stacks are initually just accumulators times their width
stackLeft[0] = stackRight[0] = stackLeftOut[0] * (stackWidth - accWidth);
stackLeft[1] = stackRight[1] = stackLeftOut[1] * (stackWidth - accWidth);
stackLeft[2] = stackRight[2] = stackLeftOut[2] * (stackWidth - accWidth);
stackLeft[3] = stackRight[3] = stackLeftOut[3] * (stackWidth - accWidth);
quadStack[0] = stackLeft[0] * stackWidth;
quadStack[1] = stackLeft[1] * stackWidth;
quadStack[2] = stackLeft[2] * stackWidth;
quadStack[3] = stackLeft[3] * stackWidth;
for(let y = 1-radius; y < 0; y++) {
const idx = x4 + y*4*width;
const idxStackRightOutLP = x4 + constrain(y, 0, height-1)*4*width;
const idxStackRightOutRP = x4 + constrain(y + accWidth, 0, height-1)*4*width;
const idxStackRightInLP = x4 + constrain(y + radius - accWidth, 0, height-1)*4*width;
const idxStackRightInRP = x4 + constrain(y + radius, 0, height-1)*4*width;
stackRightOut[0] += quadBuffer[idxStackRightOutRP+0] - quadBuffer[idxStackRightOutLP+0];
stackRightOut[1] += quadBuffer[idxStackRightOutRP+1] - quadBuffer[idxStackRightOutLP+1];
stackRightOut[2] += quadBuffer[idxStackRightOutRP+2] - quadBuffer[idxStackRightOutLP+2];
stackRightOut[3] += quadBuffer[idxStackRightOutRP+3] - quadBuffer[idxStackRightOutLP+3];
stackRightIn[0] += quadBuffer[idxStackRightInRP+0] - quadBuffer[idxStackRightInLP+0];
stackRightIn[1] += quadBuffer[idxStackRightInRP+1] - quadBuffer[idxStackRightInLP+1];
stackRightIn[2] += quadBuffer[idxStackRightInRP+2] - quadBuffer[idxStackRightInLP+2];
stackRightIn[3] += quadBuffer[idxStackRightInRP+3] - quadBuffer[idxStackRightInLP+3];
stackRight[0] += stackRightIn[0] - stackRightOut[0];
stackRight[1] += stackRightIn[1] - stackRightOut[1];
stackRight[2] += stackRightIn[2] - stackRightOut[2];
stackRight[3] += stackRightIn[3] - stackRightOut[3];
quadStack[0] += stackRight[0] - stackLeft[0];
quadStack[1] += stackRight[1] - stackLeft[1];
quadStack[2] += stackRight[2] - stackLeft[2];
quadStack[3] += stackRight[3] - stackLeft[3];
}
for (let y = 0; y < height; y++) {
const idx = x4 + y*4*width;
const idxStackLeftOutLP = x4 + constrain(y - radius, 0, height-1)*4*width;
const idxStackLeftOutRP = x4 + constrain(y - radius + accWidth, 0, height-1)*4*width;
const idxStackLeftInLP = x4 + constrain(y - accWidth, 0, height-1)*4*width;
const idxStackLeftInRP = x4 + constrain(y, 0, height-1)*4*width;
const idxStackRightOutLP = x4 + constrain(y, 0, height-1)*4*width;
const idxStackRightOutRP = x4 + constrain(y + accWidth, 0, height-1)*4*width;
const idxStackRightInLP = x4 + constrain(y + radius - accWidth, 0, height-1)*4*width;
const idxStackRightInRP = x4 + constrain(y + radius, 0, height-1)*4*width;
stackLeftOut[0] += quadBuffer[idxStackLeftOutRP + 0] - quadBuffer[idxStackLeftOutLP + 0];
stackLeftOut[1] += quadBuffer[idxStackLeftOutRP + 1] - quadBuffer[idxStackLeftOutLP + 1];
stackLeftOut[2] += quadBuffer[idxStackLeftOutRP + 2] - quadBuffer[idxStackLeftOutLP + 2];
stackLeftOut[3] += quadBuffer[idxStackLeftOutRP + 3] - quadBuffer[idxStackLeftOutLP + 3];
stackLeftIn[0] += quadBuffer[idxStackLeftInRP + 0] - quadBuffer[idxStackLeftInLP + 0];
stackLeftIn[1] += quadBuffer[idxStackLeftInRP + 1] - quadBuffer[idxStackLeftInLP + 1];
stackLeftIn[2] += quadBuffer[idxStackLeftInRP + 2] - quadBuffer[idxStackLeftInLP + 2];
stackLeftIn[3] += quadBuffer[idxStackLeftInRP + 3] - quadBuffer[idxStackLeftInLP + 3];
stackRightOut[0] += quadBuffer[idxStackRightOutRP + 0] - quadBuffer[idxStackRightOutLP + 0];
stackRightOut[1] += quadBuffer[idxStackRightOutRP + 1] - quadBuffer[idxStackRightOutLP + 1];
stackRightOut[2] += quadBuffer[idxStackRightOutRP + 2] - quadBuffer[idxStackRightOutLP + 2];
stackRightOut[3] += quadBuffer[idxStackRightOutRP + 3] - quadBuffer[idxStackRightOutLP + 3];
stackRightIn[0] += quadBuffer[idxStackRightInRP + 0] - quadBuffer[idxStackRightInLP + 0];
stackRightIn[1] += quadBuffer[idxStackRightInRP + 1] - quadBuffer[idxStackRightInLP + 1];
stackRightIn[2] += quadBuffer[idxStackRightInRP + 2] - quadBuffer[idxStackRightInLP + 2];
stackRightIn[3] += quadBuffer[idxStackRightInRP + 3] - quadBuffer[idxStackRightInLP + 3];

stackLeft[0] += stackLeftIn[0] - stackLeftOut[0];
stackLeft[1] += stackLeftIn[1] - stackLeftOut[1];
stackLeft[2] += stackLeftIn[2] - stackLeftOut[2];
stackLeft[3] += stackLeftIn[3] - stackLeftOut[3];
stackRight[0] += stackRightIn[0] - stackRightOut[0];
stackRight[1] += stackRightIn[1] - stackRightOut[1];
stackRight[2] += stackRightIn[2] - stackRightOut[2];
stackRight[3] += stackRightIn[3] - stackRightOut[3];

pixels[idx + 0] = (quadStack[0] += stackRight[0] - stackLeft[0]) / (quadWeight * quadWeight) & 0xFF;
pixels[idx + 1] = (quadStack[1] += stackRight[1] - stackLeft[1]) / (quadWeight * quadWeight) & 0xFF;
pixels[idx + 2] = (quadStack[2] += stackRight[2] - stackLeft[2]) / (quadWeight * quadWeight) & 0xFF;
pixels[idx + 3] = (quadStack[3] += stackRight[3] - stackLeft[3]) / (quadWeight * quadWeight) & 0xFF;
}
}
return pixels;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
pyramidScale = 72/(1+pyramidRadius2D)
Insert cell
pyramidData2D = {
const p = new Uint32Array(pyramidRadius2D + pyramidRadius2D + 1);
for (let i = 0; i < pyramidRadius2D+1; i++){
for (let j = 0; j <= pyramidRadius2D; j++) {
p[i+j]++;
}
}
const pyramid2D = [];
let max = 0;
for (let i = 0; i < p.length; i++){
const c = p[i];
const p2 = [];
for (let j = 0; j < p.length; j++){
const val = p[j]*c;
if (val > max) max = val;
p2.push(val);
}
pyramid2D.push(p2);
}
const norm = 4*(pyramidRadius2D+1)/max;
for (let i = 0; i < p.length; i++){
for(let j = 0; j < p.length; j++){
pyramid2D[i][j] *= norm;
}
}

return pyramid2D
}
Insert cell
quadraticData = {
const p = new Uint32Array(quadRadius+1);
for (let i = 0; i <= (quadRadius+1)/2; i++){
for (let j = 0; j <= quadRadius/2; j++) {
p[i+j]++;
}
}
const q = new Uint32Array(p.length*2 - 1);
for (let i = 0; i < p.length; i++){
for (let j = 0; j < p.length; j++) {
q[i+j] += p[j];
}
}
const quadratic = [];
for (let i = 0; i < q.length; i++){
quadratic.push({name: i - p.length + 1, value: q[i]*q[i]});
}
return quadratic
}
Insert cell
quadraticHeight = 80/(4*(1+quadRadius2D)*(1+quadRadius2D))
Insert cell
quadraticScale = 72/(1+quadRadius2D)
Insert cell
quadraticData2D = {
const p = new Uint32Array(quadRadius2D+1);
for (let i = 0; i <= (quadRadius2D+1)/2; i++){
for (let j = 0; j <= quadRadius2D/2; j++) {
p[i+j]++;
}
}
const q = new Uint32Array(p.length*2 - 1);
for (let i = 0; i < p.length; i++){
for (let j = 0; j < p.length; j++) {
q[i+j] += p[j];
}
}
let max = 0;
const quadratic2D = [];
for (let i = 0; i < q.length; i++){
const c = q[i];
const q2 = [];
for(let j = 0; j < q.length; j++){
const val = c * q[j];
if (val > max) max = val;
q2.push(val);
}
quadratic2D.push(q2);
}
const norm = 4*(quadRadius2D+1)/max;
for (let i = 0; i < q.length; i++){
for(let j = 0; j < q.length; j++){
quadratic2D[i][j] *= norm;
}
}
return quadratic2D
}
Insert cell
Insert cell
chartHeight3D = width*2/3 | 0
Insert cell
startAngle = Math.PI/10
Insert cell
import {chart as pyramidChart} with {pyramidData as data, chartHeight2D as height} from "@mbostock/d3-bar-chart"
Insert cell
import {chart as pyramidChart2D} with {pyramidData2D as data, width, startAngle, pyramidScale as scale, chartHeight3D as height, color, showText} from '@jobleonard/importable-3d-bar-chart-port'
Insert cell
import {chart as quadraticStack} with {quadraticData as data, chartHeight2D as height} from "@mbostock/d3-bar-chart"
Insert cell
import {chart as quadraticChart2D} with {quadraticData2D as data, width, startAngle, quadraticScale as scale, chartHeight3D as height, color, showText} from '@jobleonard/importable-3d-bar-chart-port'
Insert cell
showText = false
Insert cell
color = (v) => '#AEF'
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