d3-hilbert
Version:
D3 layout to visualize distance variables using a continuous Hilbert space-filling space.
155 lines (142 loc) • 5.09 kB
JavaScript
// Version 1.4.1 d3-hilbert - https://github.com/vasturiano/d3-hilbert
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3 = global.d3 || {}));
})(this, (function (exports) { 'use strict';
function hilbert () {
// Hilbert curve algo, from https://en.wikipedia.org/wiki/Hilbert_curve#Applications_and_mapping_algorithms
var hilbert = function () {
//rotate/flip a quadrant appropriately
function rot(n, xy, rx, ry) {
if (ry == 0) {
if (rx == 1) {
xy[0] = n - 1 - xy[0];
xy[1] = n - 1 - xy[1];
}
//Swap x and y
xy.push(xy.shift());
}
}
// Note: this function will start breaking down for n > 2^26 (MAX_SAFE_INTEGER = 2^53)
// x,y: cell coordinates, n: sqrt of num cells (square side size)
function point2Distance(x, y, n) {
var rx,
ry,
d = 0,
xy = [x, y];
for (var s = n / 2; s >= 1; s /= 2) {
rx = (xy[0] & s) > 0;
ry = (xy[1] & s) > 0;
d += s * s * (3 * rx ^ ry);
rot(s, xy, rx, ry);
}
return d;
}
// d: distance, n: sqrt of num cells (square side size)
function distance2Point(d, n) {
var rx,
ry,
t = d,
xy = [0, 0];
for (var s = 1; s < n; s *= 2) {
rx = 1 & t / 2;
ry = 1 & (t ^ rx);
rot(s, xy, rx, ry);
xy[0] += s * rx;
xy[1] += s * ry;
t /= 4;
}
return xy;
}
return {
point2Distance: point2Distance,
distance2Point: distance2Point
};
}();
var hilbertLayout = {},
canvasWidth = 1,
order = 4,
simplifyCurves = true;
hilbertLayout.canvasWidth = function (_) {
if (!arguments.length) return canvasWidth;
canvasWidth = +_;
return hilbertLayout;
};
// Note: Maximum safe order is 26, due to JS numbers upper-boundary of 53 bits
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER
hilbertLayout.order = function (_) {
if (!arguments.length) return order;
order = +_;
return hilbertLayout;
};
hilbertLayout.simplifyCurves = function (_) {
if (!arguments.length) return simplifyCurves;
simplifyCurves = _;
return hilbertLayout;
};
hilbertLayout.layout = function (range) {
var d = getHilbertPath(range.start, range.length, order, canvasWidth, simplifyCurves);
range.cellWidth = d.cellWidth;
range.startCell = d.startCell;
range.pathVertices = d.pathVertices;
return hilbertLayout;
};
hilbertLayout.getValAtXY = function (x, y) {
var n = Math.pow(2, order),
xy = [x, y].map(function (coord) {
return Math.floor(coord * n / canvasWidth);
});
return hilbert.point2Distance(xy[0], xy[1], n);
};
hilbertLayout.getXyAtVal = function (val) {
if (val > Math.pow(4, order) || val < 0) {
console.error('Value is outside hilbert space boundaries.');
return null;
} else {
return hilbert.distance2Point(val, Math.pow(2, order));
}
};
return hilbertLayout;
//
function getHilbertPath(start, length, order, sideSize, simplifyCurves) {
if (simplifyCurves) {
// Adjust resolution
while (!Number.isInteger(start) || !Number.isInteger(length)) {
start *= 4;
length *= 4;
order += 1;
}
// resolution simplification
while (!(start % 4) && !(length % 4) && order > 0) {
start /= 4;
length /= 4;
order -= 1;
}
}
// prevent overflow
var maxPos = Math.pow(4, order);
start = Math.min(start, maxPos);
length = Math.min(length, maxPos - start);
// nSide is on a binary boundary 2^0, 2^1, 2^2, ...
var nSide = Math.pow(2, order),
cellWidth = sideSize / nSide;
var startCell = hilbert.distance2Point(start, nSide),
vertices = [],
prevPnt = startCell,
pnt;
for (var i = 1; i < length; i++) {
pnt = hilbert.distance2Point(start + i, nSide);
vertices.push(pnt[0] > prevPnt[0] ? 'R' : pnt[0] < prevPnt[0] ? 'L' : pnt[1] > prevPnt[1] ? 'D' : 'U');
prevPnt = pnt;
}
return {
cellWidth: cellWidth,
startCell: startCell,
pathVertices: vertices
};
}
}
exports.hilbert = hilbert;
}));
//# sourceMappingURL=d3-hilbert.js.map