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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more