// <script src="https://unpkg.com/d3"></script>
// </head>
// <body>
// <!-- Create a placeholder for any controls -->
// <div id="controls"></div>
// <!-- Create the HTML canvas element -->
// <canvas id="plot" width="600" height="600"></canvas>
// <!-- Script can be here or linked to another file-->
// <script>
// // Grab the canvas
// const canvas = document.getElementById('plot')
// // Apply the '2d' context (as opposed to 'webgl')
// const ctx = canvas.getContext('2d')
// // Define visualization attributes
// const numPoints = 10000
// let pointWidth = 3
// // Categorical color scale with different sized groups
// const breakpointsPercent = [0.05, 0.2, 0.45, 0.85]
// const indexBreakpoints = breakpointsPercent.map(breakpoint => Math.floor(breakpoint * numPoints))
// const distinctColors = d3.quantize(d3.interpolatePlasma, 5)
// const colorScale = d3.scaleThreshold()
// .domain(indexBreakpoints)
// .range(distinctColors)
// // Generate the array of points with a unique ID and color
// const points = d3.range(numPoints).map(index => ({
// id: index,
// color: colorScale(index)
// }))
// // Specify the phyllotaxis layout
// // From https://bocoup.com/blog/smoothly-animate-thousands-of-points-with-html5-canvas-and-d3
// function phyllotaxisLayout(points, pointWidth, xOffset = canvas.width / 2, yOffset = canvas.height / 2, iOffset = 0) {
// // theta determines the spiral of the layout
// const theta = Math.PI * (3 - Math.sqrt(5))
// const pointRadius = pointWidth
// 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
// })
// }
// // Specify the bar chart layout
// function barChartLayout(points, pointWidth, pointMargin = 1) {
// const pointWithMargin = pointWidth + pointMargin
// const pointsPerLayer = 28 // how many points wide each bar is
// // Initialize each bar's starting position
// let xOffset = 0
// // Process each color bar
// for (let color of distinctColors) {
// // Filter points for the current color
// const currentPoints = points.filter(p => p.color === color)
// // Initialize the position for the current bar
// let rowIndex = 0
// let verticalIndex = 0
// // Process each point in the current bar
// for (let i = 0; i < currentPoints.length; i++) {
// const point = currentPoints[i]
// // Check if we need to start a new layer
// if (i !== 0 && i % pointsPerLayer === 0) {
// rowIndex = 0 // start a new layer
// verticalIndex++ // move the layer up
// }
// // Position the point in the current layer
// point.x = xOffset + (rowIndex * pointWithMargin)
// point.y = verticalIndex * pointWithMargin
// rowIndex++ // move to the next point in the layer
// }
// // Calculate the width of the current bar
// const barWidth = pointWithMargin * pointsPerLayer
// // Update the starting position for the next bar
// xOffset += barWidth + 5 // we're adding some space between bars
// }
// }
// // Drawing & animation settings
// const layouts = [phyllotaxisLayout, barChartLayout]
// let layoutIndex = 0
// function draw() {
// // erase any existing points on canvas before drawing
// ctx.clearRect(0, 0, canvas.width, canvas.height)
// // draw each point as a square
// 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()
// }
// // Draw the points in their first layout
// phyllotaxisLayout(points, pointWidth)
// draw()
// // Animation setup
// const animationDuration = 1500 // milliseconds
// let timer
// let isAnimating = false // track if animation is happening
// function animate(nextLayout) {
// if (isAnimating) return // do nothing if animation is in progress
// isAnimating = true // set flag to indicate animation is in progress
// // Stop any current animation
// if (timer) timer.stop()
// // Store the first position of the points
// points.forEach(point => {
// point.sx = point.x
// point.sy = point.y
// })
// // Apply the next layout to adjust point positions
// nextLayout(points, pointWidth)
// // Store the destination position of the points
// points.forEach(point => {
// point.tx = point.x
// point.ty = point.y
// })
// // Reset points to their starting positions for the animation.
// points.forEach(point => {
// point.x = point.sx
// point.y = point.sy
// })
// // Animate the interpolation of points from one position to another
// timer = d3.timer((elapsed) => {
// // Compute the current progress of the animation (0 to 1)
// const progress = Math.min(1, d3.easeCubic(elapsed / animationDuration))
// // Update point positions (interpolate between source and target)
// points.forEach(point => {
// point.x = point.sx + (point.tx - point.sx) * progress
// point.y = point.sy + (point.ty - point.sy) * progress
// })
// // Draw the update
// draw()
// // Stop the animation when the progress is complete
// if (progress === 1) {
// // stop current layout's timer and start new one
// timer.stop()
// isAnimating = false
// }
// })
// }
// // Create a "PLAY" button
// d3.select('#controls').append('button')
// .attr('class', 'play-control')
// .text('PLAY')
// .on('click', () => {
// if(!isAnimating) {
// const nextLayoutIndex = (layoutIndex + 1) % layouts.length
// const nextLayout = layouts[nextLayoutIndex]
// animate(nextLayout)
// layoutIndex = nextLayoutIndex
// }
// })
// </script>
// </body>
// </html>