/* // Relationship Visualizer // by Scott Murray, 2008-12-06 // // Tested with Processing 1.0 // // To run, place a file called "data.csv" in the sketch's data folder. // "data.csv" must contain edges in the form: // from_node_name, to_node_name, value // // Node names can be alpha or numeric. Values must be numeric. // // More info: alignedleft.com */ import processing.pdf.*; int nodeCount; Node[] nodes = new Node[100]; HashMap nodeTable = new HashMap(); int edgeCount; Edge[] edges = new Edge[500]; int edgeMode = 1; float arcRadiusScale = 30; float offsetIncrement = 0.01; float r = 5; int minNodeValue = 1; float easing = 0.1; PFont font; String from; String to; float value; int marked; boolean layoutMethodToggle = true; boolean record; boolean showHelp = true; // // Setup // void setup() { size(800, 600); //size(screen.width, screen.height); colorMode(HSB, 100); font = createFont("SansSerif", 10); loadData(); println("total number of nodes is " + nodeTable.size()); } // // Draw loop // void draw() { //Starts writing this frame to PDF if(record) { beginRecord(PDF, "relationship_vis-####.pdf"); colorMode(HSB, 100); } pushMatrix(); translate(width/2, height/2); background(100); textFont(font); smooth(); //If layout method is force-directed if(layoutMethodToggle) { for (int i = 0; i < edgeCount; i++) { edges[i].relax(); } for (int i = 0; i < nodeCount; i++) { nodes[i].relax(); } for (int i = 0; i < nodeCount; i++) { nodes[i].update(); } } //If layout method is clustering else { for (int i = 0; i < nodeCount; i++) { nodes[i].ease(); } } //Draw edges and nodes for (int i = 0; i < edgeCount; i++) { edges[i].draw(); } for (int i = 0; i < nodeCount; i++) { nodes[i].draw(); } popMatrix(); //Ends writing to PDF if(record) { endRecord(); record = false; } //Draw help if (showHelp) { String help = "Press ? to hide/show this help.\n\nUse the mouse to select and drag nodes.\nPress 1 & 4 for different visualizations.\nL toggles layout method (force-directed or clustered)\nJ/K decrease/increase spacing (cluster layout only)\nS/F slow/accelerate motion (vis. 4 only)\n+/- widen/flatten arcs\n0 shows/hides arc guidelines\nP exports to PDF\nRelationship Visualizer by Scott Murray, \nbased on a network visualization by Ben Fry, \nadapted to allow values to map to edge circles by Gabriel Harp"; float helpWidth = textWidth(help); float helpx = (width / 2) - (helpWidth / 2); float helpy = (height / 2); fill(14, 74, 99, 90); rect(helpx-10, helpy-10, helpWidth+20, 265); fill(0); textAlign(LEFT, TOP); text(help, helpx, helpy); } } // // Loads the data // void loadData() { //Reads in a file called "data.csv", which is expected to have rows of three values each, separated by commas only (no quotes): // 1) String -- The label for the "from" node // 2) String -- The label for the "to" node // 3) float or int -- Some arbitrary value that qualifies the nature of the connection String[] lines = loadStrings("data_gradeNet_documentation.csv"); //Loops once for each row of data for (int i = 0; i < lines.length; i++) { String[] values = split(lines[i], ","); for (int j = 0; j < values.length; j++) { from = values[0]; to = values[1]; value = float(values[2]); marked = int(values[3]); } addEdge(from, to, value); } } // // Creates an edge // void addEdge(String fromLabel, String toLabel, float connectionValue) { Node from = findNode(fromLabel); Node to = findNode(toLabel); float value = connectionValue; from.increment(); to.increment(); //Check to see whether this Edge already exists for (int i = 0; i < edgeCount; i++) { if (edges[i].from == from && edges[i].to == to) { //If it does, then... edges[i].increment(); edges[i].addValue(value); return; } } //If it doesn't exist yet, then create it, but first check to see if its opposing edge exists, so we can define giveOrReceive boolean giveOrReceive = true; for (int i = 0; i < edgeCount; i++) { if (edges[i].from == to && edges[i].to == from) { //If from == to and to == from giveOrReceive = !edges[i].giveOrReceive; } } Edge e = new Edge(from, to, value, marked, giveOrReceive); e.increment(); if (edgeCount == edges.length) { edges = (Edge[]) expand(edges); } edges[edgeCount++] = e; } // // Check to see if a node already exists // Node findNode(String label) { label = label.toLowerCase(); Node n = (Node) nodeTable.get(label); if (n == null) { return addNode(label); } return n; } // // Creates a new node // Node addNode(String label) { Node n = new Node(label); if (nodeCount == nodes.length) { nodes = (Node[]) expand(nodes); } nodeTable.put(label, n); nodes[nodeCount++] = n; return n; } // // Handle mouse clicks and drags // Node selection; void mousePressed() { //Ignore anything greater than this distance float closest = 20; for (int i = 0; i < nodeCount; i++) { Node n = nodes[i]; float d; if (layoutMethodToggle) { d = dist(mouseX, mouseY, n.x, n.y); } else { d = dist(mouseX, mouseY, n.x + width/2, n.y + height/2); } if (d < closest) { selection = n; closest = d; } } if (selection != null) { if (mouseButton == LEFT) { selection.fixed = true; } else if (mouseButton == RIGHT) { selection.fixed = false; } } } void mouseDragged() { if (selection != null) { if (layoutMethodToggle) { selection.x = mouseX; selection.y = mouseY; } else { selection.x = mouseX - width/2; selection.y = mouseY - height/2; selection.destx = mouseX - width/2; selection.desty = mouseY - height/2; } } } void mouseReleased() { selection = null; } // // Handle keyboard input // public void keyPressed() { if (key == '1') { edgeMode = 1; println("edgeMode now == " + edgeMode); } else if (key == '2') { edgeMode = 2; offsetIncrement = 0.01; println("edgeMode now == " + edgeMode); } else if (key == '3') { edgeMode = 3; offsetIncrement = 0.0001; println("edgeMode now == " + edgeMode); } else if (key == '4') { edgeMode = 4; offsetIncrement = 0.005; println("edgeMode now == " + edgeMode); } else if (key == '5') { edgeMode = 5; offsetIncrement = 0.0001; println("edgeMode now == " + edgeMode); } else if (key == 'p') { //Make PDF record = true; } else if (key == '=') { //Wider arcs if(arcRadiusScale > 1.0) { arcRadiusScale -= 1; } } else if (key == '-') { //Flatter arcs arcRadiusScale += 1; } else if (key == 'o' || key == '0') { //Toggles arc lines on/off for(int i = 0; i < edgeCount; i++) { edges[i].drawArcLines = !edges[i].drawArcLines; } } else if (key == 'f') { //Faster motion offsetIncrement += 0.0005; println("offsetIncrement now == " + offsetIncrement); } else if (key == 's') { //Slower motion offsetIncrement -= 0.0005; if (offsetIncrement < 0) { offsetIncrement = 0; } println("offsetIncrement now == " + offsetIncrement); } else if (key == 'r') { //Larger edge circles r += 0.5; } else if (key == 'e') { //Smaller edge circles r -= 0.5; } else if (key == 'l') { layoutMethodToggle = !layoutMethodToggle; if(!layoutMethodToggle) { //If switching into cluster layout clusterAllObjects(); } else { //If switching into force-directed layout for(int i = 0; i < nodeCount; i++) { nodes[i].x = random(0, width); nodes[i].y = random(0, height); } } } else if (key == ']') { //Increase filter threshold by one, and hide nodes below threshold minNodeValue++; for(int i = 0; i < nodeCount; i++) { if(nodes[i].count == minNodeValue - 1) { //If the node's "count" or sum total of connections is one less than the minNodeValue nodes[i].visible = false; //then hide the node for (int j = 0; j < edgeCount; j++) { //and also loop through and hide any edges that were connected to this node. if (edges[j].from.label == nodes[i].label || edges[j].to.label == nodes[i].label) { //If either the edge's "from" OR "to" points are equal to the node's name (label) edges[j].visible = false; //then make the edge invisible. } } } } } else if (key == '[') { //Decrease filter threshold by one, and reveal nodes above threshold minNodeValue--; for(int i = 0; i < nodeCount; i++) { if(nodes[i].count == minNodeValue) { //If the node's "count" or sum total of connections is one more than the minNodeValue nodes[i].visible = true; //then reveal the node for (int j = 0; j < edgeCount; j++) { //and also loop through and find any edges that were connected to this node. if (edges[j].from.label == nodes[i].label || edges[j].to.label == nodes[i].label) { //If the edge's "from" OR "to" points are equal to the node's name (label) if (edges[j].from.visible && edges[j].to.visible) { //AND if both nodes are visible edges[j].visible = true; //then make the edge visible. } } } } } } else if (key == 'k') { if(!layoutMethodToggle) { //If switching in cluster layout mode spacing += 10; clusterAllObjects(); } } else if (key == 'j') { if(!layoutMethodToggle) { //If switching in cluster layout mode if(spacing > 0) { spacing -= 10; clusterAllObjects(); } } } else if (key == '?' || key == '/') { showHelp = !showHelp; } }