Last active
March 21, 2025 08:55
-
-
Save pbeshai/65420c8d722cdbb0600b276c3adcc6e8 to your computer and use it in GitHub Desktop.
Revisions
-
Peter Beshai revised this gist
Mar 17, 2017 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -2,4 +2,4 @@ Using D3 and d3-transition works great when animating hundreds of points with SVG, but performance breaks down when you need to animate more than a thousand. This block demonstrates a simple approach to animating thousands of points between different layouts using canvas, d3-timer, and d3-ease. See the [blog post](https://bocoup.com/blog/smoothly-animate-thousands-of-points-with-html5-canvas-and-d3) for more details. -
pbeshai revised this gist
Mar 11, 2017 . 3 changed files with 1 addition and 8 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1 +0,0 @@ This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -4,7 +4,7 @@ <meta name='viewport' content='width=device-width, initial-scale=1'> <meta charset='UTF-8'> <script src="https://d3js.org/d3.v4.min.js"></script> <title>Animate thousands of points with canvas and D3</title> <style> html, body { padding: 0; This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +0,0 @@ -
pbeshai revised this gist
Mar 11, 2017 . 10 changed files with 331 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,5 @@ ### Animate thousands of points with canvas and D3 Using D3 and d3-transition works great when animating hundreds of points with SVG, but performance breaks down when you need to animate more than a thousand. This block demonstrates a simple approach to animating thousands of points between different layouts using canvas, d3-timer, and d3-ease. See the blog post for more details. (TBD). This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,150 @@ /** * 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); } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1 @@ .bar rect{fill:#4682b4}.bar text{fill:#fff;font:10px sans-serif} This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,2 @@ function draw(){var t=canvas.node().getContext("2d");t.save(),t.clearRect(0,0,width,height);for(var i=0;i<points.length;++i){var n=points[i];t.fillStyle=n.color,t.fillRect(n.x,n.y,pointWidth,pointWidth)}t.restore()}function animate(t){points.forEach(function(t){t.sx=t.x,t.sy=t.y}),t(points),points.forEach(function(t){t.tx=t.x,t.ty=t.y}),timer=d3.timer(function(t){var i=Math.min(1,ease(t/duration));points.forEach(function(t){t.x=t.sx*(1-i)+t.tx*i,t.y=t.sy*(1-i)+t.ty*i}),draw(),1===i&&(timer.stop(),currLayout=(currLayout+1)%layouts.length,animate(layouts[currLayout]))})}var width=600,height=600,numPoints=7e3,pointWidth=4,pointMargin=3,duration=1500,ease=d3.easeCubic,timer,currLayout=0,points=createPoints(numPoints,pointWidth,width,height),toGrid=function(t){return gridLayout(t,pointWidth+pointMargin,width)},toSine=function(t){return sineLayout(t,pointWidth+pointMargin,width,height)},toSpiral=function(t){return spiralLayout(t,pointWidth+pointMargin,width,height)},toPhyllotaxis=function(t){return phyllotaxisLayout(t,pointWidth+pointMargin,width/2,height/2)},layouts=[toSine,toPhyllotaxis,toSpiral,toPhyllotaxis,toGrid],screenScale=window.devicePixelRatio||1,canvas=d3.select("body").append("canvas").attr("width",width*screenScale).attr("height",height*screenScale).style("width",width+"px").style("height",height+"px").on("click",function(){d3.select(".play-control").style("display",""),timer.stop()});canvas.node().getContext("2d").scale(screenScale,screenScale),toGrid(points),draw(),d3.select("body").append("div").attr("class","play-control").text("PLAY").on("click",function(){animate(layouts[currLayout]),d3.select(this).style("display","none")}); //# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNjcmlwdC5qcyJdLCJuYW1lcyI6WyJkcmF3IiwiY29uc3QiLCJjdHgiLCJjYW52YXMiLCJub2RlIiwiZ2V0Q29udGV4dCIsInNhdmUiLCJjbGVhclJlY3QiLCJ3aWR0aCIsImhlaWdodCIsImxldCIsImkiLCJwb2ludHMiLCJsZW5ndGgiLCJwb2ludCIsImZpbGxTdHlsZSIsImNvbG9yIiwiZmlsbFJlY3QiLCJ4IiwieSIsInBvaW50V2lkdGgiLCJyZXN0b3JlIiwiYW5pbWF0ZSIsImxheW91dCIsImZvckVhY2giLCJzeCIsInN5IiwidHgiLCJ0eSIsInRpbWVyIiwiZDMiLCJlbGFwc2VkIiwidCIsIk1hdGgiLCJtaW4iLCJlYXNlIiwiZHVyYXRpb24iLCJzdG9wIiwiY3VyckxheW91dCIsImxheW91dHMiLCJudW1Qb2ludHMiLCJwb2ludE1hcmdpbiIsImVhc2VDdWJpYyIsImNyZWF0ZVBvaW50cyIsInRvR3JpZCIsImdyaWRMYXlvdXQiLCJ0b1NpbmUiLCJzaW5lTGF5b3V0IiwidG9TcGlyYWwiLCJzcGlyYWxMYXlvdXQiLCJ0b1BoeWxsb3RheGlzIiwicGh5bGxvdGF4aXNMYXlvdXQiLCJzY3JlZW5TY2FsZSIsIndpbmRvdyIsImRldmljZVBpeGVsUmF0aW8iLCJzZWxlY3QiLCJhcHBlbmQiLCJhdHRyIiwic3R5bGUiLCJvbiIsInNjYWxlIiwidGV4dCIsInRoaXMiXSwibWFwcGluZ3MiOiJBQWdDQSxRQUFTQSxRQUNQQyxHQUFNQyxHQUFNQyxPQUFPQyxPQUFPQyxXQUFXLEtBQ3JDSCxHQUFJSSxPQUdKSixFQUFJSyxVQUFVLEVBQUcsRUFBR0MsTUFBT0MsT0FHM0IsS0FBS0MsR0FBSUMsR0FBSSxFQUFHQSxFQUFJQyxPQUFPQyxTQUFVRixFQUFHLENBQ3RDVixHQUFNYSxHQUFRRixPQUFTRCxFQUN2QlQsR0FBSWEsVUFBWUQsRUFBTUUsTUFDdEJkLEVBQUllLFNBQVNILEVBQU1JLEVBQUdKLEVBQU1LLEVBQUdDLFdBQVlBLFlBRzdDbEIsRUFBSW1CLFVBSU4sUUFBU0MsU0FBUUMsR0FFZlgsT0FBT1ksUUFBUSxTQUFBVixHQUNiQSxFQUFNVyxHQUFLWCxFQUFNSSxFQUNqQkosRUFBTVksR0FBS1osRUFBTUssSUFJbkJJLEVBQU9YLFFBR1BBLE9BQU9ZLFFBQVEsU0FBQVYsR0FDYkEsRUFBTWEsR0FBS2IsRUFBTUksRUFDakJKLEVBQU1jLEdBQUtkLEVBQU1LLElBR25CVSxNQUFRQyxHQUFHRCxNQUFNLFNBQUFFLEdBRWY5QixHQUFPK0IsR0FBR0MsS0FBS0MsSUFBSyxFQUFFQyxLQUFLSixFQUFVSyxVQUdyQ3hCLFFBQU9ZLFFBQVEsU0FBQVYsR0FDYkEsRUFBTUksRUFBSUosRUFBTVcsSUFBTSxFQUFJTyxHQUFLbEIsRUFBTWEsR0FBS0ssRUFDMUNsQixFQUFNSyxFQUFJTCxFQUFNWSxJQUFNLEVBQUlNLEdBQUtsQixFQUFNYyxHQUFLSSxJQUk1Q2hDLE9BR1UsSUFBTmdDLElBRUZILE1BQU1RLE9BR05DLFlBQWNBLFdBQWEsR0FBS0MsUUFBUTFCLE9BR3hDUyxRQUFRaUIsUUFBUUQsZ0JBdkZ0QnJDLEdBQU1PLE9BQVEsSUFDUkMsT0FBUyxJQUdUK0IsVUFBWSxJQUNacEIsV0FBZSxFQUNmcUIsWUFBZ0IsRUFHaEJMLFNBQVcsS0FDWEQsS0FBU0wsR0FBQ1ksVUFDWmIsTUFDQVMsV0FBYSxFQUdYMUIsT0FBUytCLGFBQWFILFVBQVdwQixXQUFZWixNQUFPQyxRQUdwRG1DLE9BQVMsU0FBQWhDLEdBQUMsTUFBQWlDLFlBQVFqQyxFQUN0QlEsV0FBYXFCLFlBQWFqQyxRQUN0QnNDLE9BQVMsU0FBQWxDLEdBQUMsTUFBQW1DLFlBQVFuQyxFQUN0QlEsV0FBYXFCLFlBQWFqQyxNQUFPQyxTQUM3QnVDLFNBQVcsU0FBQXBDLEdBQUMsTUFBQXFDLGNBQVdyQyxFQUMzQlEsV0FBYXFCLFlBQWFqQyxNQUFPQyxTQUM3QnlDLGNBQWdCLFNBQUF0QyxHQUFDLE1BQUF1QyxtQkFBV3ZDLEVBQ2hDUSxXQUFhcUIsWUFBYWpDLE1BQVEsRUFBR0MsT0FBUyxJQUcxQzhCLFNBQVdPLE9BQVFJLGNBQWVGLFNBQVVFLGNBQWVOLFFBaUUzRFEsWUFBY0MsT0FBT0Msa0JBQXNCLEVBQzNDbkQsT0FBVzJCLEdBQUN5QixPQUFPLFFBQVFDLE9BQU8sVUFDckNDLEtBQUssUUFBU2pELE1BQVE0QyxhQUN0QkssS0FBSyxTQUFVaEQsT0FBUzJDLGFBQ3hCTSxNQUFNLFFBQVNsRCxNQUFRLE1BQ3ZCa0QsTUFBTSxTQUFVakQsT0FBUyxNQUN6QmtELEdBQUcsUUFBUyxXQUNYN0IsR0FBR3lCLE9BQU8saUJBQWlCRyxNQUFNLFVBQVcsSUFDNUM3QixNQUFNUSxRQUVWbEMsUUFBT0MsT0FBT0MsV0FBVyxNQUFNdUQsTUFBTVIsWUFBYUEsYUFHbERSLE9BQU9oQyxRQUNQWixPQUVBOEIsR0FBR3lCLE9BQU8sUUFBUUMsT0FBTyxPQUN0QkMsS0FBSyxRQUFTLGdCQUNkSSxLQUFLLFFBQ0xGLEdBQUcsUUFBUyxXQUVYckMsUUFBUWlCLFFBQVFELGFBR2hCUixHQUFHeUIsT0FBT08sTUFBTUosTUFBTSxVQUFXIiwiZmlsZSI6InNjcmlwdC5qcyIsInNvdXJjZXNDb250ZW50IjpbIi8vIGNhbnZhcyBzZXR0aW5nc1xuY29uc3Qgd2lkdGggPSA2MDA7XG5jb25zdCBoZWlnaHQgPSA2MDA7XG5cbi8vIHBvaW50IHNldHRpbmdzXG5jb25zdCBudW1Qb2ludHMgPSA3MDAwO1xuY29uc3QgcG9pbnRXaWR0aCA9IDQ7XG5jb25zdCBwb2ludE1hcmdpbiA9IDM7XG5cbi8vIGFuaW1hdGlvbiBzZXR0aW5nc1xuY29uc3QgZHVyYXRpb24gPSAxNTAwO1xuY29uc3QgZWFzZSA9IGQzLmVhc2VDdWJpYztcbmxldCB0aW1lcjtcbmxldCBjdXJyTGF5b3V0ID0gMDtcblxuLy8gY3JlYXRlIHNldCBvZiBwb2ludHNcbmNvbnN0IHBvaW50cyA9IGNyZWF0ZVBvaW50cyhudW1Qb2ludHMsIHBvaW50V2lkdGgsIHdpZHRoLCBoZWlnaHQpO1xuXG4vLyB3cmFwIGxheW91dCBoZWxwZXJzIHNvIHRoZXkgb25seSB0YWtlIHBvaW50cyBhcyBhbiBhcmd1bWVudFxuY29uc3QgdG9HcmlkID0gKHBvaW50cykgPT4gZ3JpZExheW91dChwb2ludHMsXG4gIHBvaW50V2lkdGggKyBwb2ludE1hcmdpbiwgd2lkdGgpO1xuY29uc3QgdG9TaW5lID0gKHBvaW50cykgPT4gc2luZUxheW91dChwb2ludHMsXG4gIHBvaW50V2lkdGggKyBwb2ludE1hcmdpbiwgd2lkdGgsIGhlaWdodCk7XG5jb25zdCB0b1NwaXJhbCA9IChwb2ludHMpID0+IHNwaXJhbExheW91dChwb2ludHMsXG4gIHBvaW50V2lkdGggKyBwb2ludE1hcmdpbiwgd2lkdGgsIGhlaWdodCk7XG5jb25zdCB0b1BoeWxsb3RheGlzID0gKHBvaW50cykgPT4gcGh5bGxvdGF4aXNMYXlvdXQocG9pbnRzLFxuICBwb2ludFdpZHRoICsgcG9pbnRNYXJnaW4sIHdpZHRoIC8gMiwgaGVpZ2h0IC8gMik7XG5cbi8vIHN0b3JlIHRoZSBsYXlvdXRzIGluIGFuIGFycmF5IHRvIHNlcXVlbmNlIHRocm91Z2hcbmNvbnN0IGxheW91dHMgPSBbdG9TaW5lLCB0b1BoeWxsb3RheGlzLCB0b1NwaXJhbCwgdG9QaHlsbG90YXhpcywgdG9HcmlkXTtcblxuLy8gZHJhdyB0aGUgcG9pbnRzIGJhc2VkIG9uIHRoZWlyIGN1cnJlbnQgbGF5b3V0XG5mdW5jdGlvbiBkcmF3KCkge1xuICBjb25zdCBjdHggPSBjYW52YXMubm9kZSgpLmdldENvbnRleHQoJzJkJyk7XG4gIGN0eC5zYXZlKCk7XG5cbiAgLy8gZXJhc2Ugd2hhdCBpcyBvbiB0aGUgY2FudmFzIGN1cnJlbnRseVxuICBjdHguY2xlYXJSZWN0KDAsIDAsIHdpZHRoLCBoZWlnaHQpO1xuXG4gIC8vIGRyYXcgZWFjaCBwb2ludCBhcyBhIHJlY3RhbmdsZVxuICBmb3IgKGxldCBpID0gMDsgaSA8IHBvaW50cy5sZW5ndGg7ICsraSkge1xuICAgIGNvbnN0IHBvaW50ID0gcG9pbnRzW2ldO1xuICAgIGN0eC5maWxsU3R5bGUgPSBwb2ludC5jb2xvcjtcbiAgICBjdHguZmlsbFJlY3QocG9pbnQueCwgcG9pbnQueSwgcG9pbnRXaWR0aCwgcG9pbnRXaWR0aCk7XG4gIH1cblxuICBjdHgucmVzdG9yZSgpO1xufVxuXG4vLyBhbmltYXRlIHRoZSBwb2ludHMgdG8gYSBnaXZlbiBsYXlvdXRcbmZ1bmN0aW9uIGFuaW1hdGUobGF5b3V0KSB7XG4gIC8vIHN0b3JlIHRoZSBzb3VyY2UgcG9zaXRpb25cbiAgcG9pbnRzLmZvckVhY2gocG9pbnQgPT4ge1xuICAgIHBvaW50LnN4ID0gcG9pbnQueDtcbiAgICBwb2ludC5zeSA9IHBvaW50Lnk7XG4gIH0pO1xuXG4gIC8vIGdldCBkZXN0aW5hdGlvbiB4IGFuZCB5IHBvc2l0aW9uIG9uIGVhY2ggcG9pbnRcbiAgbGF5b3V0KHBvaW50cyk7XG5cbiAgLy8gc3RvcmUgdGhlIGRlc3RpbmF0aW9uIHBvc2l0aW9uXG4gIHBvaW50cy5mb3JFYWNoKHBvaW50ID0+IHtcbiAgICBwb2ludC50eCA9IHBvaW50Lng7XG4gICAgcG9pbnQudHkgPSBwb2ludC55O1xuICB9KTtcblxuICB0aW1lciA9IGQzLnRpbWVyKChlbGFwc2VkKSA9PiB7XG4gICAgLy8gY29tcHV0ZSBob3cgZmFyIHRocm91Z2ggdGhlIGFuaW1hdGlvbiB3ZSBhcmUgKDAgdG8gMSlcbiAgICBjb25zdCB0ID0gTWF0aC5taW4oMSwgZWFzZShlbGFwc2VkIC8gZHVyYXRpb24pKTtcblxuICAgIC8vIHVwZGF0ZSBwb2ludCBwb3NpdGlvbnMgKGludGVycG9sYXRlIGJldHdlZW4gc291cmNlIGFuZCB0YXJnZXQpXG4gICAgcG9pbnRzLmZvckVhY2gocG9pbnQgPT4ge1xuICAgICAgcG9pbnQueCA9IHBvaW50LnN4ICogKDEgLSB0KSArIHBvaW50LnR4ICogdDtcbiAgICAgIHBvaW50LnkgPSBwb2ludC5zeSAqICgxIC0gdCkgKyBwb2ludC50eSAqIHQ7XG4gICAgfSk7XG5cbiAgICAvLyB1cGRhdGUgd2hhdCBpcyBkcmF3biBvbiBzY3JlZW5cbiAgICBkcmF3KCk7XG5cbiAgICAvLyBpZiB0aGlzIGFuaW1hdGlvbiBpcyBvdmVyXG4gICAgaWYgKHQgPT09IDEpIHtcbiAgICAgIC8vIHN0b3AgdGhpcyB0aW1lciBmb3IgdGhpcyBsYXlvdXQgYW5kIHN0YXJ0IGEgbmV3IG9uZVxuICAgICAgdGltZXIuc3RvcCgpO1xuXG4gICAgICAvLyB1cGRhdGUgdG8gdXNlIG5leHQgbGF5b3V0XG4gICAgICBjdXJyTGF5b3V0ID0gKGN1cnJMYXlvdXQgKyAxKSAlIGxheW91dHMubGVuZ3RoO1xuXG4gICAgICAvLyBzdGFydCBhbmltYXRpb24gZm9yIG5leHQgbGF5b3V0XG4gICAgICBhbmltYXRlKGxheW91dHNbY3VyckxheW91dF0pO1xuICAgIH1cbiAgfSk7XG59XG5cbi8vIGNyZWF0ZSB0aGUgY2FudmFzXG5jb25zdCBzY3JlZW5TY2FsZSA9IHdpbmRvdy5kZXZpY2VQaXhlbFJhdGlvIHx8IDE7XG5jb25zdCBjYW52YXMgPSBkMy5zZWxlY3QoJ2JvZHknKS5hcHBlbmQoJ2NhbnZhcycpXG4gIC5hdHRyKCd3aWR0aCcsIHdpZHRoICogc2NyZWVuU2NhbGUpXG4gIC5hdHRyKCdoZWlnaHQnLCBoZWlnaHQgKiBzY3JlZW5TY2FsZSlcbiAgLnN0eWxlKCd3aWR0aCcsIGAke3dpZHRofXB4YClcbiAgLnN0eWxlKCdoZWlnaHQnLCBgJHtoZWlnaHR9cHhgKVxuICAub24oJ2NsaWNrJywgZnVuY3Rpb24gKCkge1xuICAgIGQzLnNlbGVjdCgnLnBsYXktY29udHJvbCcpLnN0eWxlKCdkaXNwbGF5JywgJycpO1xuICAgIHRpbWVyLnN0b3AoKTtcbiAgfSk7XG5jYW52YXMubm9kZSgpLmdldENvbnRleHQoJzJkJykuc2NhbGUoc2NyZWVuU2NhbGUsIHNjcmVlblNjYWxlKTtcblxuLy8gc3RhcnQgb2ZmIGFzIGEgZ3JpZFxudG9HcmlkKHBvaW50cyk7XG5kcmF3KCk7XG5cbmQzLnNlbGVjdCgnYm9keScpLmFwcGVuZCgnZGl2JylcbiAgLmF0dHIoJ2NsYXNzJywgJ3BsYXktY29udHJvbCcpXG4gIC50ZXh0KCdQTEFZJylcbiAgLm9uKCdjbGljaycsIGZ1bmN0aW9uICgpIHtcbiAgICAvLyBzdGFydCB0aGUgYW5pbWF0aW9uXG4gICAgYW5pbWF0ZShsYXlvdXRzW2N1cnJMYXlvdXRdKTtcblxuICAgIC8vIHJlbW92ZSB0aGUgcGxheSBjb250cm9sXG4gICAgZDMuc2VsZWN0KHRoaXMpLnN0eWxlKCdkaXNwbGF5JywgJ25vbmUnKTtcbiAgfSk7XG4iXX0= This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,2 @@ 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 This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,45 @@ <!DOCTYPE html> <html lang="en"> <head> <meta name='viewport' content='width=device-width, initial-scale=1'> <meta charset='UTF-8'> <script src="https://d3js.org/d3.v4.min.js"></script> <title>Canvas Particles</title> <style> html, body { padding: 0; margin: 0; } canvas { cursor: pointer; } .play-control { position: absolute; top: 0px; left: 0px; width: 600px; height: 600px; line-height: 600px; text-align: center; background-color: rgba(0, 0, 0, 0.1); color: #f4f4f4; text-shadow: rgba(0, 0, 0, 0.7) 3px 3px 0px; font-size: 100px; font-family: 'helvetica neue', calibri, sans-serif; font-weight: 100; cursor: pointer; } .play-control:hover { color: #fff; text-shadow: #000 3px 3px 0px; background-color: rgba(0, 0, 0, 0.04); } </style> </head> <body> <script src="dist_common.js"></script> <script src="dist.js"></script> </body> </html> LoadingSorry, something went wrong. Reload?Sorry, we cannot display this file.Sorry, this file is invalid so it cannot be displayed.This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,120 @@ // canvas settings const width = 600; const height = 600; // point settings const numPoints = 7000; const pointWidth = 4; const pointMargin = 3; // animation settings const duration = 1500; const ease = d3.easeCubic; let timer; let currLayout = 0; // create set of points const points = createPoints(numPoints, pointWidth, width, height); // wrap layout helpers so they only take points as an argument 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); const toPhyllotaxis = (points) => phyllotaxisLayout(points, pointWidth + pointMargin, width / 2, height / 2); // store the layouts in an array to sequence through const layouts = [toSine, toPhyllotaxis, toSpiral, toPhyllotaxis, toGrid]; // draw the points based on their current layout function draw() { const ctx = canvas.node().getContext('2d'); ctx.save(); // erase what is on the canvas currently ctx.clearRect(0, 0, width, height); // draw each point as a rectangle for (let i = 0; i < points.length; ++i) { const point = points[i]; ctx.fillStyle = point.color; ctx.fillRect(point.x, point.y, pointWidth, pointWidth); } ctx.restore(); } // animate the points to a given layout function animate(layout) { // store the source position points.forEach(point => { point.sx = point.x; point.sy = point.y; }); // get destination x and y position on each point layout(points); // store the destination position points.forEach(point => { point.tx = point.x; point.ty = point.y; }); timer = d3.timer((elapsed) => { // compute how far through the animation we are (0 to 1) const t = Math.min(1, ease(elapsed / duration)); // update point positions (interpolate between source and target) points.forEach(point => { point.x = point.sx * (1 - t) + point.tx * t; point.y = point.sy * (1 - t) + point.ty * t; }); // update what is drawn on screen draw(); // if this animation is over if (t === 1) { // stop this timer for this layout and start a new one timer.stop(); // update to use next layout currLayout = (currLayout + 1) % layouts.length; // start animation for next layout animate(layouts[currLayout]); } }); } // create the canvas const screenScale = window.devicePixelRatio || 1; const canvas = d3.select('body').append('canvas') .attr('width', width * screenScale) .attr('height', height * screenScale) .style('width', `${width}px`) .style('height', `${height}px`) .on('click', function () { d3.select('.play-control').style('display', ''); timer.stop(); }); canvas.node().getContext('2d').scale(screenScale, screenScale); // start off as a grid toGrid(points); draw(); d3.select('body').append('div') .attr('class', 'play-control') .text('PLAY') .on('click', function () { // start the animation animate(layouts[currLayout]); // remove the play control d3.select(this).style('display', 'none'); }); This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,6 @@ .bar rect fill steelblue .bar text fill #fff font 10px sans-serif LoadingSorry, something went wrong. Reload?Sorry, we cannot display this file.Sorry, this file is invalid so it cannot be displayed. -
Peter Beshai created this gist
Mar 11, 2017 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,3 @@ license: mit height: 620 border: no