import traer.physics.*; import traer.animation.*; final float EDGE_LENGTH = 20; final float EDGE_STRENGTH = 0.01; final float SPACER_STRENGTH = 1100; final float RANDOM_INIT = 100; final float ZOOM = 1.2; class ForceDirectedGraph extends ParticleSystem { Smoother3D centroid; ForceDirectedGraph() { this(0.25, 0.95); } ForceDirectedGraph(float friction, float smoothness) { super(0, friction); centroid = new Smoother3D(smoothness); reset(); } void reset() { clear(); centroid.setValue(0, 0, 1); } Vector3D remap(float x, float y) { return new Vector3D( centroid.x() + (x - width/2) / centroid.z(), centroid.y() + (y - height/2) / centroid.z(), 0); } Particle addVertex() { Particle p = makeParticle(); for(int i = 0; i < numberOfParticles(); i++) { Particle t = getParticle(i); if(t != p) makeAttraction(p, t, -SPACER_STRENGTH, 15); } p.moveTo(random(-RANDOM_INIT, RANDOM_INIT), random(-RANDOM_INIT, RANDOM_INIT), random(-RANDOM_INIT, RANDOM_INIT)); return p; } Spring addEdge(Particle a, Particle b) { return makeSpring(a, b, EDGE_STRENGTH, EDGE_STRENGTH, EDGE_LENGTH); } Spring addEdge(Particle a, Particle b, float edgeLength) { return makeSpring(a, b, EDGE_STRENGTH, EDGE_STRENGTH, edgeLength); } void reposition() { centroid.tick(); translate(width/2 - centroid.x(), height/2 - centroid.y()); rotateY(frameCount * rotationSpeed); scale(centroid.z()); float xMax = Float.NEGATIVE_INFINITY, xMin = Float.POSITIVE_INFINITY, yMin = Float.POSITIVE_INFINITY, yMax = Float.NEGATIVE_INFINITY; for(int i = 0; i < numberOfParticles(); i++) { Vector3D p = getParticle(i).position(); float curX = screenX(p.x(), p.y(), p.z()) - width/2, curY = screenY(p.x(), p.y(), p.z()) - height/2; xMax = max(xMax, curX); xMin = min(xMin, curX); yMin = min(yMin, curY); yMax = max(yMax, curY); } float deltaX = xMax - xMin; float deltaY = yMax - yMin; float xOff = lerp(xMin, xMax, .5); float yOff = lerp(yMin, yMax, .5); float zoom = deltaY > deltaX ? height / deltaY: width / deltaX; centroid.setTarget( centroid.x() + xOff, centroid.y() + yOff, zoom * ZOOM); } }