Create a simulation of a force-directed graph placement algorithm.
Generate a single file p5.js sketch on a 500x400 canvas.
Set global variables for the width and height of the canvas.
The algorithm will move the position of vertices of a connected
based on a force-directed graph placement mode.
For the initial state, place 20 vertices on random locations on a 500x400 canvas.
Make the vertices be rendered by a circle with a radius of 10.
Connect each vertex to 3 other vertices using bidirectional links.
Add a controls to incrementally step through the algorithm.
Add buttons for a single Step, 10 Steps, 100 Steps and 1,000 Steps at the bottom of canvas.
Display the number of steps, and the total movement of the verticies in
each step.
// Global variablesletvertices=[];lettotalVertices=20;lettotalSteps=0;letcanvasWidth=500;letcanvasHeight=400;letvertexRadius=10;lettotalMovement=0;// To track total net movementletrepulsiveForce=500;letspringLength=100;letspringStrength=0.005;functionsetup(){createCanvas(canvasWidth,canvasHeight);textSize(16);background(245);// Initialize verticesfor(leti=0;i<totalVertices;i++){letv={x:random(vertexRadius,width-vertexRadius),y:random(vertexRadius,height-vertexRadius),edges:[],prevX:0,prevY:0};v.prevX=v.x;// Store initial positionv.prevY=v.y;vertices.push(v);}// Create edgesfor(leti=0;i<vertices.length;i++){while(vertices[i].edges.length<3){letneighbor=floor(random(totalVertices));if(neighbor!==i&&!vertices[i].edges.includes(neighbor)){vertices[i].edges.push(neighbor);vertices[neighbor].edges.push(i);// Bidirectional link}}}// Button for 'Next' stepletnextButton=createButton('Next');nextButton.position(10,height+10);nextButton.mousePressed(()=>performSteps(1));// Button for '10 Steps'lettenStepsButton=createButton('10 Steps');tenStepsButton.position(60,height+10);tenStepsButton.mousePressed(()=>performSteps(10));// Button for '100 Steps'lethundredStepsButton=createButton('100 Steps');hundredStepsButton.position(140,height+10);hundredStepsButton.mousePressed(()=>performSteps(100));// Button for '1000 Steps'letthousandStepsButton=createButton('1000 Steps');thousandStepsButton.position(230,height+10);thousandStepsButton.mousePressed(()=>performSteps(1000));}functiondraw(){background(245);drawVertices();drawEdges();displayTotalSteps();displayTotalMovement();}functiondrawVertices(){fill('blue');for(letvertexofvertices){ellipse(vertex.x,vertex.y,vertexRadius*2,vertexRadius*2);}}functiondrawEdges(){stroke(0);for(leti=0;i<vertices.length;i++){for(letedgeofvertices[i].edges){line(vertices[i].x,vertices[i].y,vertices[edge].x,vertices[edge].y);}}}functionperformSteps(steps){for(leti=0;i<steps;i++){nextStep();}}functionnextStep(){totalSteps=totalSteps+1;// Calculate repulsive forcesfor(leti=0;i<vertices.length;i++){for(letj=i+1;j<vertices.length;j++){letdx=vertices[i].x-vertices[j].x;letdy=vertices[i].y-vertices[j].y;letdistance=sqrt(dx*dx+dy*dy);letforce=repulsiveForce/(distance*distance);letforceX=force*dx/distance;letforceY=force*dy/distance;vertices[i].x+=forceX;vertices[i].y+=forceY;vertices[j].x-=forceX;vertices[j].y-=forceY;}}// Calculate attractive forces along edgesfor(letvertexofvertices){for(letedgeofvertex.edges){letdx=vertex.x-vertices[edge].x;letdy=vertex.y-vertices[edge].y;letdistance=sqrt(dx*dx+dy*dy);letforce=(distance-springLength)*springStrength;letforceX=force*dx/distance;letforceY=force*dy/distance;vertex.x-=forceX;vertex.y-=forceY;vertices[edge].x+=forceX;vertices[edge].y+=forceY;}}// Update total net movementtotalMovement=0;for(letvertexofvertices){letdx=vertex.x-vertex.prevX;letdy=vertex.y-vertex.prevY;totalMovement+=sqrt(dx*dx+dy*dy);vertex.prevX=vertex.x;vertex.prevY=vertex.y;}}functiondisplayTotalSteps(){fill(0);noStroke();textAlign(LEFT,BOTTOM);text(`Steps: ${totalSteps}`,10,height-10);}functiondisplayTotalMovement(){fill(0);noStroke();textAlign(RIGHT,BOTTOM);text(`Total Step Movement: ${totalMovement.toFixed(2)}`,width-10,height-10);}