Public
Edited
Oct 6, 2024
1 fork
Insert cell
Insert cell
class Wave {
constructor(id, frequencyNumerator, frequencyDenominator) {
this.id = id;
this.fn = frequencyNumerator;
this.fd = frequencyDenominator;
this.closed = true;
}

value(p) {
let frequencyModulator = this.fn/this.fd;
let f = frequencyModulator*2*Math.PI/p.size;
if (this.closed) {
return Math.sin(f*p.x)*Math.sin(f*p.y);
} else {
return Math.cos(f*p.x)*Math.cos(f*p.y);
}
}
close(){
this.closed = true;
return this;
}
open(){
this.closed = false;
return this;
}
};

Insert cell
class WavePool {
constructor() {
this.waves = [];
this.normalize = true;
}

add(wave) {
this.waves.push(wave);
}
value(p) {
var value = 0;
for (var i = 0; i < this.waves.length; i ++) {
value += this.waves[i].value(p);
}
value = Math.abs(value);
var normalizer = 1;
if (this.normalize) normalizer = this.waves.length;
return (value / normalizer);
}
};

Insert cell
class WaveController {

constructor(wavepool) {
this.waveCount = 0;
this.pool = wavepool;
}

getWave(waveId) {
return this.pool.get(waveId);
}

newWave() {
var waveId = "wave"+ (this.waveCount ++);
var wave = new Wave(waveId, 1,2);
wave.closed = false;
this.pool.add(wave);
}
}
Insert cell
function randomInt(lessThan){
var selection = Math.floor(Math.random()*(lessThan));
return selection;
}
Insert cell
function randomRange(greaterThan, lessThan){
var shifted = randomInt(lessThan - greaterThan);
return lessThan - shifted;
};
Insert cell
class RandomPoint {
constructor(size) {
this.x = randomRange(0,size);
this.y = randomRange(0,size);
this.size = size;
}
perturb(){
this.x = this.x + randomRange(-this.size/10,this.size/10);
this.y = this.y + randomRange(-this.size/10,this.size/10);
this.x = this.x%(this.size +1);
this.y = this.y%(this.size +1);
}
};
Insert cell
{

var colourBase = 500;
var invertColour = false;
var nodeThreshold = 0.04;
function increaseHue () {
if (colourBase < 360) { colourBase += 2}
else {colourBase = 0}
}

function decreaseHue () {
if (colourBase > 0) {colourBase -=2}
else {colourBase = 360}
}
function invertHue() {
invertColour = !invertColour;
}
function range(n) {
return Array.from((Array(n)).keys()) ;
}

function hslColorChooser(level) {
var l = Math.floor(level * 100);
if (invertColour) {
l = Math.floor(100 - l);
}
return "hsl("+ colourBase + ", 50%, " + l +"%)";
};
const size = 400;

let sandcount = 15000; //amount of sand in one push
let dotSize = 3; //initial dot size
let sand = [];
let wavepool = new WavePool();
var waveController = new WaveController(wavepool);
//wavepool.normalize = false; % setting this to false slows things down quite a bit

/*
* Customize the number of waves, their type, and their frequencies
*/
waveController.newWave();
waveController.newWave();
waveController.newWave();
//waveController.newWave();
wavepool.waves[0].fn += 1;
wavepool.waves[1].open().fn+=3;
wavepool.waves[2].close().fn += 5;
//wavepool.waves[3].close().fn += 26;
/*
* Create the SVG, Initialize and spread the sand
*/
const svg = d3.create('svg')
.attr('width', size)
.attr('height', size);

for (var i = 0; i < sandcount; i ++){
let p = new RandomPoint(size);
sand.push(p);
}
//draw circles
svg.selectAll("circle")
.data(sand)
.enter().append("circle")
.attr("cx", function(s) {
return s.x;})
.attr("cy", function(s) {
return s.y;})
.attr("r", function(s) { return dotSize; })
.attr("class","dot")
.style("fill", function(s){
let value = wavepool.value(s);
return hslColorChooser(value);
//return "black";
});

await Promises.delay(100)
yield svg.node();
/*
* Start the vibrations - sand moves randomly until it hits a nodal point
*/
while(true){
let remaining = 0;
svg.selectAll("circle").filter(function(s){
let value = wavepool.value(s);
if (value > nodeThreshold){
s.perturb();
remaining ++;
}
});
svg.selectAll("circle").transition()
.duration(600)
.attr('cx', function(s){return s.x;})
.attr('cy',function(s){return s.y;});
await Promises.delay(100)
yield svg.node();
if (remaining == 0) break;
}
}

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