• fullscreen
  • graph.pde
  • node.pde
  • /*
    Daniel Davis, 2010, nzarchitecture.com
    
    Draws a directed graph and dynamically relaxes the graph. 
    Nodes in the graph can also be dragged around.
    
    */
    
    HashMap graph;
    PFont fontA;
    
    int stageSize = 900;  //size of the stage
    int boxHeight = 10;    //height of box in pixels
    int border = 5;        //border around box in pixels
    float speed = 5;       //speed to move nodes together, 1 = slow, 100 = fast.
    float bubble = 80;    //distance nodes will try to stay apart
    float curveSize = 50; //size of the curve coming out of node, 0 = no curve.
    
    color backgroundColor = color(227, 224, 213);
    color lineColor = color(83, 100, 105);
    color boxColor = color(185, 200, 201);
    color hoverColor = color(214, 186, 204);
    color textColor = color(83, 100, 105);
    
    
    String fileName = "graph.txt"; //name of file with connection data, expecting it in this format:
    //a, b, b, b
    //a, b, b, b
    //a
    //where each row represents one node in the graph
    //a is the name of the node
    //b is the name of the nodes that are outputs for the node
    //there can be any number of outputs as long as they are seperated by commas.
    //if there are no b's, then the node only has inputs
    //inputs do not need to be stated, they are implied from the outputs.
    
    
    void setup() 
    {
      size(stageSize, stageSize);
      background(backgroundColor);
      
      char[] letters = new char[]{'a'};
      fontA = createFont("Helvetica", 16, true, letters);
    
      //create blank graph
      graph = new HashMap();
      String[] lines = loadStrings(fileName);
      
      //add the nodes into the graph
      for(int i =0; i <lines.length; i++)
      {
        String[] pieces = split(lines[i], ',');
        
        float startX = random(width);
        float startY = random(height);
        String name = trim(pieces[0]);
        
        graph.put(name, new node(startX, startY, name));
      }
      
      //add the connections
      for(int i =0; i <lines.length; i++)
      {
        String[] pieces = split(lines[i], ',');
        
        String name = trim(pieces[0]);
        for(int j=1; j<pieces.length; j++)
        {
          String output = trim(pieces[j]);
          
          if(output != "" && output != null) 
          {
            if(graph.containsKey(name) && graph.containsKey(output))
            {
              ((node)graph.get(name)).addOutput(output);
              ((node)graph.get(output)).addInput(name);
            } else {
              println("Output/name does not match");
            }
          }
        }
      }
      
      
      
      
    }
    
    void draw() 
    {
      background(backgroundColor);
      
      Iterator i = graph.entrySet().iterator();  // Get an iterator
    
      while (i.hasNext()) {
        Map.Entry me = (Map.Entry)i.next();
        ((node)me.getValue()).update();
        ((node)me.getValue()).draw();
      }
    }
    
    
    void mousePressed() 
    {
      Iterator i = graph.entrySet().iterator();
    
      boolean found = false;
      while (i.hasNext() && !found) {
        Map.Entry me = (Map.Entry)i.next();
        found = ((node)me.getValue()).pressed(); 
      }
    }
    
    void mouseReleased() 
    {
      Iterator i = graph.entrySet().iterator();
    
      while (i.hasNext()) {
        Map.Entry me = (Map.Entry)i.next();
        ((node)me.getValue()).released(); 
      }
    }
    
    /*
    A node has:
    an xy position on the screen
    a string name, which is also its key in the graph hash table
    a collection of neighbours: inputs and outputs
    
    The node will try to move towards the centroid of its neighbours
    in a form of dynamic relaxation.
    If the node is dragged, it will follow the mouse. 
    
    Any output is drawn coming form the right side of the node
    Any input is drawn coming to the left side of the node
    
    */
    
    
    
    class node {
      
      private float x, y, width, height, inputX, inputY, outputX, outputY, textX, textY, clickedX, clickedY = 0;
      private String text = "";
      private ArrayList inputs;
      private ArrayList outputs;
      private boolean move = false;
      
      
      
      // Contructor
      //ix = inital x position
      //iy = inital y position
      //iText = name of this node
      node(float ix, float iy, String iText) {
        x = iy;
        y = ix;
        text = iText;
        
        inputs = new ArrayList();
        outputs = new ArrayList();
        
        //need to draw the font before we can know and set the width
        textFont(fontA); 
        text(text, x, y); 
        
        width = textWidth(text) + 2 * border;
        height =  -1 * (boxHeight + 2 * border);
        setXY();
      }
      
      
      
      public void addInput(String input)
      {
        inputs.add(input);
      }
      
      
      
      
      public void addOutput(String output)
      {
        outputs.add(output);
      }
      
      
      
      
      //updates the xy position of the node.
      //if the node is currently being dragged, xy is from the mouse.
      //otherwise,
      //loops through all the inputs and outputs
      //finds the vector towards the centroids of the inputs and outputs
      //this vector is based on the line the joins the two nodes together, rather than taking their centers. 
      //moves node towards centroid
      //
      //then updates the xy values of the node.
      public void update() {
        if(move) 
        {
          x = mouseX + clickedX;
          y = mouseY + clickedY;
        } else {
          float newX = 0;
          float newY = 0;
          int count = 1;
          
          if(inputs != null) 
          {
            for(int i =0; i < inputs.size(); i++)
            {
              float[] newXY = moveVector((String)inputs.get(i), false);
              
              newX += newXY[0];
              newY += newXY[1];
              count++;
            }
          }
          
          if(outputs != null)
          {
            for(int i =0; i < outputs.size(); i++)
            {
              float[] newXY = moveVector((String)outputs.get(i), true);
              
              newX += newXY[0];
              newY += newXY[1];
              count++;
            }
          }
          
          //keep nodes from eachother
          Iterator i = graph.entrySet().iterator();  // Get an iterator
          while (i.hasNext()) {
            Map.Entry me = (Map.Entry)i.next();
            node nextNode = (node)me.getValue();
            
            float diffX = nextNode.getX() - x;
            float diffY = nextNode.getY() - y;
            
            float distance = sqrt(sq(diffX) + sq(diffY));
            
            if(distance > 0 && distance < bubble * 2)
            {
              newX -= speed * 10* (diffX * bubble / sq(distance));
              newY -= speed * 10 * (diffY * bubble / sq(distance));
            }
          }
          
          //average vector
          newX /= count;
          newY /= count;
          
          //move towards centroid:
          x += (newX*speed / 100);
          y += (newY*speed / 100);
        }
        
         setXY();
      }
      
     
      
      public void draw() {
       drawBox();
       drawText();
       drawCurves();
      }
      
      //callled when the mouse is pressed
      //checks to see if the mouse is currently over the node
      //if it is, sets move to true and returns true,
      //else returns false
      public boolean pressed() 
      { 
        if (over()) { 
          move = true; 
          clickedX = x-mouseX;
          clickedY = y - mouseY;
          return true;
        } else { 
          move = false; 
        }  
        return false;
      } 
    
      //called when the mouse is released
      //stops any nodes moving
      public void released() 
      { 
        move = false; 
      } 
      
      
      
      
      
      /*PRIVATE*/
      
      //finds the vector between the nodeId, and the passedId.
      //Note that the vector is taken between the output point and the input point
      //Size of vector is adjusted so that it pulls the nodes together when they are 
      //far away and pushes them apart when they are close together
      private float[] moveVector(String nodeId, boolean nodeIsOutput)
      {
         node n = (node)graph.get(nodeId);
         
         float nodeX = 0;
         float nodeY = 0;
         float myX = 0;
         float myY = 0;
         
         if(nodeIsOutput)
         {
           myX = outputX;
           myY = outputY;
           nodeX = n.getInX();
           nodeY = n.getInY();
         } else {
           myX = inputX;
           myY = inputY;
            nodeX = n.getOutX();
            nodeY = n.getOutY();
         }
         
         float diffX = nodeX - myX;
         float diffY = nodeY - myY;
          
          //note that we use the distance between nodes and the log function. 
          //This means that when nodes get within the bubble distance, we reverse the direction
          //of the vector, so that the nodes start replelling rather than attracting. 
          float distance = sqrt(sq(diffX) + sq(diffY));
          float direction = log(distance / bubble);
          
         float[] r = new float[]{diffX * direction, diffY* direction};
         return r;
      }
      
      
      
      private void drawBox()
      {
        if(over()) 
        {
          fill(hoverColor);
        } else {
          fill(boxColor);
        }
        stroke(lineColor);
        rect(round(x), round(y), width, height);
        //rect(x, y, width, height);
      }
      
      private void drawText()
      {
        fill(textColor);
        textFont(fontA); 
        text(text, textX, textY); 
      }
      
      //draws output curves
      //Only need to draw the output curves, because the other nodes that are drawing 
      //their output curves, will draw the inputs for us. 
      private void drawCurves()
      {
        noFill();
        stroke(lineColor);
        if(outputs != null)
        {
          for(int i = 0; i < outputs.size(); i++)
          {
            node inNode = (node)graph.get(outputs.get(i));
            float inX = inNode.getInX();
            float inY = inNode.getInY();
            bezier(outputX, outputY, outputX + curveSize, outputY, inX - curveSize, inY, inX, inY);
          }
        }
      }
      
      
      // Test to see if mouse is over node
      private boolean over() 
      {
        if(mouseX > x && mouseX < x+width) 
        {
          if(mouseY < y && mouseY > y + height) 
          {
            return true;
          }
        }
        return false;
      }
      
      
      //set all the xy values from the current xy value
      private void setXY()
      {
        if(x < 0) x = 0;
        if(x + width > stageSize) x = stageSize - width;
        if(y + height < 0) y = - height;
        if(y > stageSize) y = stageSize;
        textX = x+border;
        textY = y-border;
        inputX = x;
        inputY = y + height / 2;
        outputX = x+width;
        outputY = y + height / 2;
      }
     
      
      
      
      
      
      
      /*GETS*/
      
      public float getX()
      {
        return x;
      }
      
      public float getY()
      {
        return y;
      }
      
      public float getInX()
      {
        return inputX;
      }
      
      public float getInY()
      {
        return inputY;
      }
      
      public float getOutX()
      {
        return outputX;
      }
      
      public float getOutY()
      {
        return outputY;
      }
    }
    
    
    

    code

    tweaks (0)

    about this sketch

    This sketch is running as Java applet, exported from Processing.

    license

    advertisement

    nzarchitecture.com

    Directed graph, dynamic relaxation

    Add to Faves Me Likey@! 20
    You must login/register to add this sketch to your favorites.

    Draws a network based on a directed graph. Inputs go into the left, outputs come from the right.

    To change what the graph displays, edit the graph.txt file.

    The file is in the format:
    a, b, b, b
    a, b, b, b

    Each row is a new node on the graph. A is the name of that node. B is the name of a node that is an output for the node.

    You need to login/register to comment.