// 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(); }