density.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import {blur2, max, ticks} from "d3-array";
  2. import {slice} from "./array.js";
  3. import constant from "./constant.js";
  4. import Contours from "./contours.js";
  5. function defaultX(d) {
  6. return d[0];
  7. }
  8. function defaultY(d) {
  9. return d[1];
  10. }
  11. function defaultWeight() {
  12. return 1;
  13. }
  14. export default function() {
  15. var x = defaultX,
  16. y = defaultY,
  17. weight = defaultWeight,
  18. dx = 960,
  19. dy = 500,
  20. r = 20, // blur radius
  21. k = 2, // log2(grid cell size)
  22. o = r * 3, // grid offset, to pad for blur
  23. n = (dx + o * 2) >> k, // grid width
  24. m = (dy + o * 2) >> k, // grid height
  25. threshold = constant(20);
  26. function grid(data) {
  27. var values = new Float32Array(n * m),
  28. pow2k = Math.pow(2, -k),
  29. i = -1;
  30. for (const d of data) {
  31. var xi = (x(d, ++i, data) + o) * pow2k,
  32. yi = (y(d, i, data) + o) * pow2k,
  33. wi = +weight(d, i, data);
  34. if (wi && xi >= 0 && xi < n && yi >= 0 && yi < m) {
  35. var x0 = Math.floor(xi),
  36. y0 = Math.floor(yi),
  37. xt = xi - x0 - 0.5,
  38. yt = yi - y0 - 0.5;
  39. values[x0 + y0 * n] += (1 - xt) * (1 - yt) * wi;
  40. values[x0 + 1 + y0 * n] += xt * (1 - yt) * wi;
  41. values[x0 + 1 + (y0 + 1) * n] += xt * yt * wi;
  42. values[x0 + (y0 + 1) * n] += (1 - xt) * yt * wi;
  43. }
  44. }
  45. blur2({data: values, width: n, height: m}, r * pow2k);
  46. return values;
  47. }
  48. function density(data) {
  49. var values = grid(data),
  50. tz = threshold(values),
  51. pow4k = Math.pow(2, 2 * k);
  52. // Convert number of thresholds into uniform thresholds.
  53. if (!Array.isArray(tz)) {
  54. tz = ticks(Number.MIN_VALUE, max(values) / pow4k, tz);
  55. }
  56. return Contours()
  57. .size([n, m])
  58. .thresholds(tz.map(d => d * pow4k))
  59. (values)
  60. .map((c, i) => (c.value = +tz[i], transform(c)));
  61. }
  62. density.contours = function(data) {
  63. var values = grid(data),
  64. contours = Contours().size([n, m]),
  65. pow4k = Math.pow(2, 2 * k),
  66. contour = value => {
  67. value = +value;
  68. var c = transform(contours.contour(values, value * pow4k));
  69. c.value = value; // preserve exact threshold value
  70. return c;
  71. };
  72. Object.defineProperty(contour, "max", {get: () => max(values) / pow4k});
  73. return contour;
  74. };
  75. function transform(geometry) {
  76. geometry.coordinates.forEach(transformPolygon);
  77. return geometry;
  78. }
  79. function transformPolygon(coordinates) {
  80. coordinates.forEach(transformRing);
  81. }
  82. function transformRing(coordinates) {
  83. coordinates.forEach(transformPoint);
  84. }
  85. // TODO Optimize.
  86. function transformPoint(coordinates) {
  87. coordinates[0] = coordinates[0] * Math.pow(2, k) - o;
  88. coordinates[1] = coordinates[1] * Math.pow(2, k) - o;
  89. }
  90. function resize() {
  91. o = r * 3;
  92. n = (dx + o * 2) >> k;
  93. m = (dy + o * 2) >> k;
  94. return density;
  95. }
  96. density.x = function(_) {
  97. return arguments.length ? (x = typeof _ === "function" ? _ : constant(+_), density) : x;
  98. };
  99. density.y = function(_) {
  100. return arguments.length ? (y = typeof _ === "function" ? _ : constant(+_), density) : y;
  101. };
  102. density.weight = function(_) {
  103. return arguments.length ? (weight = typeof _ === "function" ? _ : constant(+_), density) : weight;
  104. };
  105. density.size = function(_) {
  106. if (!arguments.length) return [dx, dy];
  107. var _0 = +_[0], _1 = +_[1];
  108. if (!(_0 >= 0 && _1 >= 0)) throw new Error("invalid size");
  109. return dx = _0, dy = _1, resize();
  110. };
  111. density.cellSize = function(_) {
  112. if (!arguments.length) return 1 << k;
  113. if (!((_ = +_) >= 1)) throw new Error("invalid cell size");
  114. return k = Math.floor(Math.log(_) / Math.LN2), resize();
  115. };
  116. density.thresholds = function(_) {
  117. return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), density) : threshold;
  118. };
  119. density.bandwidth = function(_) {
  120. if (!arguments.length) return Math.sqrt(r * (r + 1));
  121. if (!((_ = +_) >= 0)) throw new Error("invalid bandwidth");
  122. return r = (Math.sqrt(4 * _ * _ + 1) - 1) / 2, resize();
  123. };
  124. return density;
  125. }