Skip to content

Instantly share code, notes, and snippets.

@dancingfrog
Forked from pbeshai/.block
Last active January 5, 2019 15:14
Show Gist options
  • Save dancingfrog/4fd1f486af6f26c7a3487cbf114b1636 to your computer and use it in GitHub Desktop.
Save dancingfrog/4fd1f486af6f26c7a3487cbf114b1636 to your computer and use it in GitHub Desktop.
Animate 100,000 points with regl - II
license: mit
height: 720
border: no
/**
* Given a set of points, lay them out in a phyllotaxis layout.
* Mutates the `points` passed in by updating the x and y values.
*
* @param {Object[]} points The array of points to update. Will get `x` and `y` set.
* @param {Number} pointWidth The size in pixels of the point's width. Should also include margin.
* @param {Number} xOffset The x offset to apply to all points
* @param {Number} yOffset The y offset to apply to all points
*
* @return {Object[]} points with modified x and y
*/
function phyllotaxisLayout(points, pointWidth, xOffset = 0, yOffset = 0, iOffset = 0) {
// theta determines the spiral of the layout
const theta = Math.PI * (3 - Math.sqrt(5));
const pointRadius = pointWidth / 2;
points.forEach((point, i) => {
const index = (i + iOffset) % points.length;
const phylloX = pointRadius * Math.sqrt(index) * Math.cos(index * theta);
const phylloY = pointRadius * Math.sqrt(index) * Math.sin(index * theta);
point.x = xOffset + phylloX - pointRadius;
point.y = yOffset + phylloY - pointRadius;
});
return points;
}
/**
* Given a set of points, lay them out in a grid.
* Mutates the `points` passed in by updating the x and y values.
*
* @param {Object[]} points The array of points to update. Will get `x` and `y` set.
* @param {Number} pointWidth The size in pixels of the point's width. Should also include margin.
* @param {Number} gridWidth The width of the grid of points
*
* @return {Object[]} points with modified x and y
*/
function gridLayout(points, pointWidth, gridWidth) {
const pointHeight = pointWidth;
const pointsPerRow = Math.floor(gridWidth / pointWidth);
const numRows = points.length / pointsPerRow;
points.forEach((point, i) => {
point.x = pointWidth * (i % pointsPerRow);
point.y = pointHeight * Math.floor(i / pointsPerRow);
});
return points;
}
/**
* Given a set of points, lay them out randomly.
* Mutates the `points` passed in by updating the x and y values.
*
* @param {Object[]} points The array of points to update. Will get `x` and `y` set.
* @param {Number} pointWidth The size in pixels of the point's width. Should also include margin.
* @param {Number} width The width of the area to place them in
* @param {Number} height The height of the area to place them in
*
* @return {Object[]} points with modified x and y
*/
function randomLayout(points, pointWidth, width, height) {
points.forEach((point, i) => {
point.x = Math.random() * (width - pointWidth);
point.y = Math.random() * (height - pointWidth);
});
return points;
}
/**
* Given a set of points, lay them out in a sine wave.
* Mutates the `points` passed in by updating the x and y values.
*
* @param {Object[]} points The array of points to update. Will get `x` and `y` set.
* @param {Number} pointWidth The size in pixels of the point's width. Should also include margin.
* @param {Number} width The width of the area to place them in
* @param {Number} height The height of the area to place them in
*
* @return {Object[]} points with modified x and y
*/
function sineLayout(points, pointWidth, width, height) {
const amplitude = 0.3 * (height / 2);
const yOffset = height / 2;
const periods = 3;
const yScale = d3.scaleLinear()
.domain([0, points.length - 1])
.range([0, periods * 2 * Math.PI]);
points.forEach((point, i) => {
point.x = (i / points.length) * (width - pointWidth);
point.y = amplitude * Math.sin(yScale(i)) + yOffset;
});
return points;
}
/**
* Given a set of points, lay them out in a spiral.
* Mutates the `points` passed in by updating the x and y values.
*
* @param {Object[]} points The array of points to update. Will get `x` and `y` set.
* @param {Number} pointWidth The size in pixels of the point's width. Should also include margin.
* @param {Number} width The width of the area to place them in
* @param {Number} height The height of the area to place them in
*
* @return {Object[]} points with modified x and y
*/
function spiralLayout(points, pointWidth, width, height) {
const amplitude = 0.3 * (height / 2);
const xOffset = width / 2;
const yOffset = height / 2;
const periods = 20;
const rScale = d3.scaleLinear()
.domain([0, points.length -1])
.range([0, Math.min(width / 2, height / 2) - pointWidth]);
const thetaScale = d3.scaleLinear()
.domain([0, points.length - 1])
.range([0, periods * 2 * Math.PI]);
points.forEach((point, i) => {
point.x = rScale(i) * Math.cos(thetaScale(i)) + xOffset
point.y = rScale(i) * Math.sin(thetaScale(i)) + yOffset;
});
return points;
}
/**
* Generate an object array of `numPoints` length with unique IDs
* and assigned colors
*/
function createPoints(numPoints, pointWidth, width, height) {
const colorScale = d3.scaleSequential(d3.interpolateViridis)
.domain([numPoints - 1, 0]);
const points = d3.range(numPoints).map(id => ({
id,
color: colorScale(id),
}));
return randomLayout(points, pointWidth, width, height);
}
var regl=createREGL({onDone:function (err, regl) {console.log('Initialization of regl complete\n', regl);main(err, regl);}});function main(t,n){function e(t){return function(n){var e=d3.rgb(t(1-n));return[e.r/255,e.g/255,e.b/255]}}function o(t){var e=n({frag:"\n\t\t\t// set the precision of floating point numbers\n\t\t precision highp float;\n\n\t\t // this value is populated by the vertex shader\n\t\t\tvarying vec3 fragColor;\n\n\t\t\tvoid main() {\n\t\t\t\t// gl_FragColor is a special variable that holds the color of a pixel\n\t\t\t\tgl_FragColor = vec4(fragColor, 1);\n\t\t\t}\n\t\t\t",vert:"\n\t\t\t// per vertex attributes\n\t\t\tattribute vec2 positionStart;\n\t\t\tattribute vec2 positionEnd;\n\t\t\tattribute float index;\n\t\t\tattribute vec3 colorStart;\n\t\t\tattribute vec3 colorEnd;\n\n\t\t\t// variables to send to the fragment shader\n\t\t\tvarying vec3 fragColor;\n\n\t\t\t// values that are the same for all vertices\n\t\t\tuniform float pointWidth;\n\t\t\tuniform float stageWidth;\n\t\t\tuniform float stageHeight;\n\t\t\tuniform float elapsed;\n\t\t\tuniform float duration;\n\t\t\tuniform float delayByIndex;\n\n\t\t\t// helper function to transform from pixel space to normalized device coordinates (NDC)\n\t\t\t// in NDC (0,0) is the middle, (-1, -1) is the top left and (1, 1) is the bottom right.\n\t\t\tvec2 normalizeCoords(vec2 position) {\n\t\t\t\t// read in the positions into x and y vars\n\t float x = position[0];\n\t float y = position[1];\n\n\t\t\t\treturn vec2(\n\t\t 2.0 * ((x / stageWidth) - 0.5),\n\t\t // invert y since we think [0,0] is bottom left in pixel space\n\t\t -(2.0 * ((y / stageHeight) - 0.5)));\n\t\t\t}\n\n\t\t\t// helper function to handle cubic easing (copied from d3 for consistency)\n\t\t\t// note there are pre-made easing functions available via glslify.\n\t\t\tfloat easeCubicInOut(float t) {\n\t\t\t\tt *= 2.0;\n t = (t <= 1.0 ? t * t * t : (t -= 2.0) * t * t + 2.0) / 2.0;\n\n if (t > 1.0) {\n t = 1.0;\n }\n\n return t;\n\t\t\t}\n\n\t\t\tvoid main() {\n\t\t\t\t// update the size of a point based on the prop pointWidth\n\t\t\t\tgl_PointSize = pointWidth;\n\n\t\t\t\tfloat delay = delayByIndex * index;\n\n\t\t\t\t// number between 0 and 1 indicating how far through the animation this\n\t\t\t\t// vertex is.\n\t float t;\n\n\t // drawing without animation, so show end state immediately\n\t if (duration == 0.0) {\n\t t = 1.0;\n\n\t // still delaying before animating\n\t } else if (elapsed < delay) {\n\t t = 0.0;\n\n\t // otherwise we are animating, so use cubic easing\n\t } else {\n\t t = easeCubicInOut((elapsed - delay) / duration);\n\t }\n\n\t\t\t\t// interpolate position\n\t vec2 position = mix(positionStart, positionEnd, t);\n\n\t // interpolate and send color to the fragment shader\n\t fragColor = mix(colorStart, colorEnd, t);\n\n\t\t\t\t// scale to normalized device coordinates\n\t\t\t\t// gl_Position is a special variable that holds the position of a vertex\n\t gl_Position = vec4(normalizeCoords(position), 0.0, 1.0);\n\t\t\t}\n\t\t\t",attributes:{positionStart:t.map(function(t){return[t.sx,t.sy]}),positionEnd:t.map(function(t){return[t.tx,t.ty]}),colorStart:t.map(function(t){return t.colorStart}),colorEnd:t.map(function(t){return t.colorEnd}),index:d3.range(t.length)},uniforms:{pointWidth:n.prop("pointWidth"),stageWidth:n.prop("stageWidth"),stageHeight:n.prop("stageHeight"),delayByIndex:n.prop("delayByIndex"),duration:n.prop("duration"),elapsed:function(t,n){var e=t.time,o=n.startTime;return void 0===o&&(o=0),1e3*(e-o)}},count:t.length,primitive:"points"});return e}function i(t,e){console.log("animating with new layout"),e.forEach(function(t){t.sx=t.tx,t.sy=t.ty,t.colorStart=t.colorEnd}),t(e);var a=b[w];e.forEach(function(t,n){t.tx=t.x,t.ty=t.y,t.colorEnd=a(n/e.length)});var l=o(e),f=n.frame(function(t){var o=t.time;null===x&&(x=o),n.clear({color:[0,0,0,1],depth:1}),l({pointWidth:r,stageWidth:s,stageHeight:d,duration:c,delayByIndex:u,startTime:x}),o-x>p/1e3&&(console.log("done animating, moving to next layout"),f.cancel(),y=(y+1)%v.length,x=null,w=(w+1)%b.length,i(v[y],e))})}var a=1e5,r=4,l=1,s=window.innerWidth,d=window.innerHeight,c=1500,u=500/a,p=c+u*a,f=function(t){return phyllotaxisLayout(t,r+l,s/2,d/2)},h=function(t){return gridLayout(t,r+l,s)},g=function(t){return sineLayout(t,r+l,s,d)},m=function(t){return spiralLayout(t,r+l,s,d)},v=[f,m,h,g],y=0,x=null,b=[d3.scaleSequential(d3.interpolateInferno),d3.scaleSequential(d3.interpolateMagma),d3.scaleSequential(d3.interpolateCool),d3.scaleSequential(d3.interpolateViridis)].map(e),w=0,C=createPoints(a,r,s,d);C.forEach(function(t,n){t.tx=s/2,t.ty=d/2,t.colorEnd=[0,0,0]}),i(v[y],C)}
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNjcmlwdC5qcyJdLCJuYW1lcyI6WyJtYWluIiwiZXJyIiwicmVnbCIsIndyYXBDb2xvclNjYWxlIiwic2NhbGUiLCJ0IiwiY29uc3QiLCJyZ2IiLCJkMyIsInIiLCJnIiwiYiIsImNyZWF0ZURyYXdQb2ludHMiLCJwb2ludHMiLCJkcmF3UG9pbnRzIiwiZnJhZyIsInZlcnQiLCJhdHRyaWJ1dGVzIiwicG9zaXRpb25TdGFydCIsIm1hcCIsImQiLCJzeCIsInN5IiwicG9zaXRpb25FbmQiLCJ0eCIsInR5IiwiY29sb3JTdGFydCIsImNvbG9yRW5kIiwiaW5kZXgiLCJyYW5nZSIsImxlbmd0aCIsInVuaWZvcm1zIiwicG9pbnRXaWR0aCIsInByb3AiLCJzdGFnZVdpZHRoIiwic3RhZ2VIZWlnaHQiLCJkZWxheUJ5SW5kZXgiLCJkdXJhdGlvbiIsImVsYXBzZWQiLCJyZWYiLCJyZWYkMSIsInRpbWUiLCJzdGFydFRpbWUiLCJjb3VudCIsInByaW1pdGl2ZSIsImFuaW1hdGUiLCJsYXlvdXQiLCJjb25zb2xlIiwibG9nIiwiZm9yRWFjaCIsImNvbG9yU2NhbGUiLCJjb2xvclNjYWxlcyIsImN1cnJlbnRDb2xvclNjYWxlIiwiaSIsIngiLCJ5IiwiZnJhbWVMb29wIiwiZnJhbWUiLCJjbGVhciIsImNvbG9yIiwiZGVwdGgiLCJ3aWR0aCIsImhlaWdodCIsIm1heER1cmF0aW9uIiwiY2FuY2VsIiwiY3VycmVudExheW91dCIsImxheW91dHMiLCJudW1Qb2ludHMiLCJwb2ludE1hcmdpbiIsIndpbmRvdyIsImlubmVyV2lkdGgiLCJpbm5lckhlaWdodCIsInRvUGh5bGxvdGF4aXMiLCJwaHlsbG90YXhpc0xheW91dCIsInRvR3JpZCIsImdyaWRMYXlvdXQiLCJ0b1NpbmUiLCJzaW5lTGF5b3V0IiwidG9TcGlyYWwiLCJzcGlyYWxMYXlvdXQiLCJzY2FsZVNlcXVlbnRpYWwiLCJpbnRlcnBvbGF0ZUluZmVybm8iLCJpbnRlcnBvbGF0ZU1hZ21hIiwiaW50ZXJwb2xh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Z0UsWUFBUWhFLEVBQUdtQixFQUFtQm9DLEVBQWFQLEVBQUFDLElBQ3JEZ0IsRUFBVyxTQUFBakUsR0FBQyxNQUFBa0UsY0FBV2xFLEVBQUFtQixFQUFxQm9DLEVBQWFQLEVBQUFDLElBR3pESSxHQUFXTSxFQUFlTSxFQUFVSixFQUFRRSxHQUM5Q1gsRUFBZ0IsRUFDaEJ2QixFQUFZLEtBV1ZTLEdBQ0wzQyxHQUFHd0UsZ0JBQWdCeEUsR0FBR3lFLG9CQUN0QnpFLEdBQUd3RSxnQkFBZ0J4RSxHQUFHMEUsa0JBQ3RCMUUsR0FBR3dFLGdCQUFnQnhFLEdBQUcyRSxpQkFDdEIzRSxHQUFHd0UsZ0JBQWdCeEUsR0FBRzRFLHFCQUNyQmpFLElBQUloQixHQUNGaUQsRUFBb0IsRUEwTGxCdkMsRUFBU3dFLGFBQWFsQixFQUFXbkMsRUFBWTZCLEVBQU9DLEVBRzFEakQsR0FBT29DLFFBQVEsU0FBQzdCLEVBQUdpQyxHQUNsQmpDLEVBQUVJLEdBQUtxQyxFQUFRLEVBQ2Z6QyxFQUFFSyxHQUFLcUMsRUFBUyxFQUNoQjFDLEVBQUVPLFVBQVksRUFBRyxFQUFHLEtBR3JCa0IsRUFBUXFCLEVBQVFELEdBQWdCcEQsR0FLakNYLE1BRUVvRixPQUFRdEYiLCJmaWxlIjoic2NyaXB0LmpzIiwic291cmNlc0NvbnRlbnQiOlsiZnVuY3Rpb24gbWFpbihlcnIsIHJlZ2wpIHtcblx0Y29uc3QgbnVtUG9pbnRzID0gMTAwMDAwO1xuXHRjb25zdCBwb2ludFdpZHRoID0gNDtcblx0Y29uc3QgcG9pbnRNYXJnaW4gPSAxO1xuXHRjb25zdCB3aWR0aCA9IHdpbmRvdy5pbm5lcldpZHRoO1xuXHRjb25zdCBoZWlnaHQgPSB3aW5kb3cuaW5uZXJIZWlnaHQ7XG5cblx0Ly8gZHVyYXRpb24gb2YgdGhlIGFuaW1hdGlvbiBpZ25vcmluZyBkZWxheXNcblx0Y29uc3QgZHVyYXRpb24gPSAxNTAwO1xuXG5cdC8vIG11bHRpcGx5IHRoaXMgdmFsdWUgYnkgdGhlIGluZGV4IG9mIGEgcG9pbnQgdG8gZ2V0IGl0cyBkZWxheVxuXHRjb25zdCBkZWxheUJ5SW5kZXggPSA1MDAgLyBudW1Qb2ludHM7XG5cblx0Ly8gaW5jbHVkZSBtYXggZGVsYXkgaW4gaGVyZVxuXHRjb25zdCBtYXhEdXJhdGlvbiA9IGR1cmF0aW9uICsgZGVsYXlCeUluZGV4ICogbnVtUG9pbnRzO1xuXG5cdC8vIGNyZWF0ZSBoZWxwZXJzIHRoYXQgd2lsbCBsYXlvdXQgdGhlIHBvaW50cyBpbiBkaWZmZXJlbnQgd2F5cyAoc2VlIGNvbW1vbi5qcylcblx0Y29uc3QgdG9QaHlsbG90YXhpcyA9IChwb2ludHMpID0+IHBoeWxsb3RheGlzTGF5b3V0KHBvaW50cywgcG9pbnRXaWR0aCArIHBvaW50TWFyZ2luLCB3aWR0aCAvIDIsIGhlaWdodCAvIDIpO1xuXHRjb25zdCB0b0dyaWQgPSAocG9pbnRzKSA9PiBncmlkTGF5b3V0KHBvaW50cywgcG9pbnRXaWR0aCArIHBvaW50TWFyZ2luLCB3aWR0aCk7XG5cdGNvbnN0IHRvU2luZSA9IChwb2ludHMpID0+IHNpbmVMYXlvdXQocG9pbnRzLCBwb2ludFdpZHRoICsgcG9pbnRNYXJnaW4sIHdpZHRoLCBoZWlnaHQpO1xuXHRjb25zdCB0b1NwaXJhbCA9IChwb2ludHMpID0+IHNwaXJhbExheW91dChwb2ludHMsIHBvaW50V2lkdGggKyBwb2ludE1hcmdpbiwgd2lkdGgsIGhlaWdodCk7XG5cblx0Ly8gc2V0IHRoZSBvcmRlciBvZiB0aGUgbGF5b3V0cyBhbmQgc29tZSBpbml0aWFsIGFuaW1hdGlvbiBzdGF0ZVxuXHRjb25zdCBsYXlvdXRzID0gW3RvUGh5bGxvdGF4aXMsIHRvU3BpcmFsLCB0b0dyaWQsIHRvU2luZV07XG5cdGxldCBjdXJyZW50TGF5b3V0ID0gMDtcblx0bGV0IHN0YXJ0VGltZSA9IG51bGw7IC8vIGluIHNlY29uZHNcblxuXHQvLyB3cmFwIGQzIGNvbG9yIHNjYWxlcyBzbyB0aGV5IHByb2R1Y2UgdmVjM3Mgd2l0aCB2YWx1ZXMgMC0xXG5cdGZ1bmN0aW9uIHdyYXBDb2xvclNjYWxlKHNjYWxlKSB7XG5cdFx0cmV0dXJuIHQgPT4ge1xuXHRcdFx0Y29uc3QgcmdiID0gZDMucmdiKHNjYWxlKDEgLSB0KSk7XG5cdFx0XHRyZXR1cm4gW3JnYi5yIC8gMjU1LCByZ2IuZyAvIDI1NSwgcmdiLmIgLyAyNTVdO1xuXHRcdH07XG5cdH1cblxuXHQvLyB0aGUgb3JkZXIgb2YgY29sb3Igc2NhbGVzIHRvIGxvb3AgdGhyb3VnaFxuXHRjb25zdCBjb2xvclNjYWxlcyA9IFtcblx0XHRkMy5zY2FsZVNlcXVlbnRpYWwoZDMuaW50ZXJwb2xhdGVJbmZlcm5vKSxcblx0XHRkMy5zY2FsZVNlcXVlbnRpYWwoZDMuaW50ZXJwb2xhdGVNYWdtYSksXG5cdFx0ZDMuc2NhbGVTZXF1ZW50aWFsKGQzLmludGVycG9sYXRlQ29vbCksXG5cdFx0ZDMuc2NhbGVTZXF1ZW50aWFsKGQzLmludGVycG9sYXRlVmlyaWRpcyksXG5cdF0ubWFwKHdyYXBDb2xvclNjYWxlKTtcblx0bGV0IGN1cnJlbnRDb2xvclNjYWxlID0gMDtcblxuXHQvLyBmdW5jdGlvbiB0byBjb21waWxlIGEgZHJhdyBwb2ludHMgcmVnbCBmdW5jXG5cdGZ1bmN0aW9uIGNyZWF0ZURyYXdQb2ludHMocG9pbnRzKSB7XG5cdFx0Y29uc3QgZHJhd1BvaW50cyA9IHJlZ2woe1xuXHRcdFx0ZnJhZzogYFxuXHRcdFx0Ly8gc2V0IHRoZSBwcmVjaXNpb24gb2YgZmxvYXRpbmcgcG9pbnQgbnVtYmVyc1xuXHRcdCAgcHJlY2lzaW9uIGhpZ2hwIGZsb2F0O1xuXG5cdFx0ICAvLyB0aGlzIHZhbHVlIGlzIHBvcHVsYXRlZCBieSB0aGUgdmVydGV4IHNoYWRlclxuXHRcdFx0dmFyeWluZyB2ZWMzIGZyYWdDb2xvcjtcblxuXHRcdFx0dm9pZCBtYWluKCkge1xuXHRcdFx0XHQvLyBnbF9GcmFnQ29sb3IgaXMgYSBzcGVjaWFsIHZhcmlhYmxlIHRoYXQgaG9sZHMgdGhlIGNvbG9yIG9mIGEgcGl4ZWxcblx0XHRcdFx0Z2xfRnJhZ0NvbG9yID0gdmVjNChmcmFnQ29sb3IsIDEpO1xuXHRcdFx0fVxuXHRcdFx0YCxcblxuXHRcdFx0dmVydDogYFxuXHRcdFx0Ly8gcGVyIHZlcnRleCBhdHRyaWJ1dGVzXG5cdFx0XHRhdHRyaWJ1dGUgdmVjMiBwb3NpdGlvblN0YXJ0O1xuXHRcdFx0YXR0cmlidXRlIHZlYzIgcG9zaXRpb25FbmQ7XG5cdFx0XHRhdHRyaWJ1dGUgZmxvYXQgaW5kZXg7XG5cdFx0XHRhdHRyaWJ1dGUgdmVjMyBjb2xvclN0YXJ0O1xuXHRcdFx0YXR0cmlidXRlIHZlYzMgY29sb3JFbmQ7XG5cblx0XHRcdC8vIHZhcmlhYmxlcyB0byBzZW5kIHRvIHRoZSBmcmFnbWVudCBzaGFkZXJcblx0XHRcdHZhcnlpbmcgdmVjMyBmcmFnQ29sb3I7XG5cblx0XHRcdC8vIHZhbHVlcyB0aGF0IGFyZSB0aGUgc2FtZSBmb3IgYWxsIHZlcnRpY2VzXG5cdFx0XHR1bmlmb3JtIGZsb2F0IHBvaW50V2lkdGg7XG5cdFx0XHR1bmlmb3JtIGZsb2F0IHN0YWdlV2lkdGg7XG5cdFx0XHR1bmlmb3JtIGZsb2F0IHN0YWdlSGVpZ2h0O1xuXHRcdFx0dW5pZm9ybSBmbG9hdCBlbGFwc2VkO1xuXHRcdFx0dW5pZm9ybSBmbG9hdCBkdXJhdGlvbjtcblx0XHRcdHVuaWZvcm0gZmxvYXQgZGVsYXlCeUluZGV4O1xuXG5cdFx0XHQvLyBoZWxwZXIgZnVuY3Rpb24gdG8gdHJhbnNmb3JtIGZyb20gcGl4ZWwgc3BhY2UgdG8gbm9ybWFsaXplZCBkZXZpY2UgY29vcmRpbmF0ZXMgKE5EQylcblx0XHRcdC8vIGluIE5EQyAoMCwwKSBpcyB0aGUgbWlkZGxlLCAoLTEsIC0xKSBpcyB0aGUgdG9wIGxlZnQgYW5kICgxLCAxKSBpcyB0aGUgYm90dG9tIHJpZ2h0LlxuXHRcdFx0dmVjMiBub3JtYWxpemVDb29yZHModmVjMiBwb3NpdGlvbikge1xuXHRcdFx0XHQvLyByZWFkIGluIHRoZSBwb3NpdGlvbnMgaW50byB4IGFuZCB5IHZhcnNcblx0ICAgICAgZmxvYXQgeCA9IHBvc2l0aW9uWzBdO1xuXHQgICAgICBmbG9hdCB5ID0gcG9zaXRpb25bMV07XG5cblx0XHRcdFx0cmV0dXJuIHZlYzIoXG5cdFx0ICAgICAgMi4wICogKCh4IC8gc3RhZ2VXaWR0aCkgLSAwLjUpLFxuXHRcdCAgICAgIC8vIGludmVydCB5IHNpbmNlIHdlIHRoaW5rIFswLDBdIGlzIGJvdHRvbSBsZWZ0IGluIHBpeGVsIHNwYWNlXG5cdFx0ICAgICAgLSgyLjAgKiAoKHkgLyBzdGFnZUhlaWdodCkgLSAwLjUpKSk7XG5cdFx0XHR9XG5cblx0XHRcdC8vIGhlbHBlciBmdW5jdGlvbiB0byBoYW5kbGUgY3ViaWMgZWFzaW5nIChjb3BpZWQgZnJvbSBkMyBmb3IgY29uc2lzdGVuY3kpXG5cdFx0XHQvLyBub3RlIHRoZXJlIGFyZSBwcmUtbWFkZSBlYXNpbmcgZnVuY3Rpb25zIGF2YWlsYWJsZSB2aWEgZ2xzbGlmeS5cblx0XHRcdGZsb2F0IGVhc2VDdWJpY0luT3V0KGZsb2F0IHQpIHtcblx0XHRcdFx0dCAqPSAyLjA7XG4gICAgICAgIHQgPSAodCA8PSAxLjAgPyB0ICogdCAqIHQgOiAodCAtPSAyLjApICogdCAqIHQgKyAyLjApIC8gMi4wO1xuXG4gICAgICAgIGlmICh0ID4gMS4wKSB7XG4gICAgICAgICAgdCA9IDEuMDtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0O1xuXHRcdFx0fVxuXG5cdFx0XHR2b2lkIG1haW4oKSB7XG5cdFx0XHRcdC8vIHVwZGF0ZSB0aGUgc2l6ZSBvZiBhIHBvaW50IGJhc2VkIG9uIHRoZSBwcm9wIHBvaW50V2lkdGhcblx0XHRcdFx0Z2xfUG9pbnRTaXplID0gcG9pbnRXaWR0aDtcblxuXHRcdFx0XHRmbG9hdCBkZWxheSA9IGRlbGF5QnlJbmRleCAqIGluZGV4O1xuXG5cdFx0XHRcdC8vIG51bWJlciBiZXR3ZWVuIDAgYW5kIDEgaW5kaWNhdGluZyBob3cgZmFyIHRocm91Z2ggdGhlIGFuaW1hdGlvbiB0aGlzXG5cdFx0XHRcdC8vIHZlcnRleCBpcy5cblx0ICAgICAgZmxvYXQgdDtcblxuXHQgICAgICAvLyBkcmF3aW5nIHdpdGhvdXQgYW5pbWF0aW9uLCBzbyBzaG93IGVuZCBzdGF0ZSBpbW1lZGlhdGVseVxuXHQgICAgICBpZiAoZHVyYXRpb24gPT0gMC4wKSB7XG5cdCAgICAgICAgdCA9IDEuMDtcblxuXHQgICAgICAvLyBzdGlsbCBkZWxheWluZyBiZWZvcmUgYW5pbWF0aW5nXG5cdCAgICAgIH0gZWxzZSBpZiAoZWxhcHNlZCA8IGRlbGF5KSB7XG5cdCAgICAgICAgdCA9IDAuMDtcblxuXHQgICAgICAvLyBvdGhlcndpc2Ugd2UgYXJlIGFuaW1hdGluZywgc28gdXNlIGN1YmljIGVhc2luZ1xuXHQgICAgICB9IGVsc2Uge1xuXHQgICAgICAgIHQgPSBlYXNlQ3ViaWNJbk91dCgoZWxhcHNlZCAtIGRlbGF5KSAvIGR1cmF0aW9uKTtcblx0ICAgICAgfVxuXG5cdFx0XHRcdC8vIGludGVycG9sYXRlIHBvc2l0aW9uXG5cdCAgICAgIHZlYzIgcG9zaXRpb24gPSBtaXgocG9zaXRpb25TdGFydCwgcG9zaXRpb25FbmQsIHQpO1xuXG5cdCAgICAgIC8vIGludGVycG9sYXRlIGFuZCBzZW5kIGNvbG9yIHRvIHRoZSBmcmFnbWVudCBzaGFkZXJcblx0ICAgICAgZnJhZ0NvbG9yID0gbWl4KGNvbG9yU3RhcnQsIGNvbG9yRW5kLCB0KTtcblxuXHRcdFx0XHQvLyBzY2FsZSB0byBub3JtYWxpemVkIGRldmljZSBjb29yZGluYXRlc1xuXHRcdFx0XHQvLyBnbF9Qb3NpdGlvbiBpcyBhIHNwZWNpYWwgdmFyaWFibGUgdGhhdCBob2xkcyB0aGUgcG9zaXRpb24gb2YgYSB2ZXJ0ZXhcblx0ICAgICAgZ2xfUG9zaXRpb24gPSB2ZWM0KG5vcm1hbGl6ZUNvb3Jkcyhwb3NpdGlvbiksIDAuMCwgMS4wKTtcblx0XHRcdH1cblx0XHRcdGAsXG5cblx0XHRcdGF0dHJpYnV0ZXM6IHtcblx0XHRcdFx0cG9zaXRpb25TdGFydDogcG9pbnRzLm1hcChkID0+IFtkLnN4LCBkLnN5XSksXG5cdFx0XHRcdHBvc2l0aW9uRW5kOiBwb2ludHMubWFwKGQgPT4gW2QudHgsIGQudHldKSxcblx0XHRcdFx0Y29sb3JTdGFydDogcG9pbnRzLm1hcChkID0+IGQuY29sb3JTdGFydCksXG5cdFx0XHRcdGNvbG9yRW5kOiBwb2ludHMubWFwKGQgPT4gZC5jb2xvckVuZCksXG5cdFx0XHRcdGluZGV4OiBkMy5yYW5nZShwb2ludHMubGVuZ3RoKSxcblx0XHRcdH0sXG5cblx0XHRcdHVuaWZvcm1zOiB7XG5cdFx0XHRcdHBvaW50V2lkdGg6IHJlZ2wucHJvcCgncG9pbnRXaWR0aCcpLFxuXHRcdFx0XHRzdGFnZVdpZHRoOiByZWdsLnByb3AoJ3N0YWdlV2lkdGgnKSxcblx0XHRcdFx0c3RhZ2VIZWlnaHQ6IHJlZ2wucHJvcCgnc3RhZ2VIZWlnaHQnKSxcblx0XHRcdFx0ZGVsYXlCeUluZGV4OiByZWdsLnByb3AoJ2RlbGF5QnlJbmRleCcpLFxuXHQgICAgICBkdXJhdGlvbjogcmVnbC5wcm9wKCdkdXJhdGlvbicpLFxuXG5cdCAgICAgIC8vIHRpbWUgaW4gbWlsbGlzZWNvbmRzIHNpbmNlIHRoZSBwcm9wIHN0YXJ0VGltZSAoaS5lLiB0aW1lIGVsYXBzZWQpXG5cdCAgICAgIGVsYXBzZWQ6ICh7IHRpbWUgfSwgeyBzdGFydFRpbWUgPSAwIH0pID0+ICh0aW1lIC0gc3RhcnRUaW1lKSAqIDEwMDAsXG5cdFx0XHR9LFxuXG5cdFx0XHRjb3VudDogcG9pbnRzLmxlbmd0aCxcblx0XHRcdHByaW1pdGl2ZTogJ3BvaW50cycsXG5cdFx0fSk7XG5cblx0XHRyZXR1cm4gZHJhd1BvaW50cztcblx0fVxuXG5cblx0Ly8gc3RhcnQgYW5pbWF0aW9uIGxvb3AgKG5vdGU6IHRpbWUgaXMgaW4gc2Vjb25kcylcblx0ZnVuY3Rpb24gYW5pbWF0ZShsYXlvdXQsIHBvaW50cykge1xuXHRcdGNvbnNvbGUubG9nKCdhbmltYXRpbmcgd2l0aCBuZXcgbGF5b3V0Jyk7XG5cdFx0Ly8gbWFrZSBwcmV2aW91cyBlbmQgdGhlIG5ldyBiZWdpbm5pbmdcblx0XHRwb2ludHMuZm9yRWFjaChkID0+IHtcblx0XHRcdGQuc3ggPSBkLnR4O1xuXHRcdFx0ZC5zeSA9IGQudHk7XG5cdFx0XHRkLmNvbG9yU3RhcnQgPSBkLmNvbG9yRW5kO1xuXHRcdH0pO1xuXG5cdFx0Ly8gbGF5b3V0IHBvaW50c1xuXHRcdGxheW91dChwb2ludHMpO1xuXG5cdFx0Ly8gY29weSBsYXlvdXQgeCB5IHRvIGVuZCBwb3NpdGlvbnNcblx0XHRjb25zdCBjb2xvclNjYWxlID0gY29sb3JTY2FsZXNbY3VycmVudENvbG9yU2NhbGVdO1xuXHRcdHBvaW50cy5mb3JFYWNoKChkLCBpKSA9PiB7XG5cdFx0XHRkLnR4ID0gZC54O1xuXHRcdFx0ZC50eSA9IGQueTtcblx0XHRcdGQuY29sb3JFbmQgPSBjb2xvclNjYWxlKGkgLyBwb2ludHMubGVuZ3RoKVxuXHRcdH0pO1xuXG5cdFx0Ly8gY3JlYXRlIHRoZSByZWdsIGZ1bmN0aW9uIHdpdGggdGhlIG5ldyBzdGFydCBhbmQgZW5kIHBvaW50c1xuXHRcdGNvbnN0IGRyYXdQb2ludHMgPSBjcmVhdGVEcmF3UG9pbnRzKHBvaW50cyk7XG5cblx0XHRjb25zdCBmcmFtZUxvb3AgPSByZWdsLmZyYW1lKCh7IHRpbWUgfSkgPT4ge1xuXHRcdFx0aWYgKHN0YXJ0VGltZSA9PT0gbnVsbCkge1xuXHRcdFx0XHRzdGFydFRpbWUgPSB0aW1lO1xuXHRcdFx0fVxuXG5cdFx0XHQvLyBjbGVhciB0aGUgYnVmZmVyXG5cdFx0XHRyZWdsLmNsZWFyKHtcblx0XHRcdFx0Ly8gYmFja2dyb3VuZCBjb2xvciAoYmxhY2spXG5cdFx0XHRcdGNvbG9yOiBbMCwgMCwgMCwgMV0sXG5cdFx0XHRcdGRlcHRoOiAxLFxuXHRcdFx0fSk7XG5cblx0XHRcdC8vIGRyYXcgdGhlIHBvaW50cyB1c2luZyBvdXIgY3JlYXRlZCByZWdsIGZ1bmNcblx0XHRcdGRyYXdQb2ludHMoe1xuXHRcdFx0XHRwb2ludFdpZHRoLFxuXHRcdFx0XHRzdGFnZVdpZHRoOiB3aWR0aCxcblx0XHRcdFx0c3RhZ2VIZWlnaHQ6IGhlaWdodCxcblx0XHRcdFx0ZHVyYXRpb24sXG5cdFx0XHRcdGRlbGF5QnlJbmRleCxcblx0XHRcdFx0c3RhcnRUaW1lLFxuXHRcdFx0fSk7XG5cblx0XHRcdC8vIGlmIHdlIGhhdmUgZXhjZWVkZWQgdGhlIG1heGltdW0gZHVyYXRpb24sIG1vdmUgb24gdG8gdGhlIG5leHQgYW5pbWF0aW9uXG5cdFx0XHRpZiAodGltZSAtIHN0YXJ0VGltZSA+IChtYXhEdXJhdGlvbiAvIDEwMDApKSB7XG5cdFx0XHRcdGNvbnNvbGUubG9nKCdkb25lIGFuaW1hdGluZywgbW92aW5nIHRvIG5leHQgbGF5b3V0Jyk7XG5cdFx0XHRcdGZyYW1lTG9vcC5jYW5jZWwoKTtcblxuXHRcdFx0XHRjdXJyZW50TGF5b3V0ID0gKGN1cnJlbnRMYXlvdXQgKyAxKSAlIGxheW91dHMubGVuZ3RoO1xuXHRcdFx0XHRzdGFydFRpbWUgPSBudWxsO1xuXHRcdFx0XHRjdXJyZW50Q29sb3JTY2FsZSA9IChjdXJyZW50Q29sb3JTY2FsZSArIDEpICUgY29sb3JTY2FsZXMubGVuZ3RoO1xuXHRcdFx0XHRhbmltYXRlKGxheW91dHNbY3VycmVudExheW91dF0sIHBvaW50cyk7XG5cdFx0XHR9XG5cdFx0fSk7XG5cdH1cblxuXG5cdC8vIGNyZWF0ZSBpbml0aWFsIHNldCBvZiBwb2ludHNcblx0Y29uc3QgcG9pbnRzID0gY3JlYXRlUG9pbnRzKG51bVBvaW50cywgcG9pbnRXaWR0aCwgd2lkdGgsIGhlaWdodCk7XG5cblx0Ly8gaW5pdGlhbGl6ZSB3aXRoIGFsbCB0aGUgcG9pbnRzIGluIHRoZSBtaWRkbGUgb2YgdGhlIHNjcmVlblxuXHRwb2ludHMuZm9yRWFjaCgoZCwgaSkgPT4ge1xuXHRcdGQudHggPSB3aWR0aCAvIDI7XG5cdFx0ZC50eSA9IGhlaWdodCAvIDI7XG5cdFx0ZC5jb2xvckVuZCA9IFswLCAwLCAwXTtcblx0fSk7XG5cblx0YW5pbWF0ZShsYXlvdXRzW2N1cnJlbnRMYXlvdXRdLCBwb2ludHMpO1xufVxuXG5cbi8vIGluaXRpYWxpemUgcmVnbFxucmVnbCh7XG4gIC8vIGNhbGxiYWNrIHdoZW4gcmVnbCBpcyBpbml0aWFsaXplZFxuICBvbkRvbmU6IG1haW5cbn0pO1xuIl19
function phyllotaxisLayout(points,pointWidth,xOffset,yOffset,iOffset){if(xOffset===void 0)xOffset=0;if(yOffset===void 0)yOffset=0;if(iOffset===void 0)iOffset=0;var theta=Math.PI*(3-Math.sqrt(5));var pointRadius=pointWidth/2;points.forEach(function(point,i){var index=(i+iOffset)%points.length;var phylloX=pointRadius*Math.sqrt(index)*Math.cos(index*theta);var phylloY=pointRadius*Math.sqrt(index)*Math.sin(index*theta);point.x=xOffset+phylloX-pointRadius;point.y=yOffset+phylloY-pointRadius});return points}function gridLayout(points,pointWidth,gridWidth){var pointHeight=pointWidth;var pointsPerRow=Math.floor(gridWidth/pointWidth);var numRows=points.length/pointsPerRow;points.forEach(function(point,i){point.x=pointWidth*(i%pointsPerRow);point.y=pointHeight*Math.floor(i/pointsPerRow)});return points}function randomLayout(points,pointWidth,width,height){points.forEach(function(point,i){point.x=Math.random()*(width-pointWidth);point.y=Math.random()*(height-pointWidth)});return points}function sineLayout(points,pointWidth,width,height){var amplitude=.3*(height/2);var yOffset=height/2;var periods=3;var yScale=d3.scaleLinear().domain([0,points.length-1]).range([0,periods*2*Math.PI]);points.forEach(function(point,i){point.x=i/points.length*(width-pointWidth);point.y=amplitude*Math.sin(yScale(i))+yOffset});return points}function spiralLayout(points,pointWidth,width,height){var amplitude=.3*(height/2);var xOffset=width/2;var yOffset=height/2;var periods=20;var rScale=d3.scaleLinear().domain([0,points.length-1]).range([0,Math.min(width/2,height/2)-pointWidth]);var thetaScale=d3.scaleLinear().domain([0,points.length-1]).range([0,periods*2*Math.PI]);points.forEach(function(point,i){point.x=rScale(i)*Math.cos(thetaScale(i))+xOffset;point.y=rScale(i)*Math.sin(thetaScale(i))+yOffset});return points}function createPoints(numPoints,pointWidth,width,height){var colorScale=d3.scaleSequential(d3.interpolateViridis).domain([numPoints-1,0]);var points=d3.range(numPoints).map(function(id){return{id:id,color:colorScale(id)}});return randomLayout(points,pointWidth,width,height)}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImNvbW1vbi5qcyJdLCJuYW1lcyI6WyJwaHlsbG90YXhpc0xheW91dCIsInBvaW50cyIsInBvaW50V2lkdGgiLCJ4T2Zmc2V0IiwieU9mZnNldCIsImlPZmZzZXQiLCJjb25zdCIsInRoZXRhIiwiTWF0aCIsIlBJIiwic3FydCIsInBvaW50UmFkaXVzIiwiZm9yRWFjaCIsInBvaW50IiwiaSIsImluZGV4IiwibGVuZ3RoIiwicGh5bGxvWCIsImNvcyIsInBoeWxsb1kiLCJzaW4iLCJ4IiwieSIsImdyaWRMYXlvdXQiLCJncmlkV2lkdGgiLCJwb2ludEhlaWdodCIsInBvaW50c1BlclJvdyIsImZsb29yIiwibnVtUm93cyIsInJhbmRvbUxheW91dCIsIndpZHRoIiwiaGVpZ2h0IiwicmFuZG9tIiwic2luZUxheW91dCIsImFtcGxpdHVkZSIsInBlcmlvZHMiLCJ5U2NhbGUiLCJkMyIsInNjYWxlTGluZWFyIiwiZG9tYWluIiwicmFuZ2UiLCJzcGlyYWxMYXlvdXQiLCJyU2NhbGUiLCJtaW4iLCJ0aGV0YVNjYWxlIiwiY3JlYXRlUG9pbnRzIiwibnVtUG9pbnRzIiwiY29sb3JTY2FsZSIsInNjYWxlU2VxdWVudGlhbCIsImludGVycG9sYXRlVmlyaWRpcyIsIm1hcCIsImlkIiwiY29sb3IiXSwibWFwcGluZ3MiOiJBQVdBLFFBQVNBLG1CQUFrQkMsT0FBUUMsV0FBWUMsUUFBYUMsUUFBYUMscUNBQWhCLDhCQUFhLDhCQUFhLENBRWpGQyxJQUFNQyxPQUFRQyxLQUFLQyxJQUFNLEVBQUlELEtBQUtFLEtBQUssR0FFdkNKLElBQU1LLGFBQWNULFdBQWEsQ0FFakNELFFBQU9XLFFBQVEsU0FBQ0MsTUFBT0MsR0FDckJSLEdBQU1TLFFBQVNELEVBQUlULFNBQVdKLE9BQU9lLE1BQ3JDVixJQUFNVyxTQUFVTixZQUFjSCxLQUFLRSxLQUFLSyxPQUFTUCxLQUFLVSxJQUFJSCxNQUFRUixNQUNsRUQsSUFBTWEsU0FBVVIsWUFBY0gsS0FBS0UsS0FBS0ssT0FBU1AsS0FBS1ksSUFBSUwsTUFBUVIsTUFFbEVNLE9BQU1RLEVBQUlsQixRQUFVYyxRQUFVTixXQUM5QkUsT0FBTVMsRUFBSWxCLFFBQVVlLFFBQVVSLGFBR2hDLE9BQU9WLFFBYVQsUUFBU3NCLFlBQVd0QixPQUFRQyxXQUFZc0IsV0FDdENsQixHQUFNbUIsYUFBY3ZCLFVBQ3BCSSxJQUFNb0IsY0FBZWxCLEtBQUttQixNQUFNSCxVQUFZdEIsV0FDNUNJLElBQU1zQixTQUFVM0IsT0FBT2UsT0FBU1UsWUFFaEN6QixRQUFPVyxRQUFRLFNBQUNDLE1BQU9DLEdBQ3JCRCxNQUFNUSxFQUFJbkIsWUFBY1ksRUFBSVksYUFDNUJiLE9BQU1TLEVBQUlHLFlBQWNqQixLQUFLbUIsTUFBTWIsRUFBSVksZUFHekMsT0FBT3pCLFFBY1QsUUFBUzRCLGNBQWE1QixPQUFRQyxXQUFZNEIsTUFBT0MsUUFDL0M5QixPQUFPVyxRQUFRLFNBQUNDLE1BQU9DLEdBQ3JCRCxNQUFNUSxFQUFJYixLQUFLd0IsVUFBWUYsTUFBUTVCLFdBQ25DVyxPQUFNUyxFQUFJZCxLQUFLd0IsVUFBWUQsT0FBUzdCLGFBR3RDLE9BQU9ELFFBY1QsUUFBU2dDLFlBQVdoQyxPQUFRQyxXQUFZNEIsTUFBT0MsUUFDN0N6QixHQUFNNEIsV0FBWSxJQUFPSCxPQUFTLEVBQ2xDekIsSUFBTUYsU0FBVTJCLE9BQVMsQ0FDekJ6QixJQUFNNkIsU0FBVSxDQUNoQjdCLElBQU04QixRQUFTQyxHQUFHQyxjQUNmQyxRQUFRLEVBQUd0QyxPQUFPZSxPQUFTLElBQzNCd0IsT0FBTyxFQUFHTCxRQUFVLEVBQUkzQixLQUFLQyxJQUVoQ1IsUUFBT1csUUFBUSxTQUFDQyxNQUFPQyxHQUNyQkQsTUFBTVEsRUFBS1AsRUFBSWIsT0FBT2UsUUFBV2MsTUFBUTVCLFdBQ3pDVyxPQUFNUyxFQUFJWSxVQUFZMUIsS0FBS1ksSUFBSWdCLE9BQU90QixJQUFNVixTQUc5QyxPQUFPSCxRQWNULFFBQVN3QyxjQUFheEMsT0FBUUMsV0FBWTRCLE1BQU9DLFFBQy9DekIsR0FBTTRCLFdBQVksSUFBT0gsT0FBUyxFQUNsQ3pCLElBQU1ILFNBQVUyQixNQUFRLENBQ3hCeEIsSUFBTUYsU0FBVTJCLE9BQVMsQ0FDekJ6QixJQUFNNkIsU0FBVSxFQUVoQjdCLElBQU1vQyxRQUFTTCxHQUFHQyxjQUNmQyxRQUFRLEVBQUd0QyxPQUFPZSxPQUFRLElBQzFCd0IsT0FBTyxFQUFHaEMsS0FBS21DLElBQUliLE1BQVEsRUFBR0MsT0FBUyxHQUFLN0IsWUFFL0NJLElBQU1zQyxZQUFhUCxHQUFHQyxjQUNuQkMsUUFBUSxFQUFHdEMsT0FBT2UsT0FBUyxJQUMzQndCLE9BQU8sRUFBR0wsUUFBVSxFQUFJM0IsS0FBS0MsSUFFaENSLFFBQU9XLFFBQVEsU0FBQ0MsTUFBT0MsR0FDckJELE1BQU1RLEVBQUlxQixPQUFPNUIsR0FBS04sS0FBS1UsSUFBSTBCLFdBQVc5QixJQUFNWCxPQUNoRFUsT0FBTVMsRUFBSW9CLE9BQU81QixHQUFLTixLQUFLWSxJQUFJd0IsV0FBVzlCLElBQU1WLFNBR2xELE9BQU9ILFFBVVQsUUFBUzRDLGNBQWFDLFVBQVc1QyxXQUFZNEIsTUFBT0MsUUFDbER6QixHQUFNeUMsWUFBYVYsR0FBR1csZ0JBQWdCWCxHQUFHWSxvQkFDdENWLFFBQVFPLFVBQVksRUFBRyxHQUUxQnhDLElBQU1MLFFBQVNvQyxHQUFHRyxNQUFNTSxXQUFXSSxJQUFJLFNBQUFDLElBQUcsT0FDeENBLEdBQUFBLEdBQ0FDLE1BQU9MLFdBQVdJLE1BR3BCLE9BQU90QixjQUFhNUIsT0FBUUMsV0FBWTRCLE1BQU9DIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBHaXZlbiBhIHNldCBvZiBwb2ludHMsIGxheSB0aGVtIG91dCBpbiBhIHBoeWxsb3RheGlzIGxheW91dC5cbiAqIE11dGF0ZXMgdGhlIGBwb2ludHNgIHBhc3NlZCBpbiBieSB1cGRhdGluZyB0aGUgeCBhbmQgeSB2YWx1ZXMuXG4gKlxuICogQHBhcmFtIHtPYmplY3RbXX0gcG9pbnRzIFRoZSBhcnJheSBvZiBwb2ludHMgdG8gdXBkYXRlLiBXaWxsIGdldCBgeGAgYW5kIGB5YCBzZXQuXG4gKiBAcGFyYW0ge051bWJlcn0gcG9pbnRXaWR0aCBUaGUgc2l6ZSBpbiBwaXhlbHMgb2YgdGhlIHBvaW50J3Mgd2lkdGguIFNob3VsZCBhbHNvIGluY2x1ZGUgbWFyZ2luLlxuICogQHBhcmFtIHtOdW1iZXJ9IHhPZmZzZXQgVGhlIHggb2Zmc2V0IHRvIGFwcGx5IHRvIGFsbCBwb2ludHNcbiAqIEBwYXJhbSB7TnVtYmVyfSB5T2Zmc2V0IFRoZSB5IG9mZnNldCB0byBhcHBseSB0byBhbGwgcG9pbnRzXG4gKlxuICogQHJldHVybiB7T2JqZWN0W119IHBvaW50cyB3aXRoIG1vZGlmaWVkIHggYW5kIHlcbiAqL1xuZnVuY3Rpb24gcGh5bGxvdGF4aXNMYXlvdXQocG9pbnRzLCBwb2ludFdpZHRoLCB4T2Zmc2V0ID0gMCwgeU9mZnNldCA9IDAsIGlPZmZzZXQgPSAwKSB7XG4gIC8vIHRoZXRhIGRldGVybWluZXMgdGhlIHNwaXJhbCBvZiB0aGUgbGF5b3V0XG4gIGNvbnN0IHRoZXRhID0gTWF0aC5QSSAqICgzIC0gTWF0aC5zcXJ0KDUpKTtcblxuICBjb25zdCBwb2ludFJhZGl1cyA9IHBvaW50V2lkdGggLyAyO1xuXG4gIHBvaW50cy5mb3JFYWNoKChwb2ludCwgaSkgPT4ge1xuICAgIGNvbnN0IGluZGV4ID0gKGkgKyBpT2Zmc2V0KSAlIHBvaW50cy5sZW5ndGg7XG4gICAgY29uc3QgcGh5bGxvWCA9IHBvaW50UmFkaXVzICogTWF0aC5zcXJ0KGluZGV4KSAqIE1hdGguY29zKGluZGV4ICogdGhldGEpO1xuICAgIGNvbnN0IHBoeWxsb1kgPSBwb2ludFJhZGl1cyAqIE1hdGguc3FydChpbmRleCkgKiBNYXRoLnNpbihpbmRleCAqIHRoZXRhKTtcblxuICAgIHBvaW50LnggPSB4T2Zmc2V0ICsgcGh5bGxvWCAtIHBvaW50UmFkaXVzO1xuICAgIHBvaW50LnkgPSB5T2Zmc2V0ICsgcGh5bGxvWSAtIHBvaW50UmFkaXVzO1xuICB9KTtcblxuICByZXR1cm4gcG9pbnRzO1xufVxuXG4vKipcbiAqIEdpdmVuIGEgc2V0IG9mIHBvaW50cywgbGF5IHRoZW0gb3V0IGluIGEgZ3JpZC5cbiAqIE11dGF0ZXMgdGhlIGBwb2ludHNgIHBhc3NlZCBpbiBieSB1cGRhdGluZyB0aGUgeCBhbmQgeSB2YWx1ZXMuXG4gKlxuICogQHBhcmFtIHtPYmplY3RbXX0gcG9pbnRzIFRoZSBhcnJheSBvZiBwb2ludHMgdG8gdXBkYXRlLiBXaWxsIGdldCBgeGAgYW5kIGB5YCBzZXQuXG4gKiBAcGFyYW0ge051bWJlcn0gcG9pbnRXaWR0aCBUaGUgc2l6ZSBpbiBwaXhlbHMgb2YgdGhlIHBvaW50J3Mgd2lkdGguIFNob3VsZCBhbHNvIGluY2x1ZGUgbWFyZ2luLlxuICogQHBhcmFtIHtOdW1iZXJ9IGdyaWRXaWR0aCBUaGUgd2lkdGggb2YgdGhlIGdyaWQgb2YgcG9pbnRzXG4gKlxuICogQHJldHVybiB7T2JqZWN0W119IHBvaW50cyB3aXRoIG1vZGlmaWVkIHggYW5kIHlcbiAqL1xuZnVuY3Rpb24gZ3JpZExheW91dChwb2ludHMsIHBvaW50V2lkdGgsIGdyaWRXaWR0aCkge1xuICBjb25zdCBwb2ludEhlaWdodCA9IHBvaW50V2lkdGg7XG4gIGNvbnN0IHBvaW50c1BlclJvdyA9IE1hdGguZmxvb3IoZ3JpZFdpZHRoIC8gcG9pbnRXaWR0aCk7XG4gIGNvbnN0IG51bVJvd3MgPSBwb2ludHMubGVuZ3RoIC8gcG9pbnRzUGVyUm93O1xuXG4gIHBvaW50cy5mb3JFYWNoKChwb2ludCwgaSkgPT4ge1xuICAgIHBvaW50LnggPSBwb2ludFdpZHRoICogKGkgJSBwb2ludHNQZXJSb3cpO1xuICAgIHBvaW50LnkgPSBwb2ludEhlaWdodCAqIE1hdGguZmxvb3IoaSAvIHBvaW50c1BlclJvdyk7XG4gIH0pO1xuXG4gIHJldHVybiBwb2ludHM7XG59XG5cbi8qKlxuICogR2l2ZW4gYSBzZXQgb2YgcG9pbnRzLCBsYXkgdGhlbSBvdXQgcmFuZG9tbHkuXG4gKiBNdXRhdGVzIHRoZSBgcG9pbnRzYCBwYXNzZWQgaW4gYnkgdXBkYXRpbmcgdGhlIHggYW5kIHkgdmFsdWVzLlxuICpcbiAqIEBwYXJhbSB7T2JqZWN0W119IHBvaW50cyBUaGUgYXJyYXkgb2YgcG9pbnRzIHRvIHVwZGF0ZS4gV2lsbCBnZXQgYHhgIGFuZCBgeWAgc2V0LlxuICogQHBhcmFtIHtOdW1iZXJ9IHBvaW50V2lkdGggVGhlIHNpemUgaW4gcGl4ZWxzIG9mIHRoZSBwb2ludCdzIHdpZHRoLiBTaG91bGQgYWxzbyBpbmNsdWRlIG1hcmdpbi5cbiAqIEBwYXJhbSB7TnVtYmVyfSB3aWR0aCBUaGUgd2lkdGggb2YgdGhlIGFyZWEgdG8gcGxhY2UgdGhlbSBpblxuICogQHBhcmFtIHtOdW1iZXJ9IGhlaWdodCBUaGUgaGVpZ2h0IG9mIHRoZSBhcmVhIHRvIHBsYWNlIHRoZW0gaW5cbiAqXG4gKiBAcmV0dXJuIHtPYmplY3RbXX0gcG9pbnRzIHdpdGggbW9kaWZpZWQgeCBhbmQgeVxuICovXG5mdW5jdGlvbiByYW5kb21MYXlvdXQocG9pbnRzLCBwb2ludFdpZHRoLCB3aWR0aCwgaGVpZ2h0KSB7XG4gIHBvaW50cy5mb3JFYWNoKChwb2ludCwgaSkgPT4ge1xuICAgIHBvaW50LnggPSBNYXRoLnJhbmRvbSgpICogKHdpZHRoIC0gcG9pbnRXaWR0aCk7XG4gICAgcG9pbnQueSA9IE1hdGgucmFuZG9tKCkgKiAoaGVpZ2h0IC0gcG9pbnRXaWR0aCk7XG4gIH0pO1xuXG4gIHJldHVybiBwb2ludHM7XG59XG5cbi8qKlxuICogR2l2ZW4gYSBzZXQgb2YgcG9pbnRzLCBsYXkgdGhlbSBvdXQgaW4gYSBzaW5lIHdhdmUuXG4gKiBNdXRhdGVzIHRoZSBgcG9pbnRzYCBwYXNzZWQgaW4gYnkgdXBkYXRpbmcgdGhlIHggYW5kIHkgdmFsdWVzLlxuICpcbiAqIEBwYXJhbSB7T2JqZWN0W119IHBvaW50cyBUaGUgYXJyYXkgb2YgcG9pbnRzIHRvIHVwZGF0ZS4gV2lsbCBnZXQgYHhgIGFuZCBgeWAgc2V0LlxuICogQHBhcmFtIHtOdW1iZXJ9IHBvaW50V2lkdGggVGhlIHNpemUgaW4gcGl4ZWxzIG9mIHRoZSBwb2ludCdzIHdpZHRoLiBTaG91bGQgYWxzbyBpbmNsdWRlIG1hcmdpbi5cbiAqIEBwYXJhbSB7TnVtYmVyfSB3aWR0aCBUaGUgd2lkdGggb2YgdGhlIGFyZWEgdG8gcGxhY2UgdGhlbSBpblxuICogQHBhcmFtIHtOdW1iZXJ9IGhlaWdodCBUaGUgaGVpZ2h0IG9mIHRoZSBhcmVhIHRvIHBsYWNlIHRoZW0gaW5cbiAqXG4gKiBAcmV0dXJuIHtPYmplY3RbXX0gcG9pbnRzIHdpdGggbW9kaWZpZWQgeCBhbmQgeVxuICovXG5mdW5jdGlvbiBzaW5lTGF5b3V0KHBvaW50cywgcG9pbnRXaWR0aCwgd2lkdGgsIGhlaWdodCkge1xuICBjb25zdCBhbXBsaXR1ZGUgPSAwLjMgKiAoaGVpZ2h0IC8gMik7XG4gIGNvbnN0IHlPZmZzZXQgPSBoZWlnaHQgLyAyO1xuICBjb25zdCBwZXJpb2RzID0gMztcbiAgY29uc3QgeVNjYWxlID0gZDMuc2NhbGVMaW5lYXIoKVxuICAgIC5kb21haW4oWzAsIHBvaW50cy5sZW5ndGggLSAxXSlcbiAgICAucmFuZ2UoWzAsIHBlcmlvZHMgKiAyICogTWF0aC5QSV0pO1xuXG4gIHBvaW50cy5mb3JFYWNoKChwb2ludCwgaSkgPT4ge1xuICAgIHBvaW50LnggPSAoaSAvIHBvaW50cy5sZW5ndGgpICogKHdpZHRoIC0gcG9pbnRXaWR0aCk7XG4gICAgcG9pbnQueSA9IGFtcGxpdHVkZSAqIE1hdGguc2luKHlTY2FsZShpKSkgKyB5T2Zmc2V0O1xuICB9KTtcblxuICByZXR1cm4gcG9pbnRzO1xufVxuXG4vKipcbiAqIEdpdmVuIGEgc2V0IG9mIHBvaW50cywgbGF5IHRoZW0gb3V0IGluIGEgc3BpcmFsLlxuICogTXV0YXRlcyB0aGUgYHBvaW50c2AgcGFzc2VkIGluIGJ5IHVwZGF0aW5nIHRoZSB4IGFuZCB5IHZhbHVlcy5cbiAqXG4gKiBAcGFyYW0ge09iamVjdFtdfSBwb2ludHMgVGhlIGFycmF5IG9mIHBvaW50cyB0byB1cGRhdGUuIFdpbGwgZ2V0IGB4YCBhbmQgYHlgIHNldC5cbiAqIEBwYXJhbSB7TnVtYmVyfSBwb2ludFdpZHRoIFRoZSBzaXplIGluIHBpeGVscyBvZiB0aGUgcG9pbnQncyB3aWR0aC4gU2hvdWxkIGFsc28gaW5jbHVkZSBtYXJnaW4uXG4gKiBAcGFyYW0ge051bWJlcn0gd2lkdGggVGhlIHdpZHRoIG9mIHRoZSBhcmVhIHRvIHBsYWNlIHRoZW0gaW5cbiAqIEBwYXJhbSB7TnVtYmVyfSBoZWlnaHQgVGhlIGhlaWdodCBvZiB0aGUgYXJlYSB0byBwbGFjZSB0aGVtIGluXG4gKlxuICogQHJldHVybiB7T2JqZWN0W119IHBvaW50cyB3aXRoIG1vZGlmaWVkIHggYW5kIHlcbiAqL1xuZnVuY3Rpb24gc3BpcmFsTGF5b3V0KHBvaW50cywgcG9pbnRXaWR0aCwgd2lkdGgsIGhlaWdodCkge1xuICBjb25zdCBhbXBsaXR1ZGUgPSAwLjMgKiAoaGVpZ2h0IC8gMik7XG4gIGNvbnN0IHhPZmZzZXQgPSB3aWR0aCAvIDI7XG4gIGNvbnN0IHlPZmZzZXQgPSBoZWlnaHQgLyAyO1xuICBjb25zdCBwZXJpb2RzID0gMjA7XG5cbiAgY29uc3QgclNjYWxlID0gZDMuc2NhbGVMaW5lYXIoKVxuICAgIC5kb21haW4oWzAsIHBvaW50cy5sZW5ndGggLTFdKVxuICAgIC5yYW5nZShbMCwgTWF0aC5taW4od2lkdGggLyAyLCBoZWlnaHQgLyAyKSAtIHBvaW50V2lkdGhdKTtcblxuICBjb25zdCB0aGV0YVNjYWxlID0gZDMuc2NhbGVMaW5lYXIoKVxuICAgIC5kb21haW4oWzAsIHBvaW50cy5sZW5ndGggLSAxXSlcbiAgICAucmFuZ2UoWzAsIHBlcmlvZHMgKiAyICogTWF0aC5QSV0pO1xuXG4gIHBvaW50cy5mb3JFYWNoKChwb2ludCwgaSkgPT4ge1xuICAgIHBvaW50LnggPSByU2NhbGUoaSkgKiBNYXRoLmNvcyh0aGV0YVNjYWxlKGkpKSArIHhPZmZzZXRcbiAgICBwb2ludC55ID0gclNjYWxlKGkpICogTWF0aC5zaW4odGhldGFTY2FsZShpKSkgKyB5T2Zmc2V0O1xuICB9KTtcblxuICByZXR1cm4gcG9pbnRzO1xufVxuXG5cblxuXG4vKipcbiAqIEdlbmVyYXRlIGFuIG9iamVjdCBhcnJheSBvZiBgbnVtUG9pbnRzYCBsZW5ndGggd2l0aCB1bmlxdWUgSURzXG4gKiBhbmQgYXNzaWduZWQgY29sb3JzXG4gKi9cbmZ1bmN0aW9uIGNyZWF0ZVBvaW50cyhudW1Qb2ludHMsIHBvaW50V2lkdGgsIHdpZHRoLCBoZWlnaHQpIHtcbiAgY29uc3QgY29sb3JTY2FsZSA9IGQzLnNjYWxlU2VxdWVudGlhbChkMy5pbnRlcnBvbGF0ZVZpcmlkaXMpXG4gICAgLmRvbWFpbihbbnVtUG9pbnRzIC0gMSwgMF0pO1xuXG4gIGNvbnN0IHBvaW50cyA9IGQzLnJhbmdlKG51bVBvaW50cykubWFwKGlkID0+ICh7XG4gICAgaWQsXG4gICAgY29sb3I6IGNvbG9yU2NhbGUoaWQpLFxuICB9KSk7XG5cbiAgcmV0dXJuIHJhbmRvbUxheW91dChwb2ludHMsIHBvaW50V2lkdGgsIHdpZHRoLCBoZWlnaHQpO1xufVxuIl19
<!DOCTYPE html>
<title>Animate 100,000 points with regl</title>
<body>
<script src="https://npmcdn.com/regl/dist/regl.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="dist_common.js"></script>
<script src="dist.js"></script>
</body>
var regl=createREGL({onDone:function (err, regl) {console.log('Initialization of regl complete\n', regl);main(err, regl);}});
function main(err, regl) {
const numPoints = 100000;
const pointWidth = 4;
const pointMargin = 1;
const width = window.innerWidth;
const height = window.innerHeight;
// duration of the animation ignoring delays
const duration = 1500;
// multiply this value by the index of a point to get its delay
const delayByIndex = 500 / numPoints;
// include max delay in here
const maxDuration = duration + delayByIndex * numPoints;
// create helpers that will layout the points in different ways (see common.js)
const toPhyllotaxis = (points) => phyllotaxisLayout(points, pointWidth + pointMargin, width / 2, height / 2);
const toGrid = (points) => gridLayout(points, pointWidth + pointMargin, width);
const toSine = (points) => sineLayout(points, pointWidth + pointMargin, width, height);
const toSpiral = (points) => spiralLayout(points, pointWidth + pointMargin, width, height);
// set the order of the layouts and some initial animation state
const layouts = [toPhyllotaxis, toSpiral, toGrid, toSine];
let currentLayout = 0;
let startTime = null; // in seconds
// wrap d3 color scales so they produce vec3s with values 0-1
function wrapColorScale(scale) {
return t => {
const rgb = d3.rgb(scale(1 - t));
return [rgb.r / 255, rgb.g / 255, rgb.b / 255];
};
}
// the order of color scales to loop through
const colorScales = [
d3.scaleSequential(d3.interpolateViridis),
d3.scaleSequential(d3.interpolateInferno),
d3.scaleSequential(d3.interpolateCool),
].map(wrapColorScale);
let currentColorScale = 0;
// function to compile a draw points regl func
function createDrawPoints(points) {
const drawPoints = regl({
frag: `
// set the precision of floating point numbers
precision highp float;
// this value is populated by the vertex shader
varying vec3 fragColor;
void main() {
// gl_FragColor is a special variable that holds the color of a pixel
gl_FragColor = vec4(fragColor, 1);
}
`,
vert: `
// per vertex attributes
attribute vec2 positionStart;
attribute vec2 positionEnd;
attribute float index;
attribute vec3 colorStart;
attribute vec3 colorEnd;
// variables to send to the fragment shader
varying vec3 fragColor;
// values that are the same for all vertices
uniform float pointWidth;
uniform float stageWidth;
uniform float stageHeight;
uniform float elapsed;
uniform float duration;
uniform float delayByIndex;
// helper function to transform from pixel space to normalized device coordinates (NDC)
// in NDC (0,0) is the middle, (-1, 1) is the top left and (1, -1) is the bottom right.
vec2 normalizeCoords(vec2 position) {
// read in the positions into x and y vars
float x = position[0];
float y = position[1];
return vec2(
2.0 * ((x / stageWidth) - 0.5),
// invert y since we think [0,0] is bottom left in pixel space
-(2.0 * ((y / stageHeight) - 0.5)));
}
// helper function to handle cubic easing (copied from d3 for consistency)
// note there are pre-made easing functions available via glslify.
float easeCubicInOut(float t) {
t *= 2.0;
t = (t <= 1.0 ? t * t * t : (t -= 2.0) * t * t + 2.0) / 2.0;
if (t > 1.0) {
t = 1.0;
}
return t;
}
void main() {
// update the size of a point based on the prop pointWidth
gl_PointSize = pointWidth;
float delay = delayByIndex * index;
// number between 0 and 1 indicating how far through the animation this
// vertex is.
float t;
// drawing without animation, so show end state immediately
if (duration == 0.0) {
t = 1.0;
// still delaying before animating
} else if (elapsed < delay) {
t = 0.0;
// otherwise we are animating, so use cubic easing
} else {
t = easeCubicInOut((elapsed - delay) / duration);
}
// interpolate position
vec2 position = mix(positionStart, positionEnd, t);
// interpolate and send color to the fragment shader
fragColor = mix(colorStart, colorEnd, t);
// scale to normalized device coordinates
// gl_Position is a special variable that holds the position of a vertex
gl_Position = vec4(normalizeCoords(position), 0.0, 1.0);
}
`,
attributes: {
positionStart: points.map(d => [d.sx, d.sy]),
positionEnd: points.map(d => [d.tx, d.ty]),
colorStart: points.map(d => d.colorStart),
colorEnd: points.map(d => d.colorEnd),
index: d3.range(points.length),
},
uniforms: {
pointWidth: regl.prop('pointWidth'),
stageWidth: regl.prop('stageWidth'),
stageHeight: regl.prop('stageHeight'),
delayByIndex: regl.prop('delayByIndex'),
duration: regl.prop('duration'),
// time in milliseconds since the prop startTime (i.e. time elapsed)
elapsed: ({ time }, { startTime = 0 }) => (time - startTime) * 1000,
},
count: points.length,
primitive: 'points',
});
return drawPoints;
}
// start animation loop (note: time is in seconds)
function animate(layout, points) {
console.log('animating with new layout');
// make previous end the new beginning
points.forEach(d => {
d.sx = d.tx;
d.sy = d.ty;
d.colorStart = d.colorEnd;
});
// layout points
layout(points);
// copy layout x y to end positions
const colorScale = colorScales[currentColorScale];
points.forEach((d, i) => {
d.tx = d.x;
d.ty = d.y;
d.colorEnd = colorScale(i / points.length)
});
// create the regl function with the new start and end points
const drawPoints = createDrawPoints(points);
const frameLoop = regl.frame(({ time }) => {
if (startTime === null) {
startTime = time;
}
// clear the buffer
regl.clear({
// background color (black)
color: [0, 0, 0, 1],
depth: 1,
});
// draw the points using our created regl func
drawPoints({
pointWidth,
stageWidth: width,
stageHeight: height,
duration,
delayByIndex,
startTime,
});
// if we have exceeded the maximum duration, move on to the next animation
if (time - startTime > (maxDuration / 1000)) {
console.log('done animating, moving to next layout');
frameLoop.cancel();
currentLayout = (currentLayout + 1) % layouts.length;
startTime = null;
currentColorScale = (currentColorScale + 1) % colorScales.length;
animate(layouts[currentLayout], points);
}
});
}
// create initial set of points
const points = createPoints(numPoints, pointWidth, width, height);
// initialize with all the points in the middle of the screen
points.forEach((d, i) => {
d.tx = width / 2;
d.ty = height / 2;
d.colorEnd = [0, 0, 0];
});
animate(layouts[currentLayout], points);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment