Public
Edited
Oct 11, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const width = 100 // adjust
const height = 100 // adjust
const ctx = DOM.context2d(width, height)


/////////////////////////////
// Your drawing logic here //
/////////////////////////////
yield ctx.canvas
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const width = 200 // adjust
const height = 200 // adjust
const ctx = DOM.context2d(width, height)

/////////////////////////////
// Your drawing logic here //
/////////////////////////////
ctx.fillStyle= 'purple'
ctx.fillRect(50,50,150,100)

yield ctx.canvas
}
Insert cell
{
const width = 200
const height = 100
const ctx = DOM.context2d(width, height);

// Draw a rectangle
ctx.fillStyle = "rgb(200, 0, 200)" // accepts rgb, rgba, hex, colour name, etc. https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
ctx.fillRect(0, 0, 100, 100); // x, y, width, height

yield ctx.canvas;
}
Insert cell
Insert cell
{
const width = 500
const height = 300
const ctx = DOM.context2d(width, height)

// styling before .fillRect()
ctx.fillStyle = "rgb(10, 200, 150)"
ctx.fillRect(100, 100, 100, 100)

ctx.fillStyle = "rgb(200, 10, 240)"
ctx.fillRect(0, 0, 150, 70)

ctx.strokeStyle = "rgb(100, 0, 200)"
ctx.lineWidth = 10
ctx.strokeRect(250, 150, 100, 130)
yield ctx.canvas
}
Insert cell
Insert cell
Insert cell
{
const width = 500 // adjust
const height = 240 // adjust
const ctx = DOM.context2d(width, height)

/////////////////////////////
// Your drawing logic here //
/////////////////////////////
ctx.beginPath()
ctx.fillStyle="blue"
ctx.moveTo(100, 0)
ctx.lineTo(200, 200)
ctx.lineTo(0, 200)
ctx.closePath()
ctx.strokeStyle="Pink"
ctx.lineWidth=4
ctx.stroke()
ctx.fill()
yield ctx.canvas
}
Insert cell
{
const width = 500
const height = 240
const ctx = DOM.context2d(width, height)

// A triangle
ctx.beginPath()
ctx.moveTo(100, 50)
ctx.lineTo(200, 200)
ctx.lineTo(50, 200)
ctx.fillStyle = 'rgb(100, 10, 150)'
ctx.closePath()
ctx.fill()

yield ctx.canvas
}
Insert cell
Insert cell
{
const width = 1200 // adjust
const height = 400 // adjust
const ctx = DOM.context2d(width, height)

/////////////////////////////
// Your drawing logic here //
/////////////////////////////

for (let i =0; i <50; i++){

const xStart = Math.random() * width
const yStart = Math.random() * height
const x1 = 200
const y1 = 200
const x2 = 50
const y2 = 200
const fill = `rgb(${Math.random() * 255}, ${Math.random() * 255}, ${Math.random() * 255})`
// A triangle
ctx.beginPath()
ctx.moveTo(xStart, yStart)
ctx.lineTo(x1, y1)
ctx.lineTo(x2, y2)
ctx.fillStyle = 'rgb(100, 10, 150)'
ctx.closePath()
ctx.fill()
}
yield ctx.canvas
}
Insert cell
{
const width = 1200
const height = 400
const ctx = DOM.context2d(width, height);

// Generative traingles
for (let i = 0; i < 400; i++) {
const startX = Math.random()*width
const startY = Math.random()*height
const offset = Math.random()*100
const x1 = startX + offset
const y1 = startY + offset
const x2 = x1 - offset*2
const y2 = y1
ctx.beginPath()
ctx.moveTo(startX, startY)
ctx.lineTo(x1, y1)
ctx.lineTo(x2, y2)
ctx.fillStyle = `rgba(
${Math.max(120, Math.random()*255)},
${Math.floor(Math.random()*10)},
${Math.max(180, Math.random()*255)},
${Math.random()*2}
)`
ctx.closePath()
ctx.stroke()
ctx.fill()
}

yield ctx.canvas;
}
Insert cell
Insert cell
Insert cell
{
const width = 200 // adjust
const height = 200 // adjust
const ctx = DOM.context2d(width, height)

/////////////////////////////
// Your drawing logic here //
/////////////////////////////
ctx.beginPath()
ctx.fillStyle='blue'
ctx.strokeStyle='orange'
ctx.lineWidth = 5
ctx.arc(100, 100, 70, 0, Math.PI*2)
ctx.fill()
ctx.stroke()
yield ctx.canvas
}
Insert cell
{
const width = 200
const height = 200
const ctx = DOM.context2d(width, height);

ctx.arc(100, 100, 50, 0, Math.PI*2, false)
ctx.fillStyle = 'purple'
ctx.fill()
ctx.stroke()

yield ctx.canvas;
}
Insert cell
Insert cell
{
const width = 1200
const height = 400
const ctx = DOM.context2d(width, height);

for (let i = 0; i < 200; i++) {
const cx = Math.random()*width
const cy = Math.random()*height
const r = Math.random()*50
const fill = `rgba(
${10},
${Math.max(100, Math.random()*255)},
${Math.max(180, Math.random()*255)},
${Math.random()*2}
)`
const lineWidth = Math.random()*10

ctx.beginPath()
ctx.arc(cx, cy, r, 0, Math.PI * 2)
ctx.fillStyle = fill
ctx.lineWidth = lineWidth
ctx.stroke()
ctx.fill()
ctx.closePath()
}

yield ctx.canvas;
}
Insert cell
Insert cell
Insert cell
{
const width = 300
const height = 300
const ctx = DOM.context2d(width, height);

// Create the path
const p = new Path2D('M0,0 C50,40 50,70 20,100 L0,85 L-20,100 C-50,70 -50,40 0,0') // copied from svg
// Settings
ctx.fillStyle = 'pink'
ctx.translate(100, 100)

// Draw the petals
ctx.beginPath()
//ctx.fill(p)
ctx.stroke(p)
yield ctx.canvas;
}
Insert cell
{
const width = 300
const height = 300
const ctx = DOM.context2d(width, height);

// Sample pre-defined SVG path strings: Credit to Shirley Wu
const petalPaths = [ // contains different petal stykes
'M0 0 C50 50 50 100 0 100 C-50 100 -50 50 0 0',
'M0,0 C50,40 50,70 20,100 L0,85 L-20,100 C-50,70 -50,40 0,0',
'M0 0 C50 25 50 75 0 100 C-50 75 -50 25 0 0',
]

const drawFlower = (colour='pink', petalsIncrement=Math.PI*0.05) => {

// Create the path
const d = petalPaths[1]
const p = new Path2D(d)
// Settings
ctx.fillStyle = colour
ctx.translate(100, 100)

// Draw the petals
// Lodash
ctx.beginPath()
_.range(0, Math.PI*2, petalsIncrement).map(i => {
ctx.rotate(i)
ctx.fill(p)
ctx.stroke(p)
})
}

drawFlower('pink', Math.PI*0.1)
yield ctx.canvas;
}
Insert cell
_.range(10).map(i => ({
id: i,
x: i * Math.random()})
Insert cell
Insert cell
{
const width = 1200
const height = 500
const ctx = DOM.context2d(width, height);

const petalPaths = [
'M0 0 C50 50 50 100 0 100 C-50 100 -50 50 0 0',
'M0,0 C50,40 50,70 20,100 L0,85 L-20,100 C-50,70 -50,40 0,0',
'M0 0 C50 25 50 75 0 100 C-50 75 -50 25 0 0',
]

const drawFlower = (colour='pink', scale = 1, x=100, y=100, petalsIncrement=Math.PI*0.05) => {
// Create the path
const d = petalPaths[1]
const p = new Path2D(d)
// Settings
ctx.save() // start new settings
// THIS
ctx.fillStyle = colour
ctx.translate(x, y)
ctx.scale(scale, scale)
// Draw the petals
ctx.beginPath()
_.range(0, Math.PI*2, petalsIncrement).map(i => {
ctx.rotate(i)
ctx.fill(p)
ctx.stroke(p)
})
ctx.restore() // restore to default settings - for scale and translate
}

// Set the background
ctx.lineWidth = 2
ctx.fillStyle = '#E5B80B'
ctx.strokeStyle = 'gold'
ctx.fillRect(0, 0, width, height)

// Call the drawFlower function 50 times
for (let i = 0; i < 50; i++) {
const scale = Math.max(0.4, Math.random())
const x = Math.random()*width
const y = Math.random()*height
// Draw larger pink flowers
drawFlower('#ffb7c5', scale, x, y, Math.PI*0.1)
// Draw smaller white flowers on top of the pink ones
drawFlower('#fff', scale*0.5, x, y, Math.PI*0.5)
}
yield ctx.canvas;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const width = 1200
const height = 200
const ctx = DOM.context2d(width, height)

let cx =0
const drawCircle = () => {
ctx.fillStyle = 'purple'
ctx.beginPath()
ctx.arc(100, 100, 50, 0, Math.PI*2, false)
ctx.fill()
ctx.stroke()

}
drawCircle()
yield ctx.convas

}
Insert cell
{
const width = 1200
const height = 200
const ctx = DOM.context2d(width, height)

let cx = 0
const drawCircle = () => {
// Make sure to clear the background
ctx.clearRect(0, 0, width, height)

ctx.fillStyle = 'purple'
ctx.beginPath()
ctx.arc(cx, 100, 50, 0, Math.PI*2, false)
ctx.fill()
ctx.stroke()

cx += 10 // increment at each timestep

// Conditional so that animation will stop eventually
if (cx <= width-400) {
window.requestAnimationFrame(drawCircle)
}
}

drawCircle()
yield ctx.canvas
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
const width = 1200 // adjust
const height = 400 // adjust
const ctx = DOM.context2d(width, height)

/////////////////////////////
// Your drawing logic here //
/////////////////////////////
yield ctx.canvas
}
Insert cell
{
const width = 1200 // adjust
const height = 400 // adjust
const ctx = DOM.context2d(width, height)

/////////////////////////////
// Your drawing logic here //
/////////////////////////////
// Pick a random palette for start and end data
const palette1 = utils.random.pick(palettes)
const palette2 = utils.random.pick(palettes)

const dataStart = _.range(100).map(i => ({
cx: Math.random()*width,
cy: Math.random()*height,
r: Math.random()*20,
fill: _.sample(palette1)
}))

const draw = (data) => {
data.forEach(d => {
ctx.beginPath()
ctx.arc(d.cx, d.cy, d.r, 0, Math.PI*2)
ctx.fill()
})
}

draw(dataStart)
yield ctx.canvas
}
Insert cell
Insert cell
simpleAnimationCanvas = {
const width = 1200
const height = 500
const ctx = DOM.context2d(width, height);


/////////////////////////////////////
//////////// Create data ////////////
/////////////////////////////////////
const numCircles = 300
// Pick a random palette for start and end data
const palette1 = utils.random.pick(palettes)
const palette2 = utils.random.pick(palettes)
const dataStart = _.range(0, numCircles).map(i => ({
id: i,
x: Math.random()*width,
y: Math.random()*height,
r: Math.random()*20,
fill: utils.random.pick(palette1)
}))
const dataEnd = _.range(0, numCircles).map(i => ({
id: i,
x: Math.random()*width,
y: Math.random()*height,
r: Math.abs(utils.random.gaussian(20, 5)),
fill: utils.random.pick(palette2)
}))

/////////////////////////////////////
/////////// Draw function ///////////
/////////////////////////////////////
const draw = (data) => {
ctx.fillStyle = "black"
ctx.fillRect(0, 0, width, height); // Make sure to clear or fill the rect
for (const { x, y, r, fill } of data) {
ctx.beginPath()
ctx.arc(x, y, r, 0, 2*Math.PI)
ctx.fillStyle = fill
ctx.fill()
}
}

/////////////////////////////////////
///////////// Animation /////////////
/////////////////////////////////////
const tl = gsap.to(dataStart, {
x: (index) => dataEnd[index].x,
y: (index) => dataEnd[index].y,
r: (index) => dataEnd[index].r,
fill: (index) => dataEnd[index].fill,
duration: 3,
ease: 'bounce',
onUpdate: () => draw(dataStart)
})

tl.pause()

/////////////////////////////////
///// Control the animation /////
/////////////////////////////////
const play = document.querySelector("#play-test");
const pause = document.querySelector("#pause-test");
const restart = document.querySelector("#restart-test");
const reverse = document.querySelector("#reverse-test");
play.onclick = () => tl.play();
restart.onclick = () => tl.restart()
pause.onclick = () => tl.pause();
reverse.onclick = () => tl.reverse();

draw(dataStart)

yield ctx.canvas;
}
Insert cell
{
const width = 1200 // adjust
const height = 400 // adjust
const ctx = DOM.context2d(width, height)

const numDatapoints = 23

// Data
const dataStart = _.range(0, numDatapoints).map(i => {
const data = {
id: i,
x: i*50 + 25,
y: 25,
r: 15,
fill: '#999'
}
return data
})
const dataEnd = _.range(0, numDatapoints).map(i => {
const data = {
id: i,
x: i*50 + 50*(Math.random() - 0.5),
y: height - 35,
r: 25,
fill: '#E5B80B'
}
return data
})

// Draw
const draw = (data) => {
ctx.fillStyle = '#fff'
ctx.strokeStyle = '#999'
ctx.fillRect(0, 0, width, height)
for (const { x, y, r, fill } of data) {
ctx.beginPath()
ctx.arc(x, y, r, 0, 2*Math.PI)
ctx.fillStyle = fill
ctx.fill()
ctx.stroke()
}
}

// Animate
const timeline = gsap.timeline()
.to(dataStart, {
x: index => dataEnd[index].x,
y: index => dataEnd[index].y,
r: index => dataEnd[index].r,
fill: index => dataEnd[index].fill,
duration: 2,
stagger: 0.2,
ease: 'bounce',
repeat: -1,
yoyo: true,
onUpdate: () => draw(dataStart)
})
yield ctx.canvas
}
Insert cell
Insert cell
dataStart = dataCirclePacking // Data we will animate from
Insert cell
dataEnd = dataSequentialGrid // Data will animate to
Insert cell
Insert cell
Insert cell
{
const c = DOM.context2d(width, height);
c.translate(width/2, height/2);

/////////////////////////////////////
/////////// Draw function ///////////
/////////////////////////////////////
const draw = (data) => {
c.fillStyle = "black"
c.fillRect(-width / 2, -width / 2, width, height); // Make sure to clear or fill the rect
for (const { x, y, r, fill } of data) {
c.beginPath();
c.arc(x, y, r, 0, 2*Math.PI)
c.fillStyle = fill
c.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.
const tl = gsap.to(dataStart, {
x: (index) => dataEnd[index].x,
y: (index) => dataEnd[index].y,
duration: animationDuration,
stagger: { amount: 2 },
ease: 'linear',
onUpdate: () => draw(dataStart)
});
tl.pause()


/////////////////////////////////
///// Control the animation /////
/////////////////////////////////
const play = document.querySelector("#play");
const pause = document.querySelector("#pause");
const restart = document.querySelector("#restart");
const reverse = document.querySelector("#reverse");
play.onclick = () => tl.play();
restart.onclick = () => tl.restart()
pause.onclick = () => tl.pause();
reverse.onclick = () => tl.reverse();

draw(dataStart)
yield c.canvas;

}
Insert cell
Insert cell
dataSequentialGrid = getDataSequentialGrid(dataTidy)
Insert cell
dataCirclePacking = getCirclePacking(dataTidy)
Insert cell
dataCirclePackingByRed = getCirclePacking(dataSortedByRed)
Insert cell
dataCirclePackingByGreen = getCirclePacking(dataSortedByGreen)
Insert cell
dataSortedByGreen = sortByCol(dataTidy, 'green')
Insert cell
dataSortedByRed = sortByCol(dataTidy, 'red')
Insert cell
Insert cell
function getDataSequentialGrid(dataIn) {
const numElements = dataIn.length
const cellSize = radius*2
const numCells = numElements

// If you want the width to be controlled by the width of the container
// but the height is as much as needed to fit the data.
const numCols = Math.ceil(width / cellSize)
const numRows = Math.ceil(numCells / numCols)
const height = numRows * cellSize
// If you want the height to be the height of the container, comment out the line above.

// Use d3 band scales to position the elements in rows and cols
const xScale = d3.scaleBand()
.domain(_.range(numCols))
.range([0, width])
const yScale = d3.scaleBand()
.domain(_.range(numRows))
.range([0, height])

// Create an array of just the x and y positions where each entry will go
const cells = []
let count = 0
for (let i = 0; i < numRows; i++) {
for (let j = 0; j < numCols; j++) {
if (count <= numCells) {
const entry = {
id: count,
x: xScale(j) - width / 2,
y: yScale(i) - height / 2,
}
cells.push(entry)
}
count += 1
}
}

// Merge the array of x and y positions for each entry with the data containing the
// rest of the information, i.e. the colours
const dataSequentialGrid = []
dataIn.forEach((d, i) => {
const entry = {
name: d.name,
fill: d.fill,
red: d.red,
green: d.green,
blue: d.blue,
r: d.r,
x: cells[i].x,
y: cells[i].y
}
dataSequentialGrid.push(entry)
})

return dataSequentialGrid
}
Insert cell
Insert cell
Insert cell
function getCirclePacking(dataIn) {
const dataClone = _.cloneDeep(dataIn)
const circlePacking = d3.packSiblings(dataClone)
return circlePacking
}
Insert cell
Insert cell
function sortByCol(dataIn, col) {
// value of col has to be 'red', 'green' or 'blue'
const dataClone = _.cloneDeep(dataIn)
const dataSorted = _.sortBy(dataClone, col)
return dataSorted
}
Insert cell
Insert cell
dataTidy = tidyData(dataSample)
Insert cell
function tidyData(dataIn) {
const data = []
dataIn.forEach(frame => {
frame.colours.forEach(colour => {
data.push({
name: frame.img,
fill: colour,
red: +colour.split(', ')[0].split('(')[1],
green: +colour.split(', ')[1],
blue: +colour.split(', ')[2].split(')')[0],
r: radius
})
})
})
return data
}
Insert cell
Insert cell
height = width
Insert cell
width = 900
Insert cell
radius = 3 // How big the circle for each colour-entry will be
Insert cell
Insert cell
dataSample = data.filter((d, i) => i%10 === 0) // Take every 10th frame; you can change this to experiment.
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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