Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
225 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export {default as contours} from "./src/contours"; | ||
export {default as contourDensity} from "./src/density"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// TODO Optimize edge cases. | ||
// TODO Optimize index calculation. | ||
// TODO Optimize arguments. | ||
export function blurX(source, target, r) { | ||
var n = source.width, | ||
m = source.height, | ||
w = (r << 1) + 1; | ||
for (var j = 0; j < m; ++j) { | ||
for (var i = 0, sr = 0; i < n + r; ++i) { | ||
if (i < n) { | ||
sr += source.data[i + j * n]; | ||
} | ||
if (i >= r) { | ||
if (i >= w) { | ||
sr -= source.data[i - w + j * n]; | ||
} | ||
target.data[i - r + j * n] = sr / Math.min(i + 1, n - 1 + w - i, w); | ||
} | ||
} | ||
} | ||
} | ||
|
||
// TODO Optimize edge cases. | ||
// TODO Optimize index calculation. | ||
// TODO Optimize arguments. | ||
export function blurY(source, target, r) { | ||
var n = source.width, | ||
m = source.height, | ||
w = (r << 1) + 1; | ||
for (var i = 0; i < n; ++i) { | ||
for (var j = 0, sr = 0; j < m + r; ++j) { | ||
if (j < m) { | ||
sr += source.data[i + j * n]; | ||
} | ||
if (j >= r) { | ||
if (j >= w) { | ||
sr -= source.data[i + (j - w) * n]; | ||
} | ||
target.data[i + (j - r) * n] = sr / Math.min(j + 1, m - 1 + w - j, w); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import {max, range, tickStep} from "d3-array"; | ||
import {slice} from "./array"; | ||
import {blurX, blurY} from "./blur"; | ||
import constant from "./constant"; | ||
import contours from "./contours"; | ||
|
||
function defaultX(d) { | ||
return d[0]; | ||
} | ||
|
||
function defaultY(d) { | ||
return d[1]; | ||
} | ||
|
||
export default function() { | ||
var x = defaultX, | ||
y = defaultY, | ||
dx = 960, | ||
dy = 500, | ||
r = 20, // blur radius | ||
k = 2, // log2(grid cell size) | ||
o = r * 3, // grid offset, to pad for blur | ||
n = (dx + o * 2) >> k, // grid width | ||
m = (dy + o * 2) >> k, // grid height | ||
threshold = constant(20); | ||
|
||
function density(data) { | ||
var values0 = new Float32Array(n * m), | ||
values1 = new Float32Array(n * m); | ||
|
||
data.forEach(function(d, i, data) { | ||
var xi = (x(d, i, data) + o) >> k, | ||
yi = (y(d, i, data) + o) >> k; | ||
if (xi >= 0 && xi < n && yi >= 0 && yi < m) { | ||
++values0[xi + yi * n]; | ||
} | ||
}); | ||
|
||
// TODO Optimize. | ||
blurX({width: n, height: m, data: values0}, {width: n, height: m, data: values1}, r >> k); | ||
blurY({width: n, height: m, data: values1}, {width: n, height: m, data: values0}, r >> k); | ||
blurX({width: n, height: m, data: values0}, {width: n, height: m, data: values1}, r >> k); | ||
blurY({width: n, height: m, data: values1}, {width: n, height: m, data: values0}, r >> k); | ||
blurX({width: n, height: m, data: values0}, {width: n, height: m, data: values1}, r >> k); | ||
blurY({width: n, height: m, data: values1}, {width: n, height: m, data: values0}, r >> k); | ||
|
||
var tz = threshold(values0); | ||
|
||
// Convert number of thresholds into uniform thresholds. | ||
if (!Array.isArray(tz)) { | ||
var stop = max(values0); | ||
tz = tickStep(0, stop, tz); | ||
tz = range(0, Math.floor(stop / tz) * tz, tz); | ||
tz.shift(); | ||
} | ||
|
||
return contours() | ||
.thresholds(tz) | ||
.size([n, m]) | ||
(values0) | ||
.map(transform); | ||
} | ||
|
||
function transform(geometry) { | ||
geometry.value *= Math.pow(2, -2 * k); // Density in points per square pixel. | ||
geometry.coordinates.forEach(transformPolygon); | ||
return geometry; | ||
} | ||
|
||
function transformPolygon(coordinates) { | ||
coordinates.forEach(transformRing); | ||
} | ||
|
||
function transformRing(coordinates) { | ||
coordinates.forEach(transformPoint); | ||
} | ||
|
||
// TODO Optimize. | ||
function transformPoint(coordinates) { | ||
coordinates[0] = coordinates[0] * Math.pow(2, k) - o; | ||
coordinates[1] = coordinates[1] * Math.pow(2, k) - o; | ||
} | ||
|
||
function resize() { | ||
o = r * 3; | ||
n = (dx + o * 2) >> k; | ||
m = (dy + o * 2) >> k; | ||
return density; | ||
} | ||
|
||
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.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, resize(); | ||
}; | ||
|
||
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), resize(); | ||
}; | ||
|
||
density.thresholds = function(_) { | ||
return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), density) : threshold; | ||
}; | ||
|
||
density.bandwidth = function(_) { | ||
if (!arguments.length) return Math.sqrt(r * (r + 1)); | ||
if (!((_ = +_) >= 0)) throw new Error("invalid bandwidth"); | ||
return r = Math.round((Math.sqrt(4 * _ * _ + 1) - 1) / 2), resize(); | ||
}; | ||
|
||
return density; | ||
} |