Public
Edited
Oct 29, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
canvasElement = html``
Insert cell
Insert cell
Insert cell
canvas = null
Insert cell
ctx = null
Insert cell
Insert cell
numPoints = null
Insert cell
pointWidth = null
Insert cell
Insert cell
distinctColors = null
Insert cell
colorScale = null
Insert cell
Insert cell
layouts = []
Insert cell
mutable layoutIndex = null
Insert cell
Insert cell
function draw() {
null
}
Insert cell
null
Insert cell
null
Insert cell
Insert cell
animationDuration = null
Insert cell
mutable timer = null
Insert cell
mutable isAnimating = null
Insert cell
function animate(nextLayout) {
// do nothing if animation is in progress

// set flag to indicate animation is in progress

// Stop any current animation

// Store the first position of the points

// Apply the next layout to adjust point positions

// Store the destination position of the points

// Reset points to their starting positions for the animation.

// Animate the interpolation of points from one position to another
mutable timer = d3.timer((elapsed) => {
// Compute the current progress of the animation (0 to 1)

// Update point positions (interpolate between source and target)

// Draw the update
draw()

// Stop the animation when the progress is complete
})
}
Insert cell
Insert cell
d3.select('.play-control')
.on('click', () => {
})
Insert cell
Insert cell
Insert cell
breakpointsPercent = [0.05, 0.2, 0.45, 0.85]
Insert cell
indexBreakpoints = breakpointsPercent.map(breakpoint => Math.floor(breakpoint * numPoints))
Insert cell
points = d3.range(numPoints).map(index => ({
id: index,
color: colorScale(index)
}))
Insert cell
Insert cell
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
})
}
Insert cell
Insert cell
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
}
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more