Edit in JSFiddle

// Spring forces using Hook's law (Coding train example), 
// plain NODEJS code

let GRN
let current_gene
let size = 15
let update = true
let time = 0
let delay = 10

class Network {
	constructor(genes, links){
		this.links = links
		this.genes = genes
		this.state = this.genes.map(a => a.state)
		this.trajectory = [this.state] // array of arrays of network states
	}
	
	update(){
		for(let g of this.genes) g.incoming_signals = g.bg_state	

		for (let l of this.links) {
			if(l.source.state>0) l.target.incoming_signals += l.effect
		}
		let new_state = []
		for(let g of this.genes) {
			if(g.incoming_signals > 0) g.state = 1
			else g.state = 0
			new_state.push(g.state)
		}
		this.trajectory.push(new_state)
	}
	
	switchRandomGene(){
		let g = this.genes[Math.floor(random()*this.genes.length)]
		g.state = g.state = 1 ? 0 : 1
	}
	show(){
		for(let l of this.links){
			l.show()
		}
	
		for(let g in this.genes){
			//g.update()
			this.genes[g].show()
			
		} 
		
		noStroke()
		textSize(15)
		textAlign(CENTER,CENTER)
		textStyle(BOLD);
		text("Space-time plot of states (time ->)",  155,16)
		push()
		rotate(-PI*0.5)
		textSize(12)
		text("STATES (genes on/off)",-94, 15)
		pop()
		let spacing = 120/this.genes.length
		let offset = this.trajectory.length - this.genes.length * 4
		if(offset < 0) offset = 0
		for(let t=offset; t < this.trajectory.length; t++){
			for(let s in this.trajectory[t]){

				fill( (this.trajectory[t][s]==0) ? 0 : 255)
				square(35+t*spacing - offset*spacing, 35+spacing*s,spacing)	
			}
			
		}

	}
}

class Gene{
	constructor(x,y,state,bg_state){
		this.x = x
		this.y = y
		this.state = state
		this.bg_state = bg_state
		
	}
	
	show() {
		let col = this.state == 1 ? "white" : "black"
		let strokecol = this.state == 1 ? "black" : "white"
		stroke(strokecol)
		strokeWeight(2)
		fill(col)
		circle(this.x, this.y, size*2, 100)
		fill(0)
		strokeWeight(2)
		textSize(20);
		textAlign(CENTER, CENTER);
		text(this.bg_state, this.x, this.y)
	}	
}

class Link{
	constructor(source, target, effect){
		this.source = source
		this.target = target
		this.effect = effect
	}
	show(){
		let arrowhead = ">"
		if(this.effect < 0) arrowhead="|"
		if(this.source == this.target) {
			strokeWeight(2)
			stroke(0)
			noFill()
			if(arrowhead == "|"){
				arc(this.source.x-size, this.source.y-size, 30, 30, PI,3*PI);
				fill(0)
				push() //start new drawing state    
				translate(this.source.x, this.source.y-size-2); //translates to the destination vertex
				var angle = -HALF_PI
				let headsize=10
				rotate(angle-HALF_PI); //rotates the arrow point	
				line(-headsize,0,headsize,0)
				pop();
			}
			else{
				arc(this.source.x+size, this.source.y+size, 30, 30, -0.4*PI, PI);
				fill(0)
				push() //start new drawing state    
				translate(this.source.x+size+2, this.source.y+2); //translates to the destination vertex
				var angle = arrowhead == "|" ? 0 : 0.1*PI
				let headsize=7
				rotate(angle-HALF_PI); //rotates the arrow point		
				triangle(-headsize*0.5, headsize, headsize*0.5, headsize, 0, -headsize/2); //draws the arrow point as a triangle
				pop();
			}

		} else{
			arrow(this.source.x, this.source.y, this.target.x, this.target.y,arrowhead)
		}
	}
}

 function setup(){
	createCanvas(600,600)
	randomSeed(129) // 123 = 3
  delaySlider = createSlider(1,20,delay,1)
	
	label = createSpan(' Updating delay<br>');
	label.style('font-family', 'helvetica');
// Example network
	let genes = []
	genes.push(new Gene(200, height/2, 1, 1) ) 
	genes.push(new Gene(300, height/3, 0, 0) )
	genes.push(new Gene(300, height-height/3, 0, 0)	)
	genes.push(new Gene(400, height/2, 0, 0))
	let links = []
	links.push(new Link(genes[0],genes[1],1)) // deleting this connection create quite distinct pattern, but still cyclical behaviour
	links.push(new Link(genes[0],genes[2],1)) // small effect again
	links.push(new Link(genes[2],genes[3],1)) // smalle effect, but similar
	links.push(new Link(genes[3],genes[0],-1)) // deleting this locks the whole network in "on"
	links.push(new Link(genes[1],genes[2],1)) // smalle effect, but similar
	links.push(new Link(genes[1],genes[3],1)) // neutral, deletion doesnt change the network
	
// random network 
 	genes = []
	links = []
	let num_genes = 15
	let num_connections = 50
	for(let i=0; i<num_genes; i++) 
	{
		genes.push(new Gene(50+(random(width-100)),170+random(400),random()<0.5?1:0,random()<0.5?1:0))
	}
	for(let i=0; i<num_connections; i++){
		let source = genes[Math.floor(random()*genes.length)]
		let target = genes[Math.floor(random()*genes.length)]
		if(target == source){
			i--;
			continue;
		} 
		let newlink = new Link(source, target, random() < 0.5 ? 1 : -1)
		links.push(newlink)
	} 
	GRN = new Network(genes, links)
} 


