#2: the original

Source description for demo by Lars Rönnbäck.

F=.95; // 5% friction D=40; // derived as (W+H)/30 K=2e-5; // derived as 1/D/D/D T=255; // max color value A=Math.abs; P=2*Math.PI; C=document.body.children[0]; C.width=C.height=W=H=600; X=Y=300; // N, set of nodes, zeroth position is a dummy // I, index of selected node N=[I=0]; // n, number of nodes N[n=1]={x:X,y:Y,v:0,w:0,m:1}; M=[0]; // edges, zeroth position is a dummy onkeydown=function(e){ // add a node N[i=++n]={x:X,y:Y,v:0,w:0,m:1}; // add edges to selected nodes for(j=n;k=N[j];j--) if(!k.m&&!M[j]){ N[++n]={x:(X-k.x)/2,y:(Y-k.y)/2,v:0,w:0,m:1}; M[n]=[i,j] } }; C.onmousedown=function(e){ // the blue part of the pixel encodes the node index i=G.getImageData(X,Y,1,1).data[2]; // toggle moving/fixed state if(k=N[I=M[i]?0:i]){ k.m=!k.m; k.v=k.w=0 } }; C.onmousemove=function(e){ X=e.clientX; Y=e.clientY; // if we are dragging a node, update its coordinates if(k=N[I]){ k.x=X; k.y=Y; k.m=0 } }; C.onmouseup=function(e){ I=0 }; G=C.getContext('2d'); setInterval(Q=function(e){ with(G){ strokeRect(0,0,W,H); // poor mans motion blur fillStyle='rgba(80,80,80,.4)'; fillRect(0,0,W,H); // loop over all nodes for(i=n;k=N[i];i--) with(k){ if(m&&i!=I){ // calculate the repelling forces from nearby nodes for(j=n;o=N[j];j--) if((t=A(x-o.x)+A(y-o.y))<2*D&&i!=j){ t=t?t*t:1; v+=(x-o.x)/t; w+=(y-o.y)/t } // apply attracting forces to connected nodes if this is an edge if(M[i]) for(j=0;o=N[M[i][j]];j++){ t=A(x-o.x)+A(y-o.y); v-=r=K*(x-o.x)*t; w-=s=K*(y-o.y)*t; o.v+=r; o.w+=s } // apply friction and update coordinates x+=v*=F; y+=w*=F; // do not cross borders of the canvas x=x>W?W:x<0?0:x; y=y>H?H:y<0?0:y } // draw the edge or node on the canvas beginPath(); if(M[i]){ o=N[M[i][0]]; p=N[M[i][1]]; moveTo(o.x,o.y); quadraticCurveTo(x,y,p.x,p.y) }else{ arc(x,y,D/3,0,P,0); fillStyle='rgb('+!m*T+','+m*T+','+i+')'; fill() } stroke() } } },9)