regl-scatter2d
Version:
Scatter2d plot built with regl
1,838 lines (1,541 loc) • 672 kB
HTML
<!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 <df.creative@gmail.com>">
<meta name="twitter:creator" content="Dima Yv <df.creative@gmail.com>">
<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 <df.creative@gmail.com>">
<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