function draw() {
	background(95)		
	GRN.show()
	time++
	//GRN.update()
	if(update && time%delaySlider.value()==0) GRN.update()
	if(mouseIsPressed && current_node){		
		current_node.x = mouseX
		current_node.y = mouseY
	}
}

function mousePressed() {
	current_node = getClosestNode(mouseX, mouseY)
}

function getClosestNode(mouseX, mouseY){
	let mousepos = createVector(mouseX,mouseY)	
	let closest_dist = 1000
	let closest_node
	for(let g of GRN.genes) {
		let genevector = createVector(g.x,g.y)
		let d = genevector.dist(mousepos)
		if(d < size && d < closest_dist) {
			closest_node = g
			closest_dist = d
		}
	}
	return closest_node
}

function keyPressed() {
	//text(`${key} ${keyCode}`, 10, 40);
	if (key === 'a') {
	  GRN.initialStates()
		GRN.switchRandomGene()
	}
	if (key === 's') {
		update = !update
	}
}

// returns true if the line from (a,b)->(c,d) intersects with (p,q)->(r,s)
function intersects(a,b,c,d,p,q,r,s) {
  var det, gamma, lambda;
  det = (c - a) * (s - q) - (r - p) * (d - b);
  if (det === 0) {
    return false;
  } else {
    lambda = ((s - q) * (r - a) + (p - r) * (s - b)) / det;
    gamma = ((b - d) * (r - a) + (c - a) * (s - b)) / det;
    return (0 < lambda && lambda < 1) && (0 < gamma && gamma < 1);
  }
}

function travel(x, y, dx, x1, y1, x2, y2)
{
    var a = {x: x2 - x1, y: y2 - y1},
        mag = Math.sqrt(a.x*a.x + a.y*a.y);
    if (mag == 0) {
        a.x = a.y = 0;
    }
    else {
        a.x = a.x/mag*dx;
        a.y = a.y/mag*dx;
    }
    return {x: x + a.x, y: y + a.y};
}



// draw an arrow for a vector at a given base position
function arrow(x, y, x2, y2, arrowhead=">") {
		let col = (arrowhead == ">") ? "#00008888" : "#88000088"
		stroke(col)
		fill(col)
		strokeWeight(2)
		let dist = Math.sqrt(Math.pow(x2-x,2) + Math.pow(y2-y,2)) - size*1.5
		let shortening = travel(x,y,dist,x,y,x2,y2)
		x2 = shortening.x
		y2 = shortening.y
    line(x, y, x2, y2); //draw a line beetween the vertices
		let headsize = 6
    // this code is to make the arrow point
    push() //start new drawing state    
    translate(x2, y2); //translates to the destination vertex
		var angle = atan2(y - y2, x - x2); //gets the angle of the line
    rotate(angle-HALF_PI); //rotates the arrow point		
    if(arrowhead==">")triangle(-headsize*0.5, headsize, headsize*0.5, headsize, 0, -headsize/2); //draws the arrow point as a triangle
		else line(-headsize,0,headsize,0)
		
    pop();
}