import traer.physics.*; import traer.animation.*; import java.util.Hashtable; import processing.pdf.*; final float NODE_SIZE = 60; final float EDGE_LENGTH = 22; final float EDGE_STRENGTH = 0.2; final float SPACER_STRENGTH = 1000; final float CENTROID_SIZE = 200; float scaleOffset = 1; int COLOR_SCHEME = 1; traer.physics.ParticleSystem physics; traer.animation.Smoother3D centroid; LinkedList students; Hashtable addedStudents; Particle rootParticle; static final int NUM_QUARTERS = 2; static final int MAX_DATE = 2008; static final int MIN_DATE = 2004; static final int DATE_RANGE = MAX_DATE - MIN_DATE; boolean export = false; void setup(){ size(1280, 800); ellipseMode(CENTER); rectMode(CENTER); String lines[] = loadStrings("students.txt"); students = new LinkedList(); addedStudents = new Hashtable(); println("there are " + lines.length + " lines"); for (int i=0; i < lines.length; i++) { Student s = parseStudent(lines[i]); if(s != null){ students.add(s); println(s); } } //Physics stuff physics = new ParticleSystem( 0, 0.25 ); centroid = new Smoother3D( 0.8 ); physics.clear(); rootParticle = physics.makeParticle(); centroid.setValue( 0, 0, 1.0 ); smooth(); } void draw(){ if (export) { beginRecord(PDF, "frame-####.pdf"); } if(!students.isEmpty()){ Student next = (Student)students.remove(0); addStudent(next); } physics.tick(1); if ( physics.numberOfParticles() > 1 ){ updateCentroid(); } centroid.tick(); if(COLOR_SCHEME == 1){ background( 255 ); stroke(0); }else{ background(0); stroke(255); } translate( width/2 , height/2 ); scale( centroid.z()/scaleOffset); translate( -centroid.x(), -centroid.y() ); drawNetwork(); } void drawNetwork() { // draw vertices strokeWeight(3); for ( int i = 0; i < physics.numberOfParticles(); ++i){ Particle v = physics.getParticle( i ); if(v == rootParticle){ fill(255, 255,255); ellipse( v.position().x(), v.position().y(), CENTROID_SIZE, CENTROID_SIZE ); }else{ Student s = (Student)addedStudents.get(v); if(s.isCS()){ if(COLOR_SCHEME == 1){ fill(160, 200,200); }else{ fill(255,102,0); } }else{ if(COLOR_SCHEME == 1){ fill(200,200,160); }else{ fill(153,125,15); } } float theSize = NODE_SIZE - s.date * 12; ellipse( v.position().x(), v.position().y(), theSize, theSize); } } // draw edges if(COLOR_SCHEME == 1){ stroke( 0 ); }else{ stroke(255); } beginShape( LINES ); for ( int i = 0; i < physics.numberOfSprings(); ++i ) { Spring e = physics.getSpring( i ); Particle a = e.getOneEnd(); Particle b = e.getTheOtherEnd(); vertex( a.position().x(), a.position().y() ); vertex( b.position().x(), b.position().y() ); } endShape(); if (export) { endRecord(); export = false; } } Student parseStudent(String student){ String[] items = student.split("\t"); //disregard incomplete items. Sorry kids... if(items.length != 2){ return null; } return new Student(Integer.parseInt(items[0].trim()), items[1].equals("CS")); } void updateCentroid() { float xMax = Float.NEGATIVE_INFINITY, xMin = Float.POSITIVE_INFINITY, yMin = Float.POSITIVE_INFINITY, yMax = Float.NEGATIVE_INFINITY; for ( int i = 0; i < physics.numberOfParticles(); ++i ) { Particle p = physics.getParticle( i ); xMax = max( xMax, p.position().x() ); xMin = min( xMin, p.position().x() ); yMin = min( yMin, p.position().y() ); yMax = max( yMax, p.position().y() ); } float deltaX = xMax-xMin; float deltaY = yMax-yMin; if ( deltaY > deltaX ) centroid.setTarget( xMin + 0.5*deltaX, yMin +0.5*deltaY, height/(deltaY+50) ); else centroid.setTarget( xMin + 0.5*deltaX, yMin +0.5*deltaY, width/(deltaX+50) ); } void addStudent(Student s){ //add to physics Particle p = physics.makeParticle(); Particle q = physics.getParticle( (int)random( 0, physics.numberOfParticles()-1) ); boolean tryAgain = false; Student qStudent; do{ q = physics.getParticle( (int)random( 0, physics.numberOfParticles()-1) ); qStudent = (Student)addedStudents.get(q); if(qStudent == null){ qStudent = new Student(MIN_DATE - 1, true); } tryAgain = ( //same node (q == p) //non zero root node connection || (q == rootParticle && s.date != 0) //not the same major || (q != rootParticle && qStudent.isCS() != s.isCS()) //incorrcet hierarchy // || (s.date - 1 != qStudent.date) ); }while (tryAgain); addSpacersToNode(p, q); makeEdgeBetween(p, q, qStudent); p.moveTo( q.position().x() + random( -1, 1 ), q.position().y() + random( -1, 1 ), 0 ); addedStudents.put(p, s); } void addSpacersToNode( Particle p, Particle r ){ for ( int i = 0; i < physics.numberOfParticles(); ++i ) { Particle q = physics.getParticle( i ); if ( p != q && p != r ){ float theStrength = -SPACER_STRENGTH; if( p == rootParticle || q == rootParticle){ theStrength *= 10; } physics.makeAttraction( p, q, theStrength, 20 ); } } } void makeEdgeBetween( Particle a, Particle b, Student source ){ float theLength = EDGE_LENGTH ; if(a == rootParticle || b == rootParticle){ theLength = 130; }else{ theLength *= (DATE_RANGE - .8*source.date); } physics.makeSpring( a, b, EDGE_STRENGTH, EDGE_STRENGTH, theLength ); } class Student { private int X_SECTOR_SIZE = width/(MAX_DATE - MIN_DATE); private int Y_SECTOR_SIZE = height/NUM_QUARTERS; public int date; private boolean cs; private Particle particle; public Student(int date, boolean cs){ this.date = date - MIN_DATE; this.cs = cs; } public boolean isCS(){ return this.cs; } public String toString() { return "date : " + this.date + ", CS? : " + this.cs; } } void mouseClicked(){ export = true; } void keyPressed() { if (key == CODED) { if (keyCode == UP) { scaleOffset += .1; } else if (keyCode == DOWN) { scaleOffset -= .1; } } println(scaleOffset); }