UNPKG

regl-scatter2d

Version:
1,838 lines (1,541 loc) 672 kB
<!DOCTYPE html> <html> <head> <title>regl-scatter2d</title> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" /> <meta charset=utf-8><meta name="application-name" content="regl-scatter2d"> <meta name="subject" content="Scatter2d plot built with regl"> <meta name="abstract" content="Scatter2d plot built with regl"> <meta name="twitter:title" content="regl-scatter2d"> <meta name="description" content="Scatter2d plot built with regl"> <meta name="twitter:description" content="Scatter2d plot built with regl"> <meta name="author" content="Dima Yv &lt;df.creative@gmail.com&gt;"> <meta name="twitter:creator" content="Dima Yv &lt;df.creative@gmail.com&gt;"> <meta name="twitter:card" content="summary"> <meta property="og:title" content="regl-scatter2d"> <meta property="og:description" content="Scatter2d plot built with regl"> <meta property="article:author" content="Dima Yv &lt;df.creative@gmail.com&gt;"> <style type="text/css">.github-corner{display:block;position:absolute;top:0;left:auto;right:0;z-index:10000}.github-corner svg{display:block}.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%{transform:rotate(0)}20%{transform:rotate(-25deg)}40%{transform:rotate(10deg)}60%{transform:rotate(-25deg)}80%{transform:rotate(10deg)}100%{transform:rotate(0)}}@media(max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style></head> <body><a href="https://github.com/dfcreative/regl-scatter2d" class="github-corner" aria-label="View source on Github"><svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 250 250" fill="#fff"> <path d="M0 0l115 115h15l12 27 108 108V0z" fill="#333"/> <path class="octo-arm" d="M128 109c-15-9-9-19-9-19 3-7 2-11 2-11-1-7 3-2 3-2 4 5 2 11 2 11-3 10 5 15 9 16" style="-webkit-transform-origin: 130px 106px; transform-origin: 130px 106px"/> <path class="octo-body" d="M115 115s4 2 5 0l14-14c3-2 6-3 8-3-8-11-15-24 2-41 5-5 10-7 16-7 1-2 3-7 12-11 0 0 5 3 7 16 4 2 8 5 12 9s7 8 9 12c14 3 17 7 17 7-4 8-9 11-11 11 0 6-2 11-7 16-16 16-30 10-41 2 0 3-1 7-5 11l-12 11c-1 1 1 5 1 5z"/> </svg> </a></body> <script> (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ /** * @module point-cluster */ 'use strict'; var kdtree = require('kdgrass'); var extend = require('object-assign'); var clamp = require('clamp'); var getBounds = require('array-bounds'); var snap = require('snap-points-2d'); module.exports = pointCluster; function pointCluster(points) { var tree = kdtree(points, 1); var dataBounds = getBounds(points, 2); return cluster; //returns points belonging to certain scale (that is kdtree level) function cluster(dist, bounds) { if (!bounds) bounds = dataBounds; var result = []; var r = dist / 2, r2 = r * r, pr2 = Math.PI * r2; var box = dataBounds.slice(); let maxDepth = 25; getPoints(tree, 0, tree.ids.length - 1, 0, box, 0); function getPoints(tree, from, to, axis, box, depth) { var coords = tree.coords, ids = tree.ids, nodeSize = tree.nodeSize; var range = to - from; //median var m = Math.floor((from + to) / 2); //if bottom reached - include every point if (range <= nodeSize) { // for (var id = from; id <= to; id++) { // var x = coords[2 * id]; // var y = coords[2 * id + 1]; // if (x >= bounds[0] && x <= bounds[2] && y >= bounds[1] && y <= bounds[3]) // addPoint(id) // } // addPoint(m) //pick single random from the range // var id = Math.floor(Math.random() * (from - to) + from) return; } var x = coords[2 * m]; var y = coords[2 * m + 1]; result.push(ids[m]); //if required radius larger than cluster span - exit if (sq(box) <= pr2) { //TODO: mark points belonging to radius as covered return; } // if (x >= bounds[0] && x <= bounds[2] && y >= bounds[1] && y <= bounds[3]) { // var id = Math.floor(Math.random() * range + from) // result.push(ids[m]); // } //go deeper axis = (axis + 1) % 2; depth++; if (depth <= maxDepth) { getPoints(tree, from, m - 1, axis, [box[0], box[1], axis ? x : box[2], axis ? box[3] : y], depth); getPoints(tree, m + 1, to, axis, [axis ? x : box[0], axis ? box[1] : y, box[2], box[3]], depth); } else { throw 'Max depth reached'; } } return result; } function sq(box) { var dx = box[2] - box[0]; var dy = box[3] - box[1]; return dx * dy; } function maxDist(box) { var max = Math.max(box[2] - box[0], box[3] - box[1]); return max * max; } function sqDist(box) { var dx = box[2] - box[0]; var dy = box[3] - box[1]; return dx * dx + dy * dy; } //get points covering the area with defined radius function cover(radius) { var result = []; //TODO: in order to reduce scale search, we can limit radius by known data bounds //TODO: point radius may vary for different points, so take that in account, like id => r //object is way faster than Set for .has testing on big number of items var marked = {}; var ids = tree.ids; for (var i = 0, l = ids.length; i < l; i++) { var id = ids[i]; // if we've already visited the point at this zoom level, skip it if (marked[id]) { continue; } marked[id] = true; var x = points[id * 2]; var y = points[id * 2 + 1]; // exclude neighbours from processing var neighborIds = tree.within(x, y, radius); // var neighborIds = tree.range(x-r, y-r, x+r, y+r); for (var j = 0; j < neighborIds.length; j++) { var b = neighborIds[j]; if (marked[b]) { continue; } marked[b] = true; } // put point for the level result.push(id); } return result; } } },{"array-bounds":2,"clamp":4,"kdgrass":6,"object-assign":7,"snap-points-2d":9}],2:[function(require,module,exports){ 'use strict'; module.exports = normalize; function normalize(arr, dim) { if (!arr || arr.length == null) throw Error('Argument should be an array'); if (dim == null) dim = 1;else dim = Math.floor(dim); var bounds = Array(dim * 2); for (var offset = 0; offset < dim; offset++) { var max = -Infinity, min = Infinity, i = offset, l = arr.length; for (; i < l; i += dim) { if (arr[i] > max) max = arr[i]; if (arr[i] < min) min = arr[i]; } bounds[offset] = min; bounds[dim + offset] = max; } return bounds; } },{}],3:[function(require,module,exports){ /** * Bit twiddling hacks for JavaScript. * * Author: Mikola Lysenko * * Ported from Stanford bit twiddling hack library: * http://graphics.stanford.edu/~seander/bithacks.html */ "use strict";"use restrict"; //Number of bits in an integer var INT_BITS = 32; //Constants exports.INT_BITS = INT_BITS; exports.INT_MAX = 0x7fffffff; exports.INT_MIN = -1 << INT_BITS - 1; //Returns -1, 0, +1 depending on sign of x exports.sign = function (v) { return (v > 0) - (v < 0); }; //Computes absolute value of integer exports.abs = function (v) { var mask = v >> INT_BITS - 1; return (v ^ mask) - mask; }; //Computes minimum of integers x and y exports.min = function (x, y) { return y ^ (x ^ y) & -(x < y); }; //Computes maximum of integers x and y exports.max = function (x, y) { return x ^ (x ^ y) & -(x < y); }; //Checks if a number is a power of two exports.isPow2 = function (v) { return !(v & v - 1) && !!v; }; //Computes log base 2 of v exports.log2 = function (v) { var r, shift; r = (v > 0xFFFF) << 4;v >>>= r; shift = (v > 0xFF) << 3;v >>>= shift;r |= shift; shift = (v > 0xF) << 2;v >>>= shift;r |= shift; shift = (v > 0x3) << 1;v >>>= shift;r |= shift; return r | v >> 1; }; //Computes log base 10 of v exports.log10 = function (v) { return v >= 1000000000 ? 9 : v >= 100000000 ? 8 : v >= 10000000 ? 7 : v >= 1000000 ? 6 : v >= 100000 ? 5 : v >= 10000 ? 4 : v >= 1000 ? 3 : v >= 100 ? 2 : v >= 10 ? 1 : 0; }; //Counts number of bits exports.popCount = function (v) { v = v - (v >>> 1 & 0x55555555); v = (v & 0x33333333) + (v >>> 2 & 0x33333333); return (v + (v >>> 4) & 0xF0F0F0F) * 0x1010101 >>> 24; }; //Counts number of trailing zeros function countTrailingZeros(v) { var c = 32; v &= -v; if (v) c--; if (v & 0x0000FFFF) c -= 16; if (v & 0x00FF00FF) c -= 8; if (v & 0x0F0F0F0F) c -= 4; if (v & 0x33333333) c -= 2; if (v & 0x55555555) c -= 1; return c; } exports.countTrailingZeros = countTrailingZeros; //Rounds to next power of 2 exports.nextPow2 = function (v) { v += v === 0; --v; v |= v >>> 1; v |= v >>> 2; v |= v >>> 4; v |= v >>> 8; v |= v >>> 16; return v + 1; }; //Rounds down to previous power of 2 exports.prevPow2 = function (v) { v |= v >>> 1; v |= v >>> 2; v |= v >>> 4; v |= v >>> 8; v |= v >>> 16; return v - (v >>> 1); }; //Computes parity of word exports.parity = function (v) { v ^= v >>> 16; v ^= v >>> 8; v ^= v >>> 4; v &= 0xf; return 0x6996 >>> v & 1; }; var REVERSE_TABLE = new Array(256); (function (tab) { for (var i = 0; i < 256; ++i) { var v = i, r = i, s = 7; for (v >>>= 1; v; v >>>= 1) { r <<= 1; r |= v & 1; --s; } tab[i] = r << s & 0xff; } })(REVERSE_TABLE); //Reverse bits in a 32 bit word exports.reverse = function (v) { return REVERSE_TABLE[v & 0xff] << 24 | REVERSE_TABLE[v >>> 8 & 0xff] << 16 | REVERSE_TABLE[v >>> 16 & 0xff] << 8 | REVERSE_TABLE[v >>> 24 & 0xff]; }; //Interleave bits of 2 coordinates with 16 bits. Useful for fast quadtree codes exports.interleave2 = function (x, y) { x &= 0xFFFF; x = (x | x << 8) & 0x00FF00FF; x = (x | x << 4) & 0x0F0F0F0F; x = (x | x << 2) & 0x33333333; x = (x | x << 1) & 0x55555555; y &= 0xFFFF; y = (y | y << 8) & 0x00FF00FF; y = (y | y << 4) & 0x0F0F0F0F; y = (y | y << 2) & 0x33333333; y = (y | y << 1) & 0x55555555; return x | y << 1; }; //Extracts the nth interleaved component exports.deinterleave2 = function (v, n) { v = v >>> n & 0x55555555; v = (v | v >>> 1) & 0x33333333; v = (v | v >>> 2) & 0x0F0F0F0F; v = (v | v >>> 4) & 0x00FF00FF; v = (v | v >>> 16) & 0x000FFFF; return v << 16 >> 16; }; //Interleave bits of 3 coordinates, each with 10 bits. Useful for fast octree codes exports.interleave3 = function (x, y, z) { x &= 0x3FF; x = (x | x << 16) & 4278190335; x = (x | x << 8) & 251719695; x = (x | x << 4) & 3272356035; x = (x | x << 2) & 1227133513; y &= 0x3FF; y = (y | y << 16) & 4278190335; y = (y | y << 8) & 251719695; y = (y | y << 4) & 3272356035; y = (y | y << 2) & 1227133513; x |= y << 1; z &= 0x3FF; z = (z | z << 16) & 4278190335; z = (z | z << 8) & 251719695; z = (z | z << 4) & 3272356035; z = (z | z << 2) & 1227133513; return x | z << 2; }; //Extracts nth interleaved component of a 3-tuple exports.deinterleave3 = function (v, n) { v = v >>> n & 1227133513; v = (v | v >>> 2) & 3272356035; v = (v | v >>> 4) & 251719695; v = (v | v >>> 8) & 4278190335; v = (v | v >>> 16) & 0x3FF; return v << 22 >> 22; }; //Computes next combination in colexicographic order (this is mistakenly called nextPermutation on the bit twiddling hacks page) exports.nextCombination = function (v) { var t = v | v - 1; return t + 1 | (~t & -~t) - 1 >>> countTrailingZeros(v) + 1; }; },{}],4:[function(require,module,exports){ module.exports = clamp; function clamp(value, min, max) { return min < max ? value < min ? min : value > max ? max : value : value < max ? max : value > min ? min : value; } },{}],5:[function(require,module,exports){ "use strict"; function dupe_array(count, value, i) { var c = count[i] | 0; if (c <= 0) { return []; } var result = new Array(c), j; if (i === count.length - 1) { for (j = 0; j < c; ++j) { result[j] = value; } } else { for (j = 0; j < c; ++j) { result[j] = dupe_array(count, value, i + 1); } } return result; } function dupe_number(count, value) { var result, i; result = new Array(count); for (i = 0; i < count; ++i) { result[i] = value; } return result; } function dupe(count, value) { if (typeof value === "undefined") { value = 0; } switch (typeof count) { case "number": if (count > 0) { return dupe_number(count | 0, value); } break; case "object": if (typeof count.length === "number") { return dupe_array(count, value, 0); } break; } return []; } module.exports = dupe; },{}],6:[function(require,module,exports){ /** * @module kdgrass * * Static kdbush */ 'use strict'; module.exports = KDTree; //optimized kdbush function KDTree(points, nodeSize) { if (!(this instanceof KDTree)) return new KDTree(points, nodeSize); this.nodeSize = nodeSize || 64; this.coords = points.slice(); var l = Math.floor(points.length / 2); this.ids = Array(l); for (var i = 0; i < l; i++) { this.ids[i] = i; } sortKD(this.ids, this.coords, this.nodeSize, 0, this.ids.length - 1, 0); } KDTree.prototype.range = function range(minX, minY, maxX, maxY) { var ids = this.ids, coords = this.coords, nodeSize = this.nodeSize; var stack = [0, ids.length - 1, 0]; var result = []; var x, y; while (stack.length) { var axis = stack.pop(); var right = stack.pop(); var left = stack.pop(); if (right - left <= nodeSize) { for (var i = left; i <= right; i++) { x = coords[2 * i]; y = coords[2 * i + 1]; if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[i]); } continue; } var m = Math.floor((left + right) / 2); x = coords[2 * m]; y = coords[2 * m + 1]; if (x >= minX && x <= maxX && y >= minY && y <= maxY) result.push(ids[m]); var nextAxis = (axis + 1) % 2; if (axis === 0 ? minX <= x : minY <= y) { stack.push(left); stack.push(m - 1); stack.push(nextAxis); } if (axis === 0 ? maxX >= x : maxY >= y) { stack.push(m + 1); stack.push(right); stack.push(nextAxis); } } return result; }; KDTree.prototype.within = function within(qx, qy, r) { var ids = this.ids, coords = this.coords, nodeSize = this.nodeSize; var stack = [0, ids.length - 1, 0]; var result = []; var r2 = r * r; while (stack.length) { var axis = stack.pop(); var right = stack.pop(); var left = stack.pop(); if (right - left <= nodeSize) { for (var i = left; i <= right; i++) { if (sqDist(coords[2 * i], coords[2 * i + 1], qx, qy) <= r2) result.push(ids[i]); } continue; } var m = Math.floor((left + right) / 2); var x = coords[2 * m]; var y = coords[2 * m + 1]; if (sqDist(x, y, qx, qy) <= r2) result.push(ids[m]); var nextAxis = (axis + 1) % 2; if (axis === 0 ? qx - r <= x : qy - r <= y) { stack.push(left); stack.push(m - 1); stack.push(nextAxis); } if (axis === 0 ? qx + r >= x : qy + r >= y) { stack.push(m + 1); stack.push(right); stack.push(nextAxis); } } return result; }; function sqDist(ax, ay, bx, by) { var dx = ax - bx; var dy = ay - by; return dx * dx + dy * dy; } function sortKD(ids, coords, nodeSize, left, right, depth) { if (right - left <= nodeSize) return; var m = Math.floor((left + right) / 2); select(ids, coords, m, left, right, depth % 2); sortKD(ids, coords, nodeSize, left, m - 1, depth + 1); sortKD(ids, coords, nodeSize, m + 1, right, depth + 1); } function select(ids, coords, k, left, right, inc) { while (right > left) { if (right - left > 600) { var n = right - left + 1; var m = k - left + 1; var z = Math.log(n); var s = 0.5 * Math.exp(2 * z / 3); var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); var newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); select(ids, coords, k, newLeft, newRight, inc); } var t = coords[2 * k + inc]; var i = left; var j = right; swapItem(ids, coords, left, k); if (coords[2 * right + inc] > t) swapItem(ids, coords, left, right); while (i < j) { swapItem(ids, coords, i, j); i++; j--; while (coords[2 * i + inc] < t) i++; while (coords[2 * j + inc] > t) j--; } if (coords[2 * left + inc] === t) swapItem(ids, coords, left, j);else { j++; swapItem(ids, coords, j, right); } if (j <= k) left = j + 1; if (k <= j) right = j - 1; } } function swapItem(ids, coords, i, j) { swap(ids, i, j); swap(coords, 2 * i, 2 * j); swap(coords, 2 * i + 1, 2 * j + 1); } function swap(arr, i, j) { var tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } },{}],7:[function(require,module,exports){ /* object-assign (c) Sindre Sorhus @license MIT */ 'use strict'; /* eslint-disable no-unused-vars */ var getOwnPropertySymbols = Object.getOwnPropertySymbols; var hasOwnProperty = Object.prototype.hasOwnProperty; var propIsEnumerable = Object.prototype.propertyIsEnumerable; function toObject(val) { if (val === null || val === undefined) { throw new TypeError('Object.assign cannot be called with null or undefined'); } return Object(val); } function shouldUseNative() { try { if (!Object.assign) { return false; } // Detect buggy property enumeration order in older V8 versions. // https://bugs.chromium.org/p/v8/issues/detail?id=4118 var test1 = new String('abc'); // eslint-disable-line no-new-wrappers test1[5] = 'de'; if (Object.getOwnPropertyNames(test1)[0] === '5') { return false; } // https://bugs.chromium.org/p/v8/issues/detail?id=3056 var test2 = {}; for (var i = 0; i < 10; i++) { test2['_' + String.fromCharCode(i)] = i; } var order2 = Object.getOwnPropertyNames(test2).map(function (n) { return test2[n]; }); if (order2.join('') !== '0123456789') { return false; } // https://bugs.chromium.org/p/v8/issues/detail?id=3056 var test3 = {}; 'abcdefghijklmnopqrst'.split('').forEach(function (letter) { test3[letter] = letter; }); if (Object.keys(Object.assign({}, test3)).join('') !== 'abcdefghijklmnopqrst') { return false; } return true; } catch (err) { // We don't expect any of the above to throw, but better to be safe. return false; } } module.exports = shouldUseNative() ? Object.assign : function (target, source) { var from; var to = toObject(target); var symbols; for (var s = 1; s < arguments.length; s++) { from = Object(arguments[s]); for (var key in from) { if (hasOwnProperty.call(from, key)) { to[key] = from[key]; } } if (getOwnPropertySymbols) { symbols = getOwnPropertySymbols(from); for (var i = 0; i < symbols.length; i++) { if (propIsEnumerable.call(from, symbols[i])) { to[symbols[i]] = from[symbols[i]]; } } } } return to; }; },{}],8:[function(require,module,exports){ 'use strict'; module.exports = sortLevels; var INSERT_SORT_CUTOFF = 32; function sortLevels(data_levels, data_points, data_ids, data_weights, n0) { if (n0 <= 4 * INSERT_SORT_CUTOFF) { insertionSort(0, n0 - 1, data_levels, data_points, data_ids, data_weights); } else { quickSort(0, n0 - 1, data_levels, data_points, data_ids, data_weights); } } function insertionSort(left, right, data_levels, data_points, data_ids, data_weights) { for (var i = left + 1; i <= right; ++i) { var a_level = data_levels[i]; var a_x = data_points[2 * i]; var a_y = data_points[2 * i + 1]; var a_id = data_ids[i]; var a_weight = data_weights[i]; var j = i; while (j > left) { var b_level = data_levels[j - 1]; var b_x = data_points[2 * (j - 1)]; if ((b_level - a_level || a_x - b_x) >= 0) { break; } data_levels[j] = b_level; data_points[2 * j] = b_x; data_points[2 * j + 1] = data_points[2 * j - 1]; data_ids[j] = data_ids[j - 1]; data_weights[j] = data_weights[j - 1]; j -= 1; } data_levels[j] = a_level; data_points[2 * j] = a_x; data_points[2 * j + 1] = a_y; data_ids[j] = a_id; data_weights[j] = a_weight; } } function swap(i, j, data_levels, data_points, data_ids, data_weights) { var a_level = data_levels[i]; var a_x = data_points[2 * i]; var a_y = data_points[2 * i + 1]; var a_id = data_ids[i]; var a_weight = data_weights[i]; data_levels[i] = data_levels[j]; data_points[2 * i] = data_points[2 * j]; data_points[2 * i + 1] = data_points[2 * j + 1]; data_ids[i] = data_ids[j]; data_weights[i] = data_weights[j]; data_levels[j] = a_level; data_points[2 * j] = a_x; data_points[2 * j + 1] = a_y; data_ids[j] = a_id; data_weights[j] = a_weight; } function move(i, j, data_levels, data_points, data_ids, data_weights) { data_levels[i] = data_levels[j]; data_points[2 * i] = data_points[2 * j]; data_points[2 * i + 1] = data_points[2 * j + 1]; data_ids[i] = data_ids[j]; data_weights[i] = data_weights[j]; } function rotate(i, j, k, data_levels, data_points, data_ids, data_weights) { var a_level = data_levels[i]; var a_x = data_points[2 * i]; var a_y = data_points[2 * i + 1]; var a_id = data_ids[i]; var a_weight = data_weights[i]; data_levels[i] = data_levels[j]; data_points[2 * i] = data_points[2 * j]; data_points[2 * i + 1] = data_points[2 * j + 1]; data_ids[i] = data_ids[j]; data_weights[i] = data_weights[j]; data_levels[j] = data_levels[k]; data_points[2 * j] = data_points[2 * k]; data_points[2 * j + 1] = data_points[2 * k + 1]; data_ids[j] = data_ids[k]; data_weights[j] = data_weights[k]; data_levels[k] = a_level; data_points[2 * k] = a_x; data_points[2 * k + 1] = a_y; data_ids[k] = a_id; data_weights[k] = a_weight; } function shufflePivot(i, j, a_level, a_x, a_y, a_id, a_weight, data_levels, data_points, data_ids, data_weights) { data_levels[i] = data_levels[j]; data_points[2 * i] = data_points[2 * j]; data_points[2 * i + 1] = data_points[2 * j + 1]; data_ids[i] = data_ids[j]; data_weights[i] = data_weights[j]; data_levels[j] = a_level; data_points[2 * j] = a_x; data_points[2 * j + 1] = a_y; data_ids[j] = a_id; data_weights[j] = a_weight; } function compare(i, j, data_levels, data_points, data_ids) { return (data_levels[i] - data_levels[j] || data_points[2 * j] - data_points[2 * i] || data_ids[i] - data_ids[j]) < 0; } function comparePivot(i, level, x, y, id, data_levels, data_points, data_ids) { return (level - data_levels[i] || data_points[2 * i] - x || id - data_ids[i]) < 0; } function quickSort(left, right, data_levels, data_points, data_ids, data_weights) { var sixth = (right - left + 1) / 6 | 0, index1 = left + sixth, index5 = right - sixth, index3 = left + right >> 1, index2 = index3 - sixth, index4 = index3 + sixth, el1 = index1, el2 = index2, el3 = index3, el4 = index4, el5 = index5, less = left + 1, great = right - 1, tmp = 0; if (compare(el1, el2, data_levels, data_points, data_ids, data_weights)) { tmp = el1; el1 = el2; el2 = tmp; } if (compare(el4, el5, data_levels, data_points, data_ids, data_weights)) { tmp = el4; el4 = el5; el5 = tmp; } if (compare(el1, el3, data_levels, data_points, data_ids, data_weights)) { tmp = el1; el1 = el3; el3 = tmp; } if (compare(el2, el3, data_levels, data_points, data_ids, data_weights)) { tmp = el2; el2 = el3; el3 = tmp; } if (compare(el1, el4, data_levels, data_points, data_ids, data_weights)) { tmp = el1; el1 = el4; el4 = tmp; } if (compare(el3, el4, data_levels, data_points, data_ids, data_weights)) { tmp = el3; el3 = el4; el4 = tmp; } if (compare(el2, el5, data_levels, data_points, data_ids, data_weights)) { tmp = el2; el2 = el5; el5 = tmp; } if (compare(el2, el3, data_levels, data_points, data_ids, data_weights)) { tmp = el2; el2 = el3; el3 = tmp; } if (compare(el4, el5, data_levels, data_points, data_ids, data_weights)) { tmp = el4; el4 = el5; el5 = tmp; } var pivot1_level = data_levels[el2]; var pivot1_x = data_points[2 * el2]; var pivot1_y = data_points[2 * el2 + 1]; var pivot1_id = data_ids[el2]; var pivot1_weight = data_weights[el2]; var pivot2_level = data_levels[el4]; var pivot2_x = data_points[2 * el4]; var pivot2_y = data_points[2 * el4 + 1]; var pivot2_id = data_ids[el4]; var pivot2_weight = data_weights[el4]; var ptr0 = el1; var ptr2 = el3; var ptr4 = el5; var ptr5 = index1; var ptr6 = index3; var ptr7 = index5; var level_x = data_levels[ptr0]; var level_y = data_levels[ptr2]; var level_z = data_levels[ptr4]; data_levels[ptr5] = level_x; data_levels[ptr6] = level_y; data_levels[ptr7] = level_z; for (var i1 = 0; i1 < 2; ++i1) { var x = data_points[2 * ptr0 + i1]; var y = data_points[2 * ptr2 + i1]; var z = data_points[2 * ptr4 + i1]; data_points[2 * ptr5 + i1] = x; data_points[2 * ptr6 + i1] = y; data_points[2 * ptr7 + i1] = z; } var id_x = data_ids[ptr0]; var id_y = data_ids[ptr2]; var id_z = data_ids[ptr4]; data_ids[ptr5] = id_x; data_ids[ptr6] = id_y; data_ids[ptr7] = id_z; var weight_x = data_weights[ptr0]; var weight_y = data_weights[ptr2]; var weight_z = data_weights[ptr4]; data_weights[ptr5] = weight_x; data_weights[ptr6] = weight_y; data_weights[ptr7] = weight_z; move(index2, left, data_levels, data_points, data_ids, data_weights); move(index4, right, data_levels, data_points, data_ids, data_weights); for (var k = less; k <= great; ++k) { if (comparePivot(k, pivot1_level, pivot1_x, pivot1_y, pivot1_id, data_levels, data_points, data_ids)) { if (k !== less) { swap(k, less, data_levels, data_points, data_ids, data_weights); } ++less; } else { if (!comparePivot(k, pivot2_level, pivot2_x, pivot2_y, pivot2_id, data_levels, data_points, data_ids)) { while (true) { if (!comparePivot(great, pivot2_level, pivot2_x, pivot2_y, pivot2_id, data_levels, data_points, data_ids)) { if (--great < k) { break; } continue; } else { if (comparePivot(great, pivot1_level, pivot1_x, pivot1_y, pivot1_id, data_levels, data_points, data_ids)) { rotate(k, less, great, data_levels, data_points, data_ids, data_weights); ++less; --great; } else { swap(k, great, data_levels, data_points, data_ids, data_weights); --great; } break; } } } } } shufflePivot(left, less - 1, pivot1_level, pivot1_x, pivot1_y, pivot1_id, pivot1_weight, data_levels, data_points, data_ids, data_weights); shufflePivot(right, great + 1, pivot2_level, pivot2_x, pivot2_y, pivot2_id, pivot2_weight, data_levels, data_points, data_ids, data_weights); if (less - 2 - left <= INSERT_SORT_CUTOFF) { insertionSort(left, less - 2, data_levels, data_points, data_ids, data_weights); } else { quickSort(left, less - 2, data_levels, data_points, data_ids, data_weights); } if (right - (great + 2) <= INSERT_SORT_CUTOFF) { insertionSort(great + 2, right, data_levels, data_points, data_ids, data_weights); } else { quickSort(great + 2, right, data_levels, data_points, data_ids, data_weights); } if (great - less <= INSERT_SORT_CUTOFF) { insertionSort(less, great, data_levels, data_points, data_ids, data_weights); } else { quickSort(less, great, data_levels, data_points, data_ids, data_weights); } } },{}],9:[function(require,module,exports){ 'use strict'; var pool = require('typedarray-pool'); var sortLevels = require('./lib/sort'); module.exports = snapPoints; function partition(points, ids, start, end, lox, loy, hix, hiy) { var mid = start; for (var i = start; i < end; ++i) { var x = points[2 * i]; var y = points[2 * i + 1]; var s = ids[i]; if (lox <= x && x <= hix && loy <= y && y <= hiy) { if (i === mid) { mid += 1; } else { points[2 * i] = points[2 * mid]; points[2 * i + 1] = points[2 * mid + 1]; ids[i] = ids[mid]; points[2 * mid] = x; points[2 * mid + 1] = y; ids[mid] = s; mid += 1; } } } return mid; } function SnapInterval(pixelSize, offset, count) { this.pixelSize = pixelSize; this.offset = offset; this.count = count; } function snapPoints(points, ids, weights, bounds) { var n = points.length >>> 1; if (n < 1) { return []; } var lox = Infinity, loy = Infinity; var hix = -Infinity, hiy = -Infinity; for (var i = 0; i < n; ++i) { var x = points[2 * i]; var y = points[2 * i + 1]; lox = Math.min(lox, x); hix = Math.max(hix, x); loy = Math.min(loy, y); hiy = Math.max(hiy, y); ids[i] = i; } if (lox === hix) { hix += 1 + Math.abs(hix); } if (loy === hiy) { hiy += 1 + Math.abs(hix); } //Calculate diameter var scaleX = 1.0 / (hix - lox); var scaleY = 1.0 / (hiy - loy); var diam = Math.max(hix - lox, hiy - loy); bounds = bounds || [0, 0, 0, 0]; bounds[0] = lox; bounds[1] = loy; bounds[2] = hix; bounds[3] = hiy; var levels = pool.mallocInt32(n); var ptr = 0; function snapRec(x, y, diam, start, end, level) { var diam_2 = diam * 0.5; var offset = start + 1; var count = end - start; weights[ptr] = count; levels[ptr++] = level; for (var i = 0; i < 2; ++i) { for (var j = 0; j < 2; ++j) { var nx = x + i * diam_2; var ny = y + j * diam_2; var nextOffset = partition(points, ids, offset, end, nx, ny, nx + diam_2, ny + diam_2); if (nextOffset === offset) { continue; } if (nextOffset - offset >= Math.max(0.9 * count, 32)) { var mid = end + start >>> 1; snapRec(nx, ny, diam_2, offset, mid, level + 1); offset = mid; } snapRec(nx, ny, diam_2, offset, nextOffset, level + 1); offset = nextOffset; } } } snapRec(lox, loy, diam, 0, n, 0); sortLevels(levels, points, ids, weights, n); var lod = []; var lastLevel = 0; var prevOffset = n; for (var ptr = n - 1; ptr >= 0; --ptr) { points[2 * ptr] = (points[2 * ptr] - lox) * scaleX; points[2 * ptr + 1] = (points[2 * ptr + 1] - loy) * scaleY; var level = levels[ptr]; if (level === lastLevel) { continue; } lod.push(new SnapInterval(diam * Math.pow(0.5, level), ptr + 1, prevOffset - (ptr + 1))); prevOffset = ptr + 1; lastLevel = level; } lod.push(new SnapInterval(diam * Math.pow(0.5, level + 1), 0, prevOffset)); pool.free(levels); return lod; } },{"./lib/sort":8,"typedarray-pool":10}],10:[function(require,module,exports){ (function (global,Buffer){ 'use strict'; var bits = require('bit-twiddle'); var dup = require('dup' //Legacy pool support );if (!global.__TYPEDARRAY_POOL) { global.__TYPEDARRAY_POOL = { UINT8: dup([32, 0]), UINT16: dup([32, 0]), UINT32: dup([32, 0]), INT8: dup([32, 0]), INT16: dup([32, 0]), INT32: dup([32, 0]), FLOAT: dup([32, 0]), DOUBLE: dup([32, 0]), DATA: dup([32, 0]), UINT8C: dup([32, 0]), BUFFER: dup([32, 0]) }; } var hasUint8C = typeof Uint8ClampedArray !== 'undefined'; var POOL = global.__TYPEDARRAY_POOL; //Upgrade pool if (!POOL.UINT8C) { POOL.UINT8C = dup([32, 0]); } if (!POOL.BUFFER) { POOL.BUFFER = dup([32, 0]); } //New technique: Only allocate from ArrayBufferView and Buffer var DATA = POOL.DATA, BUFFER = POOL.BUFFER; exports.free = function free(array) { if (Buffer.isBuffer(array)) { BUFFER[bits.log2(array.length)].push(array); } else { if (Object.prototype.toString.call(array) !== '[object ArrayBuffer]') { array = array.buffer; } if (!array) { return; } var n = array.length || array.byteLength; var log_n = bits.log2(n) | 0; DATA[log_n].push(array); } }; function freeArrayBuffer(buffer) { if (!buffer) { return; } var n = buffer.length || buffer.byteLength; var log_n = bits.log2(n); DATA[log_n].push(buffer); } function freeTypedArray(array) { freeArrayBuffer(array.buffer); } exports.freeUint8 = exports.freeUint16 = exports.freeUint32 = exports.freeInt8 = exports.freeInt16 = exports.freeInt32 = exports.freeFloat32 = exports.freeFloat = exports.freeFloat64 = exports.freeDouble = exports.freeUint8Clamped = exports.freeDataView = freeTypedArray; exports.freeArrayBuffer = freeArrayBuffer; exports.freeBuffer = function freeBuffer(array) { BUFFER[bits.log2(array.length)].push(array); }; exports.malloc = function malloc(n, dtype) { if (dtype === undefined || dtype === 'arraybuffer') { return mallocArrayBuffer(n); } else { switch (dtype) { case 'uint8': return mallocUint8(n); case 'uint16': return mallocUint16(n); case 'uint32': return mallocUint32(n); case 'int8': return mallocInt8(n); case 'int16': return mallocInt16(n); case 'int32': return mallocInt32(n); case 'float': case 'float32': return mallocFloat(n); case 'double': case 'float64': return mallocDouble(n); case 'uint8_clamped': return mallocUint8Clamped(n); case 'buffer': return mallocBuffer(n); case 'data': case 'dataview': return mallocDataView(n); default: return null; } } return null; }; function mallocArrayBuffer(n) { var n = bits.nextPow2(n); var log_n = bits.log2(n); var d = DATA[log_n]; if (d.length > 0) { return d.pop(); } return new ArrayBuffer(n); } exports.mallocArrayBuffer = mallocArrayBuffer; function mallocUint8(n) { return new Uint8Array(mallocArrayBuffer(n), 0, n); } exports.mallocUint8 = mallocUint8; function mallocUint16(n) { return new Uint16Array(mallocArrayBuffer(2 * n), 0, n); } exports.mallocUint16 = mallocUint16; function mallocUint32(n) { return new Uint32Array(mallocArrayBuffer(4 * n), 0, n); } exports.mallocUint32 = mallocUint32; function mallocInt8(n) { return new Int8Array(mallocArrayBuffer(n), 0, n); } exports.mallocInt8 = mallocInt8; function mallocInt16(n) { return new Int16Array(mallocArrayBuffer(2 * n), 0, n); } exports.mallocInt16 = mallocInt16; function mallocInt32(n) { return new Int32Array(mallocArrayBuffer(4 * n), 0, n); } exports.mallocInt32 = mallocInt32; function mallocFloat(n) { return new Float32Array(mallocArrayBuffer(4 * n), 0, n); } exports.mallocFloat32 = exports.mallocFloat = mallocFloat; function mallocDouble(n) { return new Float64Array(mallocArrayBuffer(8 * n), 0, n); } exports.mallocFloat64 = exports.mallocDouble = mallocDouble; function mallocUint8Clamped(n) { if (hasUint8C) { return new Uint8ClampedArray(mallocArrayBuffer(n), 0, n); } else { return mallocUint8(n); } } exports.mallocUint8Clamped = mallocUint8Clamped; function mallocDataView(n) { return new DataView(mallocArrayBuffer(n), 0, n); } exports.mallocDataView = mallocDataView; function mallocBuffer(n) { n = bits.nextPow2(n); var log_n = bits.log2(n); var cache = BUFFER[log_n]; if (cache.length > 0) { return cache.pop(); } return new Buffer(n); } exports.mallocBuffer = mallocBuffer; exports.clearCache = function clearCache() { for (var i = 0; i < 32; ++i) { POOL.UINT8[i].length = 0; POOL.UINT16[i].length = 0; POOL.UINT32[i].length = 0; POOL.INT8[i].length = 0; POOL.INT16[i].length = 0; POOL.INT32[i].length = 0; POOL.FLOAT[i].length = 0; POOL.DOUBLE[i].length = 0; POOL.UINT8C[i].length = 0; DATA[i].length = 0; BUFFER[i].length = 0; } }; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("buffer").Buffer) },{"bit-twiddle":3,"buffer":19,"dup":5}],11:[function(require,module,exports){ 'use strict'; const createRegl = require('regl'); const extend = require('object-assign'); const rgba = require('color-rgba'); const getBounds = require('array-bounds'); const clamp = require('clamp'); const atlas = require('font-atlas-sdf'); const colorId = require('color-id'); const snapPoints = require('snap-points-2d'); const clusterPoints = require('../point-cluster'); const normalize = require('array-normalize'); module.exports = Scatter; function Scatter(options) { if (!(this instanceof Scatter)) return new Scatter(options); // regl instance if (options.regl) this.regl = options.regl; // gl-plot2d case else if (options.plot) { this.plot = options.plot; this.regl = createRegl({ gl: this.plot.gl, pixelRatio: this.plot.pixelRatio }); this.plot.addObject(this); } // container/gl/canvas case else { this.regl = createRegl({ pixelRatio: options.pixelRatio || this.pixelRatio, gl: options.gl, container: options.container, canvas: options.canvas }); } // refs for compatibility this.gl = this.regl._gl; this.canvas = this.gl.canvas; this.container = this.canvas.parentNode; this.init(options); } //last positions raw data Scatter.prototype.positions = []; Scatter.prototype.pointCount = 0; //selected point indexes array Scatter.prototype.selection = null; //current viewport settings Scatter.prototype.scale = [1, 1]; Scatter.prototype.translate = [0, 0]; //TODO Scatter.prototype.viewBox = null; Scatter.prototype.dataBox = null; //point style options Scatter.prototype.size = 12; Scatter.prototype.color = [1, 0, 0, 1]; Scatter.prototype.borderSize = 1; Scatter.prototype.borderColor = [0, 0, 0, 1]; //gl settings Scatter.prototype.pixelRatio = window.devicePixelRatio; Scatter.prototype.gl = null; Scatter.prototype.container = null; Scatter.prototype.canvas = null; //group points for faster rendering of huge number of them Scatter.prototype.cluster = false; //font atlas texture singleton Scatter.prototype.charCanvas = document.createElement('canvas'); Scatter.prototype.charStep = 400; //create drawing methods based on initial options Scatter.prototype.init = function (options) { let regl = this.regl; this.bounds = [-Infinity, -Infinity, Infinity, Infinity]; //textures for glyphs and color palette this.charTexture = regl.texture(this.charCanvas //awesome buffers to reuse );this.sizeBuffer = regl.buffer({ usage: 'dynamic', type: 'float', data: null }); this.positionBuffer = regl.buffer({ usage: 'static', type: 'float', data: null }); this.colorBuffer = regl.buffer({ usage: 'dynamic', type: 'uint8', data: null }); this.update(options); this.drawPoints = regl({ vert: ` precision mediump float; attribute vec2 position; attribute float size; attribute vec4 color; uniform vec2 scale, translate; uniform float borderSize; varying vec4 fragColor; varying float centerFraction; void main() { gl_PointSize = size; gl_Position = vec4((position + translate) * scale * 2. - 1., 0, 1); // gl_Position.y *= -1.; centerFraction = borderSize == 0. ? 2. : size / (size + borderSize + 1.25); fragColor = color; }`, frag: ` precision mediump float; uniform vec4 borderColor; const float fragWeight = 1.0; varying vec4 fragColor; varying float centerFraction; float smoothStep(float x, float y) { return 1.0 / (1.0 + exp(50.0*(x - y))); } void main() { float radius = length(2.0*gl_PointCoord.xy-1.0); if(radius > 1.0) { discard; } vec4 baseColor = mix(borderColor, fragColor, smoothStep(radius, centerFraction)); float alpha = 1.0 - pow(1.0 - baseColor.a, fragWeight); gl_FragColor = vec4(baseColor.rgb * alpha, alpha); }`, uniforms: { scale: regl.this('scale'), translate: regl.this('translate'), borderColor: regl.this('borderColor'), borderSize: regl.this('borderSize') }, attributes: { position: this.positionBuffer, size: () => { if (Array.isArray(this.size)) { return this.sizeBuffer; } return { constant: this.size }; }, color: () => { if (Array.isArray(this.color[0])) { return this.colorBuffer; } return { constant: this.color }; } }, blend: { enable: true, equation: { rgb: 'add', alpha: 'add' }, func: { src: 'one', dst: 'one minus src alpha' } }, count: regl.this('pointCount'), // and same for the selection // elements: [0,1], primitive: 'points' } //debug run );this.drawTest = regl({ frag: ` precision mediump float; void main() { gl_FragColor = vec4(0, 1, 0, 1); }`, vert: ` precision mediump float; attribute vec2 position; void main() { gl_Position = vec4(position, 0, 1); }`, attributes: { position: [[-1, -1], [1, 0], [0, 1]] }, count: 3 }); return this; }; Scatter.prototype.update = function (options) { let regl = this.regl, w = this.canvas.width, h = this.canvas.height; if (options.length != null) options = { positions: options }; let { positions, selection, scale, translate, size, color, borderSize, borderColor, glyph, pixelRatio, viewBox, dataBox, cluster } = options; if (cluster != null) this.cluster = cluster; //make sure scale/translate are properly set if (translate != null) { this.translate = typeof translate === 'number' ? [translate, translate] : translate; } if (scale != null) { this.scale = typeof scale === 'number' ? [scale, scale] : scale; this.scale[0] = Math.max(this.scale[0], 1e-10); this.scale[1] = Math.max(this.scale[1], 1e-10); } //update buffer if (positions != null) { if (this.cluster) { //do clustering //TODO: send clustering to worker this.getPoints = clusterPoints(positions); } else { this.positionBuffer(positions); this.pointCount = Math.floor(positions.length / 2); } this.positions = positions; //update bounds this.bounds = getBounds(positions, 2); } //sizes if (size != null) { this.size = size; if (Array.isArray(this.size)) { this.sizeBuffer(this.size); } } if (borderSize != null) this.borderSize = borderSize; //reobtain points in case if translate/scale/positions changed if (scale != null || positions != null) { //recalc bounds for the data if (this.cluster) { //TODO: read actual point radius/size here let radius = (this.size[0] || this.size) / Math.max(w, h) / this.scale[0]; let ids = this.getPoints(radius); let subpositions = new Float32Array(ids.length * 2); for (let i = 0, id; i < ids.length; i++) { let id = ids[i]; subpositions[i * 2] = this.positions[id * 2]; subpositions[i * 2 + 1] = this.positions[id * 2 + 1]; } this.positionBuffer(subpositions); this.pointCount = Math.floor(subpositions.length / 2); } } //process colors if (color != null) { //ensure colors are arrays if (Array.isArray(color) && (Array.isArray(color[0]) || typeof color[0] === 'string')) { for (let i = 0, l = this.positions.length / 2; i < l; i++) { if (color[i] != null) { color[i] = rgba(color[i]); } else { color[i] = Scatter.prototype.color; } } this.colorBuffer(color); } else if (typeof color === 'string') { color = rgba(color); } this.color = color; } if (borderColor != null) { this.borderColor = borderColor; } //aggregate glyphs if (glyph != null) {} // var glyphChars = {} // for (var i = 0, l = this.pointCount, k = 0; i < l; i++) { // var char = glyphs[i] // if (glyphChars[char] == null) { // glyphChars[char] = k++ // } // } //update atlas /* var maxSize = 0 for (var i = 0, l = sizes.length; i < l; ++i) { if (sizes[i] > maxSize) maxSize = sizes[i] } var oldStep = this.charStep this.charStep = clamp(Math.ceil(maxSize*4), 128, 768) var chars = Object.keys(glyphChars) var step = this.charStep var charSize = Math.floor(step / 2) var maxW = gl.getParameter(gl.MAX_TEXTURE_SIZE) var maxChars = (maxW / step) * (maxW / step) var atlasW = Math.min(maxW, step*chars.length) var atlasH = Math.min(maxW, step*Math.ceil(step*chars.length/maxW)) var cols = Math.floor(atlasW / step) if (chars.length > maxChars) { console.warn('gl-scatter2d-fancy: number of characters is more than maximum texture size. Try reducing it.') } //do not overupdate atlas if (!this.chars || (this.chars+'' !== chars+'') || this.charStep != oldStep) { this.charCanvas = atlas({ canvas: this.charCanvas, family: 'sans-serif', size: charSize, shape: [atlasW, atlasH], step: [step, step], chars: chars, align: true }) this.chars = chars } */ return this; }; // Then we assign regl commands directly to the prototype of the class Scatter.prototype.draw = function () { //TODO: make multipass-render here //handle gl-plot2d case //FIXME: get rid of that once regl-plot2d is available if (this.plot) { let bounds = this.bounds; let dataBox = this.plot.dataBox; let viewBox = this.plot.viewBox; //hack to support gl-plot2d this.regl._refresh(); this.gl.scissor(viewBox[0], viewBox[1], viewBox[2] - viewBox[0], viewBox[3] - viewBox[1]); this.gl.viewport(viewBox[0], viewBox[1], viewBox[2] - viewBox[0], viewBox[3] - viewBox[1]); this.scale[0] = 1 / (dataBox[2] - dataBox[0]); this.scale[1] = 1 / (dataBox[3] - dataBox[1]); this.translate[0] = -dataBox[0]; this.translate[1] = -dataBox[1]; } this.drawPoints // this.drawTest() ();return this.pointCount; }; // adjust scale and transform so to see all the data Scatter.prototype.autorange = function (positions) { if (!positions) positions = this.positions; if (!positions || positions.length == 0) return this; let bounds = this.bounds; let scale = [1 / (bounds[2] - bounds[0]), 1 / (bounds[3] - bounds[1])]; this.update({ scale: scale, translate: [-bounds[0], -bounds[1]] }); return this; }; Scatter.prototype.clear = function () { this.regl.clear({ color: [1, 1, 1, 1], depth: 1, stencil: 0 }); return this; }; Scatter.prototype.pick = function (x, y, value) { // return this.draw() return null; }; Scatter.prototype.drawPick = function () { return this.pointCount; }; Scatter.prototype.dispose = function () { this.charTexture.destroy(); this.sizeBuffer.destroy(); this.positionBuffer.destroy(); if (this.plot) this.plot.removeObject(this); return this; }; Scatter.prototype.select = function () { //TODO: init regl draw here return this; }; //TODO: move to a separate quadtree-based package function createCluster(points) { let scales = snapPoints(points, [], [], []); return function getPoints(pixelSize, bounds) { for (var scaleNum = scales.length - 1; scaleNum >= 0; scaleNum--) { var lod = scales[scaleNum]; if (lod.pixelSize < pixelSize && scaleNum > 1) continue; var range = this.getVisibleRange(lod); var startOffset = range[0], endOffset = range[1]; if (endOffset > startOffset) gl.drawArrays(gl.POINTS, startOffset, endOffset - startOffset); if (!pick && firstLevel) { firstLevel = false; shader.uniforms.useWeight = 0; } } }; } },{"../point-cluster":1,"array-bounds":13,"array-normalize":14,"clamp":23,"color-id":24,"color-rgba":27,"font-atlas-sdf":40,"object-assign":74,"regl":83,"snap-points-2d":1