/* global d3 */ /** * 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); }