Public
Edited
Aug 8, 2022
3 forks
33 stars
Insert cell
Insert cell
Insert cell
{
const width = 1000;
const height = 900;
const radius = 1.7;
const padding = 1;
const n = 10000;

// bring in the animation control buttons
const play = document.querySelector("#play");
const pause = document.querySelector("#pause");
const restart = document.querySelector("#restart");
const reverse = document.querySelector("#reverse");

const c = DOM.context2d(width, height);
c.translate(250, 250);

/// Starting data ///
// these are the particles that we are going to animate - store positional or other animatable arguments
const circles = d3.packSiblings(d3.range(n).map(() => ({
r: radius + padding,
fill: '#ffafcc'
})));
// add some random group element that we can use to split the particles on
circles.forEach((circle, i) => {
circle.group = i % 3 === 0? 'pandas' : 'cats' // these are... completely random
})

/// New position of data ///

// 1. Create new packSiblings for each of our groups - so that the circles are packed tight together
// if we don't do that it will still work but the new circle packings will have the original (x, y)
// positions so will look very 'sparse'
const groups = ['pandas', 'cats']
const circlesPerGroup = {} // store in object with keys = the gorups
for (const group of groups) {
circlesPerGroup[group] = d3.packSiblings(d3.range(
circles.filter(circle => circle.group === group).length
).map(() => ({
r: radius + padding,
group: group
})))
};

// 2. Create the new circles by looping over the groups
const xOffset = 500; // how far right to move horizontally from initial position
const yOffsetPerGroup = 350; // this can also be defined with a scale if we have more groups
const movedCircles = []
for (const [group, groupCircles] of Object.entries(circlesPerGroup)) {
const groupNumber = groups.indexOf(group)
groupCircles.forEach((circle, index) => {
movedCircles.push({
x: groupCircles[index].x + xOffset,
y: groupCircles[index].y + yOffsetPerGroup*groupNumber,
fill: circle.group === 'cats'? '#76c893' : '#1a759f', // can be done with a colour scheme
group: circle.group
})
})
}
/// Animation ///
// animate the x,y pos and the fill s.t. x and y are moved to the position of the moved circles,
// which we can access via the index; stagger the particles
const tl = gsap.to(circles, {
x: (index, target, targets) => movedCircles[index].x,
y: (index, target, targets) => movedCircles[index].y,
fill: (index, target, targets) => movedCircles[index].fill,
duration: 1,
ease: 'power.3.out',
stagger: { amount: 2 },
onUpdate: animate
});
tl.pause() // pause to begin with; play if button is clicked
// this is what happens on update of the gsap animation
function animate() {
c.fillStyle = "#22223b"
c.fillRect(-250, -250, 1200, 1200); // make sure to clear or fill the rect
for (const { x, y, r, fill } of circles) {
c.beginPath();
c.arc(x, y, r-padding, 0, 2*Math.PI)
c.fillStyle = fill
c.fill()
}
}

// run the drawing logic once just so that we can see the initial circle packing
animate()

// button controls to be able to play/pause etc the gsap animation
play.onclick = () => tl.play();
restart.onclick = () => tl.restart()
pause.onclick = () => tl.pause();
reverse.onclick = () => tl.reverse();
yield c.canvas;
}
Insert cell
Insert cell
{

const width = 750;
const height = 650;
const radius = 2;
const padding = 1;
const n = 4000;
const c = DOM.context2d(width, height);
c.translate(200, 200);

/// Starting data ///
// these are the particles that we are going to animate, so
// make sure that any positional or other animatable arguments are stored here
const circles = d3.packSiblings(d3.range(n).map(() => ({
r: radius + padding,
fill: 'rebeccapurple'
})));

/// New position of data ///
// these are the new positions etc of the particles
// easiest way to do this is to just create a new array out of the
// starting data and then move the (x, y) coords etc to new position
const movedCircles = []
circles.forEach(circle => {
movedCircles.push({
x: circle.x + 300,
y: circle.y + 200,
fill: '#76c893' // will also animate the fill
})
});
/// Animation ///
// animate the x,y pos and the fill s.t. x and y are moved to the position of the moved circles,
// which we can access via the index; we can also stagger the particles so that they move very smoothly
gsap.to(circles, {
x: (index, target, targets) => movedCircles[index].x,
y: (index, target, targets) => movedCircles[index].y,
fill: (index, target, targets) => movedCircles[index].fill,
duration: 0.5,
ease: 'power.3.out',
stagger: { amount: 10 },
onUpdate: animate
});

// this is what happens on update of the gsap animation
function animate() {
c.fillStyle = "white"
c.fillRect(-200, -200, 700, 700); // make sure to clear or fill the rect
for (const { x, y, r, fill } of circles) {
c.beginPath();
c.arc(x, y, r-padding, 0, 2*Math.PI);
c.fillStyle = fill;
c.fill();
}
}
yield c.canvas;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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