Published
Edited
Jun 26, 2019
1 fork
1 star
Insert cell
Insert cell
chart = {

const width = 400, height = 400;
const context = DOM.context2d(width, height);

const particles = [
{
position: new Vector(width/2, height/2),
velocity: new Vector(10, 1).scaleTo(4),
acceleration: new Vector()
}
]
function tick() {
context.clearRect(0, 0, width, height);
context.strokeStyle = '#ccc';
context.strokeRect(0, 0, width, height);
particles.forEach(updateParticle);
requestAnimationFrame(tick);
}
function updateParticle(b) {
b.position.add(b.velocity.add(b.acceleration));
if (b.position.y > height) {
b.position.y -= height;
} else if (b.position.y < 0) {
b.position.y += height;
}
if (b.position.x > width) {
b.position.x -= width;
} else if (b.position.x < 0) {
b.position.x += width;
}
context.beginPath();
context.fillStyle = 'black';
context.arc(b.position.x, b.position.y, 4, 0, 2 * Math.PI);
context.fill();
}
requestAnimationFrame(tick);
return context.canvas
}
Insert cell
md`
## Lets add Gravity
`
Insert cell
{

const width = 400, height = 400;
const context = DOM.context2d(width, height);
const maxFrames = 1000;
const gravObjects = [{
position: new Vector(width*3/4, height*3/4)
},{
position: new Vector(width*1/4, height*1/4)
}]

const particles = [
{
position: new Vector(width/2, height/2),
velocity: new Vector(-10, 0).scaleTo(1),
acceleration: new Vector()
}
]
function tick() {
context.clearRect(0, 0, width, height);
// Draw border
context.beginPath();
context.strokeStyle = '#ccc';
context.strokeRect(0, 0, width, height);
gravObjects.forEach(g => {
context.beginPath();
context.fillStyle = 'red';
context.arc(g.position.x, g.position.y, 10, 0, 2 * Math.PI);
context.fill();
})
particles.forEach(function(p){
p.acceleration = new Vector()
gravObjects.forEach(g => {
const f1 = new Vector()
var diff = p.position.clone().subtract(g.position),
distance = diff.length();
f1.add(diff.clone().scaleTo(-1 / distance)).active = true;
p.acceleration.add(f1);
})
});
particles.forEach(updateParticle);
requestAnimationFrame(tick);
}
function updateParticle(b) {
b.position.add(b.velocity.add(b.acceleration).truncate(2));
if (b.position.y > height) {
b.position.y -= height;
} else if (b.position.y < 0) {
b.position.y += height;
}
if (b.position.x > width) {
b.position.x -= width;
} else if (b.position.x < 0) {
b.position.x += width;
}
context.beginPath();
context.fillStyle = 'black';
context.arc(b.position.x, b.position.y, 4, 0, 2 * Math.PI);
context.fill();
}
requestAnimationFrame(tick);
return context.canvas
}
Insert cell
{

const width = 400, height = 400;
const context = DOM.context2d(width, height);
const maxFrames = 1000;
const gravObjects = [{
position: new Vector(width*3/4, height*3/4)
},{
position: new Vector(width*1/4, height*1/4)
}]

const particles = [
{
position: new Vector(width/2, height/2),
velocity: new Vector(-10, 0).scaleTo(1),
acceleration: new Vector()
}
]
function tick() {
context.clearRect(0, 0, width, height);
// Draw border
context.beginPath();
context.strokeStyle = '#ccc';
context.strokeRect(0, 0, width, height);
particles.forEach(function(p){
p.acceleration = new Vector()
gravObjects.forEach(g => {
const f1 = new Vector()
var diff = p.position.clone().subtract(g.position),
distance = diff.length();
f1.add(diff.clone().scaleTo(-1 / distance)).active = true;
p.acceleration.add(f1);
})
});
particles.forEach(updateParticle);
gravObjects.forEach(updateGravObjs);
requestAnimationFrame(tick);
}
function updateParticle(b) {
b.position.add(b.velocity.add(b.acceleration).truncate(2));
if (b.position.y > height) {
b.position.y -= height;
} else if (b.position.y < 0) {
b.position.y += height;
}
if (b.position.x > width) {
b.position.x -= width;
} else if (b.position.x < 0) {
b.position.x += width;
}
context.beginPath();
context.fillStyle = 'black';
context.arc(b.position.x, b.position.y, 4, 0, 2 * Math.PI);
context.fill();
}
function updateGravObjs(g) {
const noiseVec = new Vector(1 - Math.random() * 2, 1 - Math.random() * 2).scaleTo(4)
g.position.add(noiseVec);
context.beginPath();
context.fillStyle = 'red';
context.arc(g.position.x, g.position.y, 10, 0, 2 * Math.PI);
context.fill();
}
requestAnimationFrame(tick);
return context.canvas
}
Insert cell
{

const width = 400, height = 400;
const context = DOM.context2d(width, height);
const maxFrames = 4000;
let curTick = 0
const gravObjects = [{
position: new Vector(width*3/4, height*3/4)
},{
position: new Vector(width*1/4, height*1/4)
}]
var line = d3.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.curve(d3.curveStep)
.context(context);

const particles = [
{
position: new Vector(width/2, height/2),
velocity: new Vector(-10, 0).scaleTo(1),
acceleration: new Vector(),
path: []
}
]
function tick() {
context.clearRect(0, 0, width, height);
context.font = '18px';
context.fillStyle = 'black';
context.fillText(`Frame: ${curTick}`, 10, 10);
// Draw border
context.beginPath();
context.strokeStyle = '#ccc';
context.strokeRect(0, 0, width, height);
particles.forEach(function(p){
p.acceleration = new Vector()
gravObjects.forEach(g => {
const f1 = new Vector()
var diff = p.position.clone().subtract(g.position),
distance = diff.length();
f1.add(diff.clone().scaleTo(-1 / distance)).active = true;
p.acceleration.add(f1);
})
});
particles.forEach(updateParticle);
gravObjects.forEach(updateGravObjs);
if (curTick++ < maxFrames) requestAnimationFrame(tick);
}
function updateParticle(b) {
b.position.add(b.velocity.add(b.acceleration).truncate(2));
if (b.position.y > height) {
b.position.y -= height;
} else if (b.position.y < 0) {
b.position.y += height;
}
if (b.position.x > width) {
b.position.x -= width;
} else if (b.position.x < 0) {
b.position.x += width;
}
b.path.push({x: b.position.x, y: b.position.y})
context.beginPath();
line(b.path);
context.lineWidth = 1.5;
context.strokeStyle = "steelblue";
context.stroke();
context.beginPath();
context.fillStyle = 'black';
context.arc(b.position.x, b.position.y, 4, 0, 2 * Math.PI);
context.fill();
}
function updateGravObjs(g) {
// const noiseVec = new Vector(1 - Math.random() * 2, 1 - Math.random() * 2).scaleTo(4)
// g.position.add(noiseVec);
context.beginPath();
context.fillStyle = 'red';
context.arc(g.position.x, g.position.y, 10, 0, 2 * Math.PI);
context.fill();
}
requestAnimationFrame(tick);
return context.canvas
}
Insert cell
md`
# Different Masses
`
Insert cell
{

const width = 400, height = 400;
const context = DOM.context2d(width, height);
const maxFrames = 4000;
let curTick = 0
const gScale = d3.scaleLinear()
.domain([1, 4])
.range([3, 10]);
const gravObjects = [
{
position: new Vector(width*3/4, height*3/4),
mass: 1
},{
position: new Vector(width*1/4, height*1/4),
mass: 1.1
},{
position: new Vector(width, height),
mass: 10
}
]
var line = d3.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.curve(d3.curveStep)
.context(context);

const particles = [
{
position: new Vector(width/2, height/2),
velocity: new Vector(-10, 0).scaleTo(1),
acceleration: new Vector(),
path: []
}
]
function tick() {
context.clearRect(0, 0, width, height);
context.font = '18px';
context.fillStyle = 'black';
context.fillText(`Frame: ${curTick}`, 10, 10);
// Draw border
context.beginPath();
context.strokeStyle = '#ccc';
context.strokeRect(0, 0, width, height);
particles.forEach(function(p){
p.acceleration = new Vector()
gravObjects.forEach(g => {
const f1 = new Vector()
var diff = p.position.clone().subtract(g.position),
distance = diff.length();
f1.add(diff.clone().scaleTo(-(g.mass) / distance)).active = true;
p.acceleration.add(f1);
})
});
particles.forEach(updateParticle);
gravObjects.forEach(updateGravObjs);
if (curTick++ < maxFrames) requestAnimationFrame(tick);
}
function updateParticle(b) {
b.position.add(b.velocity.add(b.acceleration).truncate(2));
if (b.position.y > height) {
b.position.y -= height;
} else if (b.position.y < 0) {
b.position.y += height;
}
if (b.position.x > width) {
b.position.x -= width;
} else if (b.position.x < 0) {
b.position.x += width;
}
b.path.push({x: b.position.x, y: b.position.y})
context.beginPath();
line(b.path);
context.lineWidth = 1.5;
context.strokeStyle = "steelblue";
context.stroke();
context.beginPath();
context.fillStyle = 'black';
context.arc(b.position.x, b.position.y, 4, 0, 2 * Math.PI);
context.fill();
}
function updateGravObjs(g) {
// const noiseVec = new Vector(1 - Math.random() * 2, 1 - Math.random() * 2).scaleTo(4)
// g.position.add(noiseVec);
context.beginPath();
context.fillStyle = 'red';
context.arc(g.position.x, g.position.y, gScale(g.mass), 0, 2 * Math.PI);
context.fill();
}
requestAnimationFrame(tick);
return context.canvas
}
Insert cell
{

const width = 400, height = 400;
const context = DOM.context2d(width, height);
const maxFrames = 4000;
let curTick = 0
const gScale = d3.scaleLinear()
.domain([1, 4])
.range([3, 10]);
const gravObjects = [
{
position: new Vector(width*3/4, height*3/4),
mass: 1.6
},{
position: new Vector(width*1/4, height*1/4),
mass: 1.6
}
]
var line = d3.line()
.defined(function(d) { return d; })
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.curve(d3.curveStep)
.context(context);

const particles = [
{
position: new Vector(width/2, height/2),
velocity: new Vector(-10, 0).scaleTo(1),
acceleration: new Vector(),
path: []
},
{
position: new Vector(0, 0),
velocity: new Vector(2,5).scaleTo(1),
acceleration: new Vector(),
path: []
}
]
function tick() {
context.clearRect(0, 0, width, height);
context.font = '18px';
context.fillStyle = 'black';
context.fillText(`Frame: ${curTick}`, 10, 10);
// Draw border
context.beginPath();
context.strokeStyle = '#ccc';
context.strokeRect(0, 0, width, height);
particles.forEach(function(p){
p.acceleration = new Vector()
// Deal with the Fixed Objects
gravObjects.forEach(g => {
const f1 = new Vector()
var diff = p.position.clone().subtract(g.position),
distance = diff.length();
f1.add(diff.clone().scaleTo(-(g.mass) / distance)).active = true;
p.acceleration.add(f1);
})
// Cross Forces
const alignmentForce = new Vector(),
cohesionForce = new Vector(),
separationForce = new Vector()
particles.forEach(function(p2){
if (p === p2) return;
var diff = p2.position.clone().subtract(p.position),
distance = diff.length();
if (distance && distance < 30) {
separationForce.add(diff.clone().scaleTo(-1 / distance)).active = true;
}
if (distance < 60) {
cohesionForce.add(diff).active = true;
separationForce.add(p2.velocity).active = true;
}
});
if (alignmentForce.active) {
alignmentForce.scaleTo(2)
.subtract(p.velocity)
.truncate(0.03);
p.acceleration.add(alignmentForce);
}
if (cohesionForce.active) {
cohesionForce.scaleTo(2)
.subtract(p.velocity)
.truncate(0.03);
p.acceleration.add(cohesionForce);
}
if (separationForce.active) {
separationForce.scaleTo(2)
.subtract(p.velocity)
.truncate(0.1);
p.acceleration.add(separationForce);
}
});
particles.forEach(updateParticle);
gravObjects.forEach(updateGravObjs);
if (curTick++ < maxFrames) requestAnimationFrame(tick);
}
function updateParticle(b) {
b.position.add(b.velocity.add(b.acceleration).truncate(2));
if (b.position.y > height) {
b.path.push(null)
b.position.y -= height;
} else if (b.position.y < 0) {
b.path.push(null)
b.position.y += height;
}
if (b.position.x > width) {
b.path.push(null)
b.position.x -= width;
} else if (b.position.x < 0) {
b.path.push(null)
b.position.x += width;
}
b.path.push({x: b.position.x, y: b.position.y})
context.beginPath();
line(b.path);
context.lineWidth = 1.5;
context.strokeStyle = "steelblue";
context.stroke();
context.beginPath();
context.fillStyle = 'black';
context.arc(b.position.x, b.position.y, 4, 0, 2 * Math.PI);
context.fill();
}
function updateGravObjs(g) {
// const noiseVec = new Vector(1 - Math.random() * 2, 1 - Math.random() * 2).scaleTo(4)
// g.position.add(noiseVec);
context.beginPath();
context.fillStyle = 'red';
context.arc(g.position.x, g.position.y, gScale(g.mass), 0, 2 * Math.PI);
context.fill();
}
requestAnimationFrame(tick);
return context.canvas
}
Insert cell
Insert cell
{

const width = 400, height = 400;
const context = DOM.context2d(width, height);
const maxFrames = 1000;
let curTick = 0
const gScale = d3.scaleLinear()
.domain([1, 4])
.range([3, 10]);
const gravObjects = [
{
position: new Vector(width*3/4, height*3/4),
mass: 1.6
},{
position: new Vector(width*1/4, height*1/4),
mass: 1.6
}
]
var line = d3.line()
.defined(function(d) { return d; })
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
// .curve(d3.curveStep)
.context(context);

// const particles = [
// {
// position: new Vector(width/2, height/2),
// velocity: new Vector(-10, 0).scaleTo(1),
// acceleration: new Vector(),
// path: []
// },
// {
// position: new Vector(0, 0),
// velocity: new Vector(2,5).scaleTo(1),
// acceleration: new Vector(),
// path: []
// }
// ]
const particles = d3.range(10).map(() => {
const angle = d3.randomUniform(-Math.PI/4, Math.PI/4)()
const vx = Math.cos(angle)
const vy = Math.sin(angle)
return {
position: new Vector(0, d3.randomUniform(0, height)()),
velocity: new Vector(vx, vy).scaleTo(1),
acceleration: new Vector(),
path: []
}
})
function tick() {
context.clearRect(0, 0, width, height);
// Draw border
context.beginPath();
context.strokeStyle = '#ccc';
context.strokeRect(0, 0, width, height);
particles.forEach(function(p){
p.acceleration = new Vector()
// Deal with the Fixed Objects
gravObjects.forEach(g => {
const f1 = new Vector()
var diff = p.position.clone().subtract(g.position),
distance = diff.length();
f1.add(diff.clone().scaleTo(-(g.mass) / distance)).active = true;
p.acceleration.add(f1);
})
// Cross Forces
const alignmentForce = new Vector(),
cohesionForce = new Vector(),
separationForce = new Vector()
particles.forEach(function(p2){
if (p === p2) return;
var diff = p2.position.clone().subtract(p.position),
distance = diff.length();
if (distance && distance < 30) {
separationForce.add(diff.clone().scaleTo(-1 / distance)).active = true;
}
if (distance < 60) {
cohesionForce.add(diff).active = true;
separationForce.add(p2.velocity).active = true;
}
});
if (alignmentForce.active) {
alignmentForce.scaleTo(2)
.subtract(p.velocity)
.truncate(0.03);
p.acceleration.add(alignmentForce);
}
if (cohesionForce.active) {
cohesionForce.scaleTo(2)
.subtract(p.velocity)
.truncate(0.03);
p.acceleration.add(cohesionForce);
}
if (separationForce.active) {
separationForce.scaleTo(2)
.subtract(p.velocity)
.truncate(0.05);
p.acceleration.add(separationForce);
}
});
particles.forEach(updateParticle);
gravObjects.forEach(updateGravObjs);
context.font = '18px';
context.fillStyle = 'black';
context.fillText(`Frame: ${curTick}`, 10, 10);
if (curTick++ < maxFrames) requestAnimationFrame(tick);
}
function updateParticle(b) {
b.position.add(b.velocity.add(b.acceleration).truncate(2));
if (b.position.y > height) {
b.path.push(null)
b.position.y -= height;
} else if (b.position.y < 0) {
b.path.push(null)
b.position.y += height;
}
if (b.position.x > width) {
b.path.push(null)
b.position.x -= width;
} else if (b.position.x < 0) {
b.path.push(null)
b.position.x += width;
}
b.path.push({x: b.position.x, y: b.position.y})
context.beginPath();
line(b.path);
context.lineWidth = 1.5;
context.strokeStyle = "steelblue";
context.stroke();
context.beginPath();
context.fillStyle = 'black';
context.arc(b.position.x, b.position.y, 4, 0, 2 * Math.PI);
context.fill();
}
function updateGravObjs(g) {
context.beginPath();
context.fillStyle = 'red';
context.arc(g.position.x, g.position.y, gScale(g.mass), 0, 2 * Math.PI);
context.fill();
}
requestAnimationFrame(tick);
return context.canvas
}
Insert cell
Insert cell
{

const width = 800, height = 200;
const context = DOM.context2d(width, height);
const maxFrames = 2000;
let curTick = 0
const blendMode = "color-burn"
context.globalCompositeOperation = blendMode
const gScale = d3.scaleLinear()
.domain([1, 4])
.range([3, 10]);
const gravObjects = [
// {
// position: new Vector(width*3/4, height*3/4),
// mass: 1.2
// },{
// position: new Vector(width*1/4, height*1/4),
// mass: 1
// }
]
var line = d3.line()
.defined(function(d) { return d; })
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.context(context);
const genParticle = () => {
const angle = d3.randomUniform(-Math.PI/4, Math.PI/4)()
const vx = Math.cos(angle)
const vy = Math.sin(angle)
return {
id: (new Date()).getTime(),
position: new Vector(0, d3.randomUniform(0, height)()),
velocity: new Vector(vx, vy).scaleTo(1),
acceleration: new Vector(),
path: [],
active: true,
color: d3.interpolateYlOrRd(Math.random())
}
}
const particles = []
function tick() {
context.clearRect(0, 0, width, height);
// context.globalCompositeOperation = blendMode
if (Math.random() >= 0.97 ) {
particles.push(genParticle())
}
// Draw border
context.beginPath();
context.strokeStyle = '#ccc';
context.strokeRect(0, 0, width, height);
particles.filter(p => p.active).forEach(function(p){
p.acceleration = new Vector()
// Deal with the Fixed Objects
gravObjects.forEach(g => {
var diff = g.position.clone().subtract(p.position),
distance = diff.length();
// const f1 = new Vector()
// f1.add(diff.clone().scaleTo(g.mass/distance)).active = true;
// p.acceleration.add(f1);
if (distance > 20) {
const f1 = new Vector()
f1.add(diff.clone().scaleTo(g.mass/distance)).active = true;
p.acceleration.add(f1);
}
if (distance < 10) {
const f1 = new Vector()
f1.add(diff.clone().scaleTo(-3*g.mass/distance)).active = true;
p.acceleration.add(f1);
}
// const f2 = new Vector()
// f2.add(diff.clone().scaleTo(-distance)).active = true;
// p.acceleration.add(f2);
})
// Cross Forces
const alignmentForce = new Vector(),
cohesionForce = new Vector(),
separationForce = new Vector()
particles.filter(p => p.active).forEach(function(p2){
if (p === p2) return;
var diff = p2.position.clone().subtract(p.position),
distance = diff.length();
if (distance && distance < 30) {
separationForce.add(diff.clone().scaleTo(-1 / distance)).active = true;
}
if (distance < 60) {
cohesionForce.add(diff).active = true;
alignmentForce.add(p2.velocity).active = true;
}
});
if (alignmentForce.active) {
alignmentForce.scaleTo(2)
.subtract(p.velocity)
.truncate(0.03);
p.acceleration.add(alignmentForce);
}
if (cohesionForce.active) {
cohesionForce.scaleTo(2)
.subtract(p.velocity)
.truncate(0.03);
p.acceleration.add(cohesionForce);
}
if (separationForce.active) {
separationForce.scaleTo(2)
.subtract(p.velocity)
.truncate(0.05);
p.acceleration.add(separationForce);
}
});
particles.forEach(updateParticle);
gravObjects.forEach(updateGravObjs);
context.font = '18px';
context.fillStyle = 'black';
context.fillText(`Frame: ${curTick}`, 10, 10);
if (curTick++ < maxFrames) requestAnimationFrame(tick);
}
function updateParticle(b) {
if (b.active) {
b.position.add(b.velocity.add(b.acceleration).truncate(2));
}
if (b.position.y > height) {
b.active = false
} else if (b.position.y < 0) {
b.active = false
}
if (b.position.x > width) {
b.active = false
} else if (b.position.x < 0) {
b.active = false
}
b.path.push({x: b.position.x, y: b.position.y})
context.beginPath();
line(b.path);
context.lineWidth = 3;
context.strokeStyle = b.color;
context.stroke();
context.beginPath();
context.fillStyle = 'white';
context.arc(b.position.x, b.position.y, 4, 0, 2 * Math.PI);
context.fill();
}
function updateGravObjs(g) {
context.beginPath();
context.fillStyle = 'red';
context.arc(g.position.x, g.position.y, gScale(g.mass), 0, 2 * Math.PI);
context.fill();
}
requestAnimationFrame(tick);
return context.canvas
}
Insert cell
import { slider, checkbox, radio } from "@jashkenas/inputs"
Insert cell
d3 = require("d3@5")
Insert cell
_ = require('lodash')
Insert cell
import { Vector } from "@dvreed77/vectors"
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