| 1 | // https://d3js.org/d3-contour/ v4.0.2 Copyright 2012-2023 Mike Bostock
|
|---|
| 2 | (function (global, factory) {
|
|---|
| 3 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array')) :
|
|---|
| 4 | typeof define === 'function' && define.amd ? define(['exports', 'd3-array'], factory) :
|
|---|
| 5 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3 = global.d3 || {}, global.d3));
|
|---|
| 6 | })(this, (function (exports, d3Array) { 'use strict';
|
|---|
| 7 |
|
|---|
| 8 | var array = Array.prototype;
|
|---|
| 9 |
|
|---|
| 10 | var slice = array.slice;
|
|---|
| 11 |
|
|---|
| 12 | function ascending(a, b) {
|
|---|
| 13 | return a - b;
|
|---|
| 14 | }
|
|---|
| 15 |
|
|---|
| 16 | function area(ring) {
|
|---|
| 17 | var i = 0, n = ring.length, area = ring[n - 1][1] * ring[0][0] - ring[n - 1][0] * ring[0][1];
|
|---|
| 18 | while (++i < n) area += ring[i - 1][1] * ring[i][0] - ring[i - 1][0] * ring[i][1];
|
|---|
| 19 | return area;
|
|---|
| 20 | }
|
|---|
| 21 |
|
|---|
| 22 | var constant = x => () => x;
|
|---|
| 23 |
|
|---|
| 24 | function contains(ring, hole) {
|
|---|
| 25 | var i = -1, n = hole.length, c;
|
|---|
| 26 | while (++i < n) if (c = ringContains(ring, hole[i])) return c;
|
|---|
| 27 | return 0;
|
|---|
| 28 | }
|
|---|
| 29 |
|
|---|
| 30 | function ringContains(ring, point) {
|
|---|
| 31 | var x = point[0], y = point[1], contains = -1;
|
|---|
| 32 | for (var i = 0, n = ring.length, j = n - 1; i < n; j = i++) {
|
|---|
| 33 | var pi = ring[i], xi = pi[0], yi = pi[1], pj = ring[j], xj = pj[0], yj = pj[1];
|
|---|
| 34 | if (segmentContains(pi, pj, point)) return 0;
|
|---|
| 35 | if (((yi > y) !== (yj > y)) && ((x < (xj - xi) * (y - yi) / (yj - yi) + xi))) contains = -contains;
|
|---|
| 36 | }
|
|---|
| 37 | return contains;
|
|---|
| 38 | }
|
|---|
| 39 |
|
|---|
| 40 | function segmentContains(a, b, c) {
|
|---|
| 41 | var i; return collinear(a, b, c) && within(a[i = +(a[0] === b[0])], c[i], b[i]);
|
|---|
| 42 | }
|
|---|
| 43 |
|
|---|
| 44 | function collinear(a, b, c) {
|
|---|
| 45 | return (b[0] - a[0]) * (c[1] - a[1]) === (c[0] - a[0]) * (b[1] - a[1]);
|
|---|
| 46 | }
|
|---|
| 47 |
|
|---|
| 48 | function within(p, q, r) {
|
|---|
| 49 | return p <= q && q <= r || r <= q && q <= p;
|
|---|
| 50 | }
|
|---|
| 51 |
|
|---|
| 52 | function noop() {}
|
|---|
| 53 |
|
|---|
| 54 | var cases = [
|
|---|
| 55 | [],
|
|---|
| 56 | [[[1.0, 1.5], [0.5, 1.0]]],
|
|---|
| 57 | [[[1.5, 1.0], [1.0, 1.5]]],
|
|---|
| 58 | [[[1.5, 1.0], [0.5, 1.0]]],
|
|---|
| 59 | [[[1.0, 0.5], [1.5, 1.0]]],
|
|---|
| 60 | [[[1.0, 1.5], [0.5, 1.0]], [[1.0, 0.5], [1.5, 1.0]]],
|
|---|
| 61 | [[[1.0, 0.5], [1.0, 1.5]]],
|
|---|
| 62 | [[[1.0, 0.5], [0.5, 1.0]]],
|
|---|
| 63 | [[[0.5, 1.0], [1.0, 0.5]]],
|
|---|
| 64 | [[[1.0, 1.5], [1.0, 0.5]]],
|
|---|
| 65 | [[[0.5, 1.0], [1.0, 0.5]], [[1.5, 1.0], [1.0, 1.5]]],
|
|---|
| 66 | [[[1.5, 1.0], [1.0, 0.5]]],
|
|---|
| 67 | [[[0.5, 1.0], [1.5, 1.0]]],
|
|---|
| 68 | [[[1.0, 1.5], [1.5, 1.0]]],
|
|---|
| 69 | [[[0.5, 1.0], [1.0, 1.5]]],
|
|---|
| 70 | []
|
|---|
| 71 | ];
|
|---|
| 72 |
|
|---|
| 73 | function Contours() {
|
|---|
| 74 | var dx = 1,
|
|---|
| 75 | dy = 1,
|
|---|
| 76 | threshold = d3Array.thresholdSturges,
|
|---|
| 77 | smooth = smoothLinear;
|
|---|
| 78 |
|
|---|
| 79 | function contours(values) {
|
|---|
| 80 | var tz = threshold(values);
|
|---|
| 81 |
|
|---|
| 82 | // Convert number of thresholds into uniform thresholds.
|
|---|
| 83 | if (!Array.isArray(tz)) {
|
|---|
| 84 | const e = d3Array.extent(values, finite);
|
|---|
| 85 | tz = d3Array.ticks(...d3Array.nice(e[0], e[1], tz), tz);
|
|---|
| 86 | while (tz[tz.length - 1] >= e[1]) tz.pop();
|
|---|
| 87 | while (tz[1] < e[0]) tz.shift();
|
|---|
| 88 | } else {
|
|---|
| 89 | tz = tz.slice().sort(ascending);
|
|---|
| 90 | }
|
|---|
| 91 |
|
|---|
| 92 | return tz.map(value => contour(values, value));
|
|---|
| 93 | }
|
|---|
| 94 |
|
|---|
| 95 | // Accumulate, smooth contour rings, assign holes to exterior rings.
|
|---|
| 96 | // Based on https://github.com/mbostock/shapefile/blob/v0.6.2/shp/polygon.js
|
|---|
| 97 | function contour(values, value) {
|
|---|
| 98 | const v = value == null ? NaN : +value;
|
|---|
| 99 | if (isNaN(v)) throw new Error(`invalid value: ${value}`);
|
|---|
| 100 |
|
|---|
| 101 | var polygons = [],
|
|---|
| 102 | holes = [];
|
|---|
| 103 |
|
|---|
| 104 | isorings(values, v, function(ring) {
|
|---|
| 105 | smooth(ring, values, v);
|
|---|
| 106 | if (area(ring) > 0) polygons.push([ring]);
|
|---|
| 107 | else holes.push(ring);
|
|---|
| 108 | });
|
|---|
| 109 |
|
|---|
| 110 | holes.forEach(function(hole) {
|
|---|
| 111 | for (var i = 0, n = polygons.length, polygon; i < n; ++i) {
|
|---|
| 112 | if (contains((polygon = polygons[i])[0], hole) !== -1) {
|
|---|
| 113 | polygon.push(hole);
|
|---|
| 114 | return;
|
|---|
| 115 | }
|
|---|
| 116 | }
|
|---|
| 117 | });
|
|---|
| 118 |
|
|---|
| 119 | return {
|
|---|
| 120 | type: "MultiPolygon",
|
|---|
| 121 | value: value,
|
|---|
| 122 | coordinates: polygons
|
|---|
| 123 | };
|
|---|
| 124 | }
|
|---|
| 125 |
|
|---|
| 126 | // Marching squares with isolines stitched into rings.
|
|---|
| 127 | // Based on https://github.com/topojson/topojson-client/blob/v3.0.0/src/stitch.js
|
|---|
| 128 | function isorings(values, value, callback) {
|
|---|
| 129 | var fragmentByStart = new Array,
|
|---|
| 130 | fragmentByEnd = new Array,
|
|---|
| 131 | x, y, t0, t1, t2, t3;
|
|---|
| 132 |
|
|---|
| 133 | // Special case for the first row (y = -1, t2 = t3 = 0).
|
|---|
| 134 | x = y = -1;
|
|---|
| 135 | t1 = above(values[0], value);
|
|---|
| 136 | cases[t1 << 1].forEach(stitch);
|
|---|
| 137 | while (++x < dx - 1) {
|
|---|
| 138 | t0 = t1, t1 = above(values[x + 1], value);
|
|---|
| 139 | cases[t0 | t1 << 1].forEach(stitch);
|
|---|
| 140 | }
|
|---|
| 141 | cases[t1 << 0].forEach(stitch);
|
|---|
| 142 |
|
|---|
| 143 | // General case for the intermediate rows.
|
|---|
| 144 | while (++y < dy - 1) {
|
|---|
| 145 | x = -1;
|
|---|
| 146 | t1 = above(values[y * dx + dx], value);
|
|---|
| 147 | t2 = above(values[y * dx], value);
|
|---|
| 148 | cases[t1 << 1 | t2 << 2].forEach(stitch);
|
|---|
| 149 | while (++x < dx - 1) {
|
|---|
| 150 | t0 = t1, t1 = above(values[y * dx + dx + x + 1], value);
|
|---|
| 151 | t3 = t2, t2 = above(values[y * dx + x + 1], value);
|
|---|
| 152 | cases[t0 | t1 << 1 | t2 << 2 | t3 << 3].forEach(stitch);
|
|---|
| 153 | }
|
|---|
| 154 | cases[t1 | t2 << 3].forEach(stitch);
|
|---|
| 155 | }
|
|---|
| 156 |
|
|---|
| 157 | // Special case for the last row (y = dy - 1, t0 = t1 = 0).
|
|---|
| 158 | x = -1;
|
|---|
| 159 | t2 = values[y * dx] >= value;
|
|---|
| 160 | cases[t2 << 2].forEach(stitch);
|
|---|
| 161 | while (++x < dx - 1) {
|
|---|
| 162 | t3 = t2, t2 = above(values[y * dx + x + 1], value);
|
|---|
| 163 | cases[t2 << 2 | t3 << 3].forEach(stitch);
|
|---|
| 164 | }
|
|---|
| 165 | cases[t2 << 3].forEach(stitch);
|
|---|
| 166 |
|
|---|
| 167 | function stitch(line) {
|
|---|
| 168 | var start = [line[0][0] + x, line[0][1] + y],
|
|---|
| 169 | end = [line[1][0] + x, line[1][1] + y],
|
|---|
| 170 | startIndex = index(start),
|
|---|
| 171 | endIndex = index(end),
|
|---|
| 172 | f, g;
|
|---|
| 173 | if (f = fragmentByEnd[startIndex]) {
|
|---|
| 174 | if (g = fragmentByStart[endIndex]) {
|
|---|
| 175 | delete fragmentByEnd[f.end];
|
|---|
| 176 | delete fragmentByStart[g.start];
|
|---|
| 177 | if (f === g) {
|
|---|
| 178 | f.ring.push(end);
|
|---|
| 179 | callback(f.ring);
|
|---|
| 180 | } else {
|
|---|
| 181 | fragmentByStart[f.start] = fragmentByEnd[g.end] = {start: f.start, end: g.end, ring: f.ring.concat(g.ring)};
|
|---|
| 182 | }
|
|---|
| 183 | } else {
|
|---|
| 184 | delete fragmentByEnd[f.end];
|
|---|
| 185 | f.ring.push(end);
|
|---|
| 186 | fragmentByEnd[f.end = endIndex] = f;
|
|---|
| 187 | }
|
|---|
| 188 | } else if (f = fragmentByStart[endIndex]) {
|
|---|
| 189 | if (g = fragmentByEnd[startIndex]) {
|
|---|
| 190 | delete fragmentByStart[f.start];
|
|---|
| 191 | delete fragmentByEnd[g.end];
|
|---|
| 192 | if (f === g) {
|
|---|
| 193 | f.ring.push(end);
|
|---|
| 194 | callback(f.ring);
|
|---|
| 195 | } else {
|
|---|
| 196 | fragmentByStart[g.start] = fragmentByEnd[f.end] = {start: g.start, end: f.end, ring: g.ring.concat(f.ring)};
|
|---|
| 197 | }
|
|---|
| 198 | } else {
|
|---|
| 199 | delete fragmentByStart[f.start];
|
|---|
| 200 | f.ring.unshift(start);
|
|---|
| 201 | fragmentByStart[f.start = startIndex] = f;
|
|---|
| 202 | }
|
|---|
| 203 | } else {
|
|---|
| 204 | fragmentByStart[startIndex] = fragmentByEnd[endIndex] = {start: startIndex, end: endIndex, ring: [start, end]};
|
|---|
| 205 | }
|
|---|
| 206 | }
|
|---|
| 207 | }
|
|---|
| 208 |
|
|---|
| 209 | function index(point) {
|
|---|
| 210 | return point[0] * 2 + point[1] * (dx + 1) * 4;
|
|---|
| 211 | }
|
|---|
| 212 |
|
|---|
| 213 | function smoothLinear(ring, values, value) {
|
|---|
| 214 | ring.forEach(function(point) {
|
|---|
| 215 | var x = point[0],
|
|---|
| 216 | y = point[1],
|
|---|
| 217 | xt = x | 0,
|
|---|
| 218 | yt = y | 0,
|
|---|
| 219 | v1 = valid(values[yt * dx + xt]);
|
|---|
| 220 | if (x > 0 && x < dx && xt === x) {
|
|---|
| 221 | point[0] = smooth1(x, valid(values[yt * dx + xt - 1]), v1, value);
|
|---|
| 222 | }
|
|---|
| 223 | if (y > 0 && y < dy && yt === y) {
|
|---|
| 224 | point[1] = smooth1(y, valid(values[(yt - 1) * dx + xt]), v1, value);
|
|---|
| 225 | }
|
|---|
| 226 | });
|
|---|
| 227 | }
|
|---|
| 228 |
|
|---|
| 229 | contours.contour = contour;
|
|---|
| 230 |
|
|---|
| 231 | contours.size = function(_) {
|
|---|
| 232 | if (!arguments.length) return [dx, dy];
|
|---|
| 233 | var _0 = Math.floor(_[0]), _1 = Math.floor(_[1]);
|
|---|
| 234 | if (!(_0 >= 0 && _1 >= 0)) throw new Error("invalid size");
|
|---|
| 235 | return dx = _0, dy = _1, contours;
|
|---|
| 236 | };
|
|---|
| 237 |
|
|---|
| 238 | contours.thresholds = function(_) {
|
|---|
| 239 | return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), contours) : threshold;
|
|---|
| 240 | };
|
|---|
| 241 |
|
|---|
| 242 | contours.smooth = function(_) {
|
|---|
| 243 | return arguments.length ? (smooth = _ ? smoothLinear : noop, contours) : smooth === smoothLinear;
|
|---|
| 244 | };
|
|---|
| 245 |
|
|---|
| 246 | return contours;
|
|---|
| 247 | }
|
|---|
| 248 |
|
|---|
| 249 | // When computing the extent, ignore infinite values (as well as invalid ones).
|
|---|
| 250 | function finite(x) {
|
|---|
| 251 | return isFinite(x) ? x : NaN;
|
|---|
| 252 | }
|
|---|
| 253 |
|
|---|
| 254 | // Is the (possibly invalid) x greater than or equal to the (known valid) value?
|
|---|
| 255 | // Treat any invalid value as below negative infinity.
|
|---|
| 256 | function above(x, value) {
|
|---|
| 257 | return x == null ? false : +x >= value;
|
|---|
| 258 | }
|
|---|
| 259 |
|
|---|
| 260 | // During smoothing, treat any invalid value as negative infinity.
|
|---|
| 261 | function valid(v) {
|
|---|
| 262 | return v == null || isNaN(v = +v) ? -Infinity : v;
|
|---|
| 263 | }
|
|---|
| 264 |
|
|---|
| 265 | function smooth1(x, v0, v1, value) {
|
|---|
| 266 | const a = value - v0;
|
|---|
| 267 | const b = v1 - v0;
|
|---|
| 268 | const d = isFinite(a) || isFinite(b) ? a / b : Math.sign(a) / Math.sign(b);
|
|---|
| 269 | return isNaN(d) ? x : x + d - 0.5;
|
|---|
| 270 | }
|
|---|
| 271 |
|
|---|
| 272 | function defaultX(d) {
|
|---|
| 273 | return d[0];
|
|---|
| 274 | }
|
|---|
| 275 |
|
|---|
| 276 | function defaultY(d) {
|
|---|
| 277 | return d[1];
|
|---|
| 278 | }
|
|---|
| 279 |
|
|---|
| 280 | function defaultWeight() {
|
|---|
| 281 | return 1;
|
|---|
| 282 | }
|
|---|
| 283 |
|
|---|
| 284 | function density() {
|
|---|
| 285 | var x = defaultX,
|
|---|
| 286 | y = defaultY,
|
|---|
| 287 | weight = defaultWeight,
|
|---|
| 288 | dx = 960,
|
|---|
| 289 | dy = 500,
|
|---|
| 290 | r = 20, // blur radius
|
|---|
| 291 | k = 2, // log2(grid cell size)
|
|---|
| 292 | o = r * 3, // grid offset, to pad for blur
|
|---|
| 293 | n = (dx + o * 2) >> k, // grid width
|
|---|
| 294 | m = (dy + o * 2) >> k, // grid height
|
|---|
| 295 | threshold = constant(20);
|
|---|
| 296 |
|
|---|
| 297 | function grid(data) {
|
|---|
| 298 | var values = new Float32Array(n * m),
|
|---|
| 299 | pow2k = Math.pow(2, -k),
|
|---|
| 300 | i = -1;
|
|---|
| 301 |
|
|---|
| 302 | for (const d of data) {
|
|---|
| 303 | var xi = (x(d, ++i, data) + o) * pow2k,
|
|---|
| 304 | yi = (y(d, i, data) + o) * pow2k,
|
|---|
| 305 | wi = +weight(d, i, data);
|
|---|
| 306 | if (wi && xi >= 0 && xi < n && yi >= 0 && yi < m) {
|
|---|
| 307 | var x0 = Math.floor(xi),
|
|---|
| 308 | y0 = Math.floor(yi),
|
|---|
| 309 | xt = xi - x0 - 0.5,
|
|---|
| 310 | yt = yi - y0 - 0.5;
|
|---|
| 311 | values[x0 + y0 * n] += (1 - xt) * (1 - yt) * wi;
|
|---|
| 312 | values[x0 + 1 + y0 * n] += xt * (1 - yt) * wi;
|
|---|
| 313 | values[x0 + 1 + (y0 + 1) * n] += xt * yt * wi;
|
|---|
| 314 | values[x0 + (y0 + 1) * n] += (1 - xt) * yt * wi;
|
|---|
| 315 | }
|
|---|
| 316 | }
|
|---|
| 317 |
|
|---|
| 318 | d3Array.blur2({data: values, width: n, height: m}, r * pow2k);
|
|---|
| 319 | return values;
|
|---|
| 320 | }
|
|---|
| 321 |
|
|---|
| 322 | function density(data) {
|
|---|
| 323 | var values = grid(data),
|
|---|
| 324 | tz = threshold(values),
|
|---|
| 325 | pow4k = Math.pow(2, 2 * k);
|
|---|
| 326 |
|
|---|
| 327 | // Convert number of thresholds into uniform thresholds.
|
|---|
| 328 | if (!Array.isArray(tz)) {
|
|---|
| 329 | tz = d3Array.ticks(Number.MIN_VALUE, d3Array.max(values) / pow4k, tz);
|
|---|
| 330 | }
|
|---|
| 331 |
|
|---|
| 332 | return Contours()
|
|---|
| 333 | .size([n, m])
|
|---|
| 334 | .thresholds(tz.map(d => d * pow4k))
|
|---|
| 335 | (values)
|
|---|
| 336 | .map((c, i) => (c.value = +tz[i], transform(c)));
|
|---|
| 337 | }
|
|---|
| 338 |
|
|---|
| 339 | density.contours = function(data) {
|
|---|
| 340 | var values = grid(data),
|
|---|
| 341 | contours = Contours().size([n, m]),
|
|---|
| 342 | pow4k = Math.pow(2, 2 * k),
|
|---|
| 343 | contour = value => {
|
|---|
| 344 | value = +value;
|
|---|
| 345 | var c = transform(contours.contour(values, value * pow4k));
|
|---|
| 346 | c.value = value; // preserve exact threshold value
|
|---|
| 347 | return c;
|
|---|
| 348 | };
|
|---|
| 349 | Object.defineProperty(contour, "max", {get: () => d3Array.max(values) / pow4k});
|
|---|
| 350 | return contour;
|
|---|
| 351 | };
|
|---|
| 352 |
|
|---|
| 353 | function transform(geometry) {
|
|---|
| 354 | geometry.coordinates.forEach(transformPolygon);
|
|---|
| 355 | return geometry;
|
|---|
| 356 | }
|
|---|
| 357 |
|
|---|
| 358 | function transformPolygon(coordinates) {
|
|---|
| 359 | coordinates.forEach(transformRing);
|
|---|
| 360 | }
|
|---|
| 361 |
|
|---|
| 362 | function transformRing(coordinates) {
|
|---|
| 363 | coordinates.forEach(transformPoint);
|
|---|
| 364 | }
|
|---|
| 365 |
|
|---|
| 366 | // TODO Optimize.
|
|---|
| 367 | function transformPoint(coordinates) {
|
|---|
| 368 | coordinates[0] = coordinates[0] * Math.pow(2, k) - o;
|
|---|
| 369 | coordinates[1] = coordinates[1] * Math.pow(2, k) - o;
|
|---|
| 370 | }
|
|---|
| 371 |
|
|---|
| 372 | function resize() {
|
|---|
| 373 | o = r * 3;
|
|---|
| 374 | n = (dx + o * 2) >> k;
|
|---|
| 375 | m = (dy + o * 2) >> k;
|
|---|
| 376 | return density;
|
|---|
| 377 | }
|
|---|
| 378 |
|
|---|
| 379 | density.x = function(_) {
|
|---|
| 380 | return arguments.length ? (x = typeof _ === "function" ? _ : constant(+_), density) : x;
|
|---|
| 381 | };
|
|---|
| 382 |
|
|---|
| 383 | density.y = function(_) {
|
|---|
| 384 | return arguments.length ? (y = typeof _ === "function" ? _ : constant(+_), density) : y;
|
|---|
| 385 | };
|
|---|
| 386 |
|
|---|
| 387 | density.weight = function(_) {
|
|---|
| 388 | return arguments.length ? (weight = typeof _ === "function" ? _ : constant(+_), density) : weight;
|
|---|
| 389 | };
|
|---|
| 390 |
|
|---|
| 391 | density.size = function(_) {
|
|---|
| 392 | if (!arguments.length) return [dx, dy];
|
|---|
| 393 | var _0 = +_[0], _1 = +_[1];
|
|---|
| 394 | if (!(_0 >= 0 && _1 >= 0)) throw new Error("invalid size");
|
|---|
| 395 | return dx = _0, dy = _1, resize();
|
|---|
| 396 | };
|
|---|
| 397 |
|
|---|
| 398 | density.cellSize = function(_) {
|
|---|
| 399 | if (!arguments.length) return 1 << k;
|
|---|
| 400 | if (!((_ = +_) >= 1)) throw new Error("invalid cell size");
|
|---|
| 401 | return k = Math.floor(Math.log(_) / Math.LN2), resize();
|
|---|
| 402 | };
|
|---|
| 403 |
|
|---|
| 404 | density.thresholds = function(_) {
|
|---|
| 405 | return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), density) : threshold;
|
|---|
| 406 | };
|
|---|
| 407 |
|
|---|
| 408 | density.bandwidth = function(_) {
|
|---|
| 409 | if (!arguments.length) return Math.sqrt(r * (r + 1));
|
|---|
| 410 | if (!((_ = +_) >= 0)) throw new Error("invalid bandwidth");
|
|---|
| 411 | return r = (Math.sqrt(4 * _ * _ + 1) - 1) / 2, resize();
|
|---|
| 412 | };
|
|---|
| 413 |
|
|---|
| 414 | return density;
|
|---|
| 415 | }
|
|---|
| 416 |
|
|---|
| 417 | exports.contourDensity = density;
|
|---|
| 418 | exports.contours = Contours;
|
|---|
| 419 |
|
|---|
| 420 | }));
|
|---|