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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more