//////////////////////////////////////////////////////////// // // A word of caution: This code can be optimized - I made it in quite a hurry. // A large chunk is from an example from Traer Physics. // // Feel free to use / modify this code however you wish. // I would be happy if you would make a reference to the aharef.info site! // //////////////////////////////////////////////////////////// import traer.physics.*; import traer.animation.*; import java.util.Iterator; import java.util.ArrayList; import java.util.HashMap; import processing.net.*; import org.htmlparser.*; import org.htmlparser.util.*; import org.htmlparser.filters.*; import org.htmlparser.nodes.*; int totalNumberOfNodes = 0; ArrayList elements = new ArrayList(); ArrayList parents = new ArrayList(); int nodesAdded = 0; int maxDegree = 0; HashMap particleNodeLookup = new HashMap(); HashMap particleInfoLookup = new HashMap(); ParticleSystem physics; Smoother3D centroid; // MAKE SURE YOU CHANGE THIS! I might change this site in the future. // Simply point to a site on your own server that gets the html from any other site. private String urlPath = "http://www.forreststevens.com/htmlgraph/returnHTML.php?url="; private String content; float NODE_SIZE = 30; float EDGE_LENGTH = 50; float EDGE_STRENGTH = 0.2; float SPACER_STRENGTH = 2000; Particle activeParticle = null; int intFontX = 0; int intFontSize = 12; int intTextRow = 0; int intRelMouseX = 0; int intRelMouseY = 0; float fltCentroidZAdj = 0; int intCentroidXAdj = 0; int intCentroidYAdj = 0; final String GRAY = "155,155,155"; final String BLUE = "0,0,155"; final String ORANGE = "255,155,51"; final String YELLOW = "255,255,51"; final String RED = "255,0,0"; final String GREEN = "0,155,0"; final String VIOLET = "204,0,255"; final String BLACK = "0,0,0"; final String PINKISH = "255,102,153"; final String PINK = "255,51,102"; void setup() { size(600, 600); // if you want to run it locally from within processing, comment the two following lines, and define urlPath as http://www.whateveryorsite.com String urlFromForm = param("urlFromForm"); urlPath += urlFromForm; //urlPath += "http://www.culhamwriting.com"; smooth(); framerate(24); strokeWeight(2); ellipseMode( CENTER ); physics = new ParticleSystem( 0, 0.25 ); centroid = new Smoother3D( 0.0, 0.0, 1.0,0.8 ); initialize(); this.getDataFromClient(); } void getDataFromClient() { try { org.htmlparser.Parser ps = new org.htmlparser.Parser (); ps.setURL(urlPath); OrFilter orf = new OrFilter(); NodeFilter[] nfls = new NodeFilter[1]; nfls[0] = new TagNameFilter("html"); orf.setPredicates(nfls); NodeList nList = ps.parse(orf); Node node = nList.elementAt (0); this.parseTree(node); EDGE_STRENGTH = (1.0 / maxDegree) * 5; if (EDGE_STRENGTH > 0.2) EDGE_STRENGTH = 0.2; } catch (Exception e) { e.printStackTrace(); } } void initialize() { physics.clear(); } void parseTree(Node node) { int degree = 0; if (node == null) return; String nodeText = node.getText(); if (node instanceof TagNode && !((TagNode)node).isEndTag()) { //println(((TagNode)node).getTagName()); totalNumberOfNodes++; elements.add(node); parents.add(((TagNode)node).getParent()); } NodeList children = node.getChildren(); if (children == null) return; SimpleNodeIterator iter = children.elements(); while(iter.hasMoreNodes()) { Node child = iter.nextNode(); if (child instanceof TagNode && !((TagNode)child).isEndTag()) degree++; parseTree(child); } if (degree > maxDegree) maxDegree = degree; } Particle addNode(Particle q) { Particle p = physics.makeParticle(); if (q == null) return p; addSpacersToNode( p, q ); makeEdgeBetween( p, q ); float randomX = (float)((Math.random() * 0.5) + 0.5); if (Math.random() < 0.5) randomX *= -1; float randomY = (float)((Math.random() * 0.5) + 0.5); if (Math.random() < 0.5) randomY *= -1; p.moveTo( q.position().x() +randomX, q.position().y() + randomY, 0 ); return p; } void draw() { if (nodesAdded < totalNumberOfNodes) { this.addNodesToGraph(); } else { if (EDGE_STRENGTH < 0.05) EDGE_STRENGTH += 0.0001; } physics.tick( 1.0 ); if (physics.numberOfParticles() > 1) { updateCentroid(); } centroid.tick(); background(255); translate(width/2, height/2); scale(centroid.z()); translate( -centroid.x(), -centroid.y() ); drawNetwork(); } void addNodesToGraph() { Particle newParticle; Node nodeToAdd = (Node)elements.get(nodesAdded); Node parentNode = (Node)parents.get(nodesAdded); if (parentNode == null) { // this is the html element newParticle = addNode(null); } else { Particle parentParticle = (Particle)particleNodeLookup.get(parentNode); newParticle = addNode(parentParticle); } particleNodeLookup.put(nodeToAdd,newParticle); String nodeColor = GRAY; if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("a")) nodeColor = BLUE; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("ul")) nodeColor = PINK; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("ol")) nodeColor = PINK; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("li")) nodeColor = PINKISH; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("div")) nodeColor = GREEN; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("html")) nodeColor = BLACK; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("tr")) nodeColor = RED; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("td")) nodeColor = RED; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("table")) nodeColor = RED; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("br")) nodeColor = ORANGE; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("p")) nodeColor = ORANGE; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("blockquote")) nodeColor = ORANGE; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("h1")) nodeColor = ORANGE; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("h2")) nodeColor = ORANGE; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("h3")) nodeColor = ORANGE; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("img")) nodeColor = VIOLET; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("form")) nodeColor = YELLOW; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("input")) nodeColor = YELLOW; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("textarea")) nodeColor = YELLOW; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("select")) nodeColor = YELLOW; else if (((TagNode)nodeToAdd).getTagName().equalsIgnoreCase("option")) nodeColor = YELLOW; String strDisplayTag = "", strKey = "", strValue = ""; Hashtable arrTagAttrib = ((TagNode)nodeToAdd).getAttributes(); strDisplayTag = (String)((TagNode)nodeToAdd).toHtml(); strDisplayTag = strDisplayTag.substring(0, strDisplayTag.indexOf(">") + 1); strDisplayTag = strDisplayTag.replace(",", "-"); particleInfoLookup.put(newParticle, nodeColor + "," + strDisplayTag); nodesAdded++; //println(nodesAdded + " of " + totalNumberOfNodes + " (" + nodeToAdd.getNodeName() + ")"); } void drawNetwork() { String strTagName = ""; // Load the font. Fonts are located within the // main Processing directory/folder and they // must be placed within the data directory // of your sketch for them to load PFont fontA = loadFont("Ziggurat-HTF-Black-32.vlw"); //PFont fontA = loadFont("CourierNew36.vlw"); // Set the font and its size (in units of pixels) textFont(fontA, intFontSize/centroid.getZTarget()); intRelMouseX = (int)((mouseX - (width / 2))/centroid.getZTarget() + centroid.getXTarget()); intRelMouseY = (int)((mouseY - (height / 2))/centroid.getZTarget() + centroid.getYTarget()); //intFontX = (int)((mouseX + centroid.getXTarget() - (width / 2))*centroid.getZTarget()); //intTextRow = (int)((mouseY + centroid.getYTarget() - (height / 2))*centroid.getZTarget()); intTextRow = 0; //ellipse( intRelMouseX, intRelMouseY, NODE_SIZE, NODE_SIZE ); /* text( "mouseX: " + mouseX, intFontX, intTextRow++ * intFontSize ); text( "mouseY: " + mouseY, intFontX, intTextRow++ * intFontSize ); text( "centroidX: " + centroid.getXTarget(), intFontX, intTextRow++ * intFontSize ); text( "centroidY: " + centroid.getYTarget(), intFontX, intTextRow++ * intFontSize ); text( "centroidZ: " + centroid.getZTarget(), intFontX, intTextRow++ * intFontSize ); text( "RelMouseX: " + intRelMouseX, intFontX, intTextRow++ * intFontSize ); text( "RelMouseY: " + intRelMouseY, intFontX, intTextRow++ * intFontSize ); */ // draw edges stroke( 0 ); 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(); // draw vertices noStroke(); for ( int i = 0; i < physics.numberOfParticles(); ++i ) { Particle v = physics.getParticle(i); if (i == 1) { /* text( "htmlX: " + (int)v.position().x(), intFontX, intTextRow++ * intFontSize ); text( "htmlY: " + (int)v.position().y(), intFontX, intTextRow++ * intFontSize ); */ } String info = (String)(particleInfoLookup.get(v)); if (info != null) { String[] infos = info.split(","); fill(Integer.parseInt(infos[0]),Integer.parseInt(infos[1]),Integer.parseInt(infos[2])); strTagName = infos[3]; } else { fill(155); } ellipse( v.position().x(), v.position().y(), NODE_SIZE, NODE_SIZE ); } if ( activeParticle != null ) { activeParticle.moveTo( intRelMouseX, intRelMouseY, 0); activeParticle.setVelocity( (mouseX - pmouseX), (mouseY - pmouseY), 0 ); // this is so you can throw it... /* text( "activeX: " + (int)activeParticle.position().x(), intFontX, intTextRow++ * intFontSize ); text( "activeY: " + (int)activeParticle.position().y(), intFontX, intTextRow++ * intFontSize ); */ String info = (String)(particleInfoLookup.get(activeParticle)); if (info != null) { String[] infos = info.split(","); fill(Integer.parseInt(infos[0]),Integer.parseInt(infos[1]),Integer.parseInt(infos[2])); strTagName = infos[3]; fill(64); text( strTagName, activeParticle.position().x() + 20, activeParticle.position().y() + 20 ); } } } void keyPressed() { if ( key == 'c' ) { initialize(); return; } else if ( key == '>') { fltCentroidZAdj = fltCentroidZAdj + 0.1; return; } else if ( key == '<') { fltCentroidZAdj = fltCentroidZAdj - 0.1; return; } else if ( key == CODED) { if (keyCode == UP) { intCentroidYAdj = intCentroidYAdj + 10; return; } else if ( keyCode == DOWN ) { intCentroidYAdj = intCentroidYAdj - 10; return; } else if ( keyCode == LEFT ) { intCentroidXAdj = intCentroidXAdj + 10; return; } else if ( keyCode == RIGHT ) { intCentroidXAdj = intCentroidXAdj - 10; return; } } } void mousePressed() { float curMin = 9999, newMin = 9999; for ( int i = 0; i < physics.numberOfParticles(); ++i ) { Particle v = physics.getParticle(i); newMin = min(curMin, dist(intRelMouseX, intRelMouseY, (int)v.position().x(), (int)v.position().y())); if (newMin != curMin) { activeParticle = v; curMin = newMin; } } return; } void mouseReleased() { activeParticle = null; return; } 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 + intCentroidXAdj, yMin + 0.5*deltaY + intCentroidYAdj, height/(deltaY+50) + fltCentroidZAdj); } else { centroid.setTarget(xMin + 0.5*deltaX + intCentroidXAdj, yMin + 0.5*deltaY + intCentroidYAdj, width/(deltaX+50) + fltCentroidZAdj); } } void addSpacersToNode( Particle p, Particle r ) { for ( int i = 0; i < physics.numberOfParticles(); ++i ) { Particle q = physics.getParticle(i); if (p != q && p != r) { physics.makeAttraction(p, q, -SPACER_STRENGTH, 20); } } } void makeEdgeBetween(Particle a, Particle b) { physics.makeSpring( a, b, EDGE_STRENGTH, EDGE_STRENGTH, EDGE_LENGTH ); }