Published
Edited
Jun 19, 2020
7 stars
Insert cell
Insert cell
Insert cell
Insert cell
nrdx = nrd(data.map(d => x(d.x))) / 5
Insert cell
nrdy = nrd(data.map(d => x(d.x))) / 5
Insert cell
Insert cell
Insert cell
Insert cell
// needs https://github.com/d3/d3-array/pull/151
d3ArrayBlur = require("https://files-hpppc9gqy.vercel.app/d3-array.js")
Insert cell
function nrd(x) {
const { ascending, deviation, quantile } = d3;

const sorted = x.slice().sort(ascending);
const r1 = quantile(sorted, 0.25),
r2 = quantile(sorted, 0.75),
h = (r2 - r1) / 1.34,
d = deviation(sorted) || 0;
return 4 * 1.06 * Math.min(d, h) * Math.pow(x.length, -1 / 5);
}
Insert cell
function nrdxy(x, y) {
const minRadius = 20;
return [Math.max(nrd(x), minRadius), Math.max(nrd(y), minRadius)];
}
Insert cell
contourDensity = {
// import {ascending, deviation, max, quantile, range, tickStep} from "d3-array";
const { ascending, deviation, max, quantile, range, tickStep } = d3;
// import {slice} from "./array.js";
const slice = Array.prototype.slice;
// import blur from "./blur.js";
const blur = d3ArrayBlur.blur;
// import constant from "./constant.js";
const constant = x => () => x;
// import contours from "./contours.js";
const contours = d3.contours;

function defaultX(d) {
return d[0];
}

function defaultY(d) {
return d[1];
}

function defaultWeight() {
return 1;
}



return function() {
var x = defaultX,
y = defaultY,
weight = defaultWeight,
dx = 960,
dy = 500,
r = nrdxy,
rx,
ry, // blur radii
n,
m, // grid width, height
ox,
oy, // grid offset, to pad for blur
k = 2, // log2(grid cell size)
pow2k = Math.pow(2, k),
pow_4k = Math.pow(2, -2 * k),
pow_2k = Math.pow(2, -k),
threshold = constant(20);

function density(data) {
if (!data.length) return [];
var vx = new Float32Array(data.length),
vy = new Float32Array(data.length);

for (let i = 0; i < data.length; i++) {
vx[i] = x(data[i], i, data);
vy[i] = y(data[i], i, data);
}

resize(vx, vy);
var grid = new Float32Array(n * m);

for (let i = 0; i < data.length; i++) {
var xi = (vx[i] + ox) * pow_2k,
yi = (vy[i] + oy) * pow_2k,
wi = +weight(data[i], i, data);
if (xi >= 0 && xi < n && yi >= 0 && yi < m) {
var x0 = Math.floor(xi),
y0 = Math.floor(yi),
xt = xi - x0 - 0.5,
yt = yi - y0 - 0.5;
grid[x0 + y0 * n] += (1 - xt) * (1 - yt) * wi;
grid[x0 + 1 + y0 * n] += xt * (1 - yt) * wi;
grid[x0 + 1 + (y0 + 1) * n] += xt * yt * wi;
grid[x0 + (y0 + 1) * n] += (1 - xt) * yt * wi;
}
}

grid = blur()
.radiusX(rx >> k)
.radiusY(ry >> k)
.width(n)(grid);

var tz = threshold(grid);

// Convert number of thresholds into uniform thresholds.
if (!Array.isArray(tz)) {
var stop = max(grid);
tz = tickStep(0, stop, tz);
tz = range(0, Math.floor(stop / tz) * tz, tz);
tz.shift();
}

return contours()
.thresholds(tz)
.size([n, m])(grid)
.map(transform);
}

function transform(geometry) {
geometry.value *= pow_4k; // Density in points per square pixel.
geometry.coordinates.forEach(transformPolygon);
return geometry;
}

function transformPolygon(coordinates) {
coordinates.forEach(transformRing);
}

function transformRing(coordinates) {
coordinates.forEach(transformPoint);
}

function transformPoint(coordinates) {
coordinates[0] = coordinates[0] * pow2k - ox;
coordinates[1] = coordinates[1] * pow2k - oy;
}

function resize(vx, vy) {
var R = r(vx, vy);
(rx = R[0]), (ry = R[1]);
ox = rx * 3;
oy = ry * 3;
n = (dx + ox * 2) >> k;
m = (dy + ox * 2) >> k;
}

density.x = function(_) {
return arguments.length
? ((x = typeof _ === "function" ? _ : constant(+_)), density)
: x;
};

density.y = function(_) {
return arguments.length
? ((y = typeof _ === "function" ? _ : constant(+_)), density)
: y;
};

density.weight = function(_) {
return arguments.length
? ((weight = typeof _ === "function" ? _ : constant(+_)), density)
: weight;
};

density.size = function(_) {
if (!arguments.length) return [dx, dy];
var _0 = Math.ceil(_[0]),
_1 = Math.ceil(_[1]);
if (!(_0 >= 0) && !(_0 >= 0)) throw new Error("invalid size");
return (dx = _0), (dy = _1), density;
};

density.cellSize = function(_) {
if (!arguments.length) return 1 << k;
if (!((_ = +_) >= 1)) throw new Error("invalid cell size");
return (k = Math.floor(Math.log(_) / Math.LN2)), density;
};

density.thresholds = function(_) {
return arguments.length
? ((threshold =
typeof _ === "function"
? _
: Array.isArray(_)
? constant(slice.call(_))
: constant(_)),
density)
: threshold;
};

density.bandwidth = function(_) {
if (!arguments.length) return r;
if (typeof _ === "function") {
r = _;
} else if (Array.isArray(_)) {
var rx = +_[0],
ry = +_[1];
if (!(rx >= 0)) throw new Error("invalid x bandwidth");
if (!(ry >= 0)) throw new Error("invalid y bandwidth");
rx = Math.round((Math.sqrt(4 * rx * rx + 1) - 1) / 2);
ry = Math.round((Math.sqrt(4 * ry * ry + 1) - 1) / 2);
r = constant([rx, ry]);
} else {
if (!((_ = +_) >= 0)) throw new Error("invalid bandwidth");
r = Math.round((Math.sqrt(4 * _ * _ + 1) - 1) / 2);
r = constant([r, r]);
}
return density;
};

return density;
};
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
contour = (!sw ? contourDensity : d3.contourDensity)()
.x(d => x(d.x))
.y(d => y(d.y))
.size([width, height])
.bandwidth(
!sw ? [bw * nrdx, bw * nrdy * ratio] : bw * Math.sqrt(nrdx * nrdy * ratio)
)
.thresholds(100)
Insert cell
contour.bandwidth()
Insert cell
Insert cell
Insert cell
Insert cell
import { checkbox, slider } from "@jashkenas/inputs"
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