Innovations in Visualization

Assignment 1

This is code for assignment 1 that you can use as a basis for assignment 2 if your assignment 1 wasn't that fabulous:

import ca.ucalgary.innovis.*;
import java.io.File;

float rootsize;
NAryTree tree;
NAryTreeNode root;
float phyl_angle = 137.5;
int depth;
PFont font;

void setup() {
  rootsize = 600;
  size((int)rootsize,(int)rootsize);
  background(102);
  noLoop();

  File file = new File(dataPath("product_9_197.tree"));
  tree = NAryTreeLoader.loadTree(file);
  depth = tree.getDepth(false);

  root = (NAryTreeNode)tree.getRoot();
  root.setWidth(rootsize);
  root.setPosition(rootsize/2.0,rootsize/2.0);

  smooth();


}

void draw() {
  background(102);

  float cx = (float) (rootsize / 2.0);
  float cy = (float) (rootsize / 2.0);
  fill(138,157,162);
  ellipse(cx, cy, rootsize, rootsize);

  //draw all the children of this node
  drawChildren(root);
}

void drawChildren(NAryTreeNode node){

  //first let's find out how many children we need to draw
  int childCount = node.getChildCount();

  //save the position of the node whose children we want to draw
  double xnode = node.getXPosition();
  double ynode = node.getYPosition();
  double w    = node.getWidth();

  //now let's figure out how much space we have
  //this is done by inverting the formula r = c * sqrt(n)
  //w / 2.0 is our radius available for laying out the children, so...
  //this will be our spacing constant for this node's children:
  double c = (w / 2.0) / sqrt(childCount);

  //the first coding pass. Figuring out the initial position for each node within the parent
  //the caveat is that the children can be located on the rim of the parents' circle,
  //nevertheless, we will first look at the positions

  for(int n = 0; n < childCount; n++){

    //the radius in polar coordinates
    double radius = c * sqrt(n);
    //the angle in polar coordinates
    double angleInDegrees  = n * phyl_angle;
    float angle = radians((float)angleInDegrees);
    //the position in the euclidean coordinate system
    double x = radius * cos(angle) + xnode;
    double y = radius * sin(angle) + ynode;

    //now we get the n'th child and place it
    NAryTreeNode child = (NAryTreeNode) node.getChildAt(n);
    child.setPosition(x,y);
  }

  //the second coding pass. Figuring out the initial size of the nodes
  //the next question is what size each node should get based on where their siblings are located
  //keep in mind that nodes will still sit on the rim of their parent (set the scaleFactor below to 1.0 if you want to know what it looks like
  for(int n = 0; n < childCount; n++){

    //this is the child we're looking at now
    NAryTreeNode child = (NAryTreeNode) node.getChildAt(n);

    //get the distance
    double xchild = child.getXPosition();
    double ychild = child.getYPosition();
    //the closest child should be less than the width of the parent away
    double closestDistance = w;
    String closestN = "";

    //this is costly but will give us correct distances
    for(int z = 0; z < childCount; z++){

      if(z == n) continue; // we don't need to test a distance with ourselves
      //get the siblings and their positions
      NAryTreeNode sibling = (NAryTreeNode) node.getChildAt(z);

      float xsib = (float) sibling.getXPosition();
      float ysib = (float) sibling.getYPosition();

      //calculate the euclidean distance from our child to this sibling
      float distance = dist((float) xchild,(float) ychild, xsib, ysib);
      //double distance = Math.sqrt((xchild - xsib)*(xchild - xsib) + (ychild - ysib) * (ychild - ysib));

      if(distance < closestDistance){
          closestDistance = distance;
          closestN = sibling.getLabel();
      }
    }
    //println("Closest neighbor of: " + child.getLabel() + " is: " + closestN + " distance: " + closestDistance);
    // if we have only one child then make it a bit smaller than the parent so we know it's there
    if(childCount == 1) closestDistance = w / 1.5;

    //now we should know the closest distance and can appropriately size the node
    //now this only ensures that nodes will never ever overlap
    //what we really want is for the nodes to be the biggest size possible
    //this is going to get a bit more complicated
    child.setWidth(closestDistance);
  }

  //the third coding pass. Figuring out how much the nodes go over.
  //for this we need to shoot a ray through the center of the nodes and intersect twice.
  //the second intersection is the one that may lie outside of the parent


  double radiusOverParent = w / 2.0;

   for(int n = 0; n < childCount; n++){
      NAryTreeNode child = (NAryTreeNode) node.getChildAt(n);
      double s = child.getWidth();
      double xchild = child.getXPosition();
      double ychild = child.getYPosition();

      //first let's get the distance from the circle center to the parent center
      float distance = dist( (float)xchild, (float)ychild, (float)xnode, (float)ynode);
      //now add the distance to the radius of the node
      double extend = (distance + s / 2.0);
      //if this length is longer than the radius of the parent, then the child sticks out
      //here we find the longest amount by which a child sticks out
      if (extend >  radiusOverParent) radiusOverParent = extend;
   }

   //fourth coding pass

   //we now know how much we're over and need to scale.
   //the scale factor is the radius of the parent / however much we're over:
   double scaleFactor =  (w / 2.0) / radiusOverParent;

   //now we need to scale and move each child so it sits inside the parent
   for(int n = 0; n < childCount; n++){
      NAryTreeNode child = (NAryTreeNode) node.getChildAt(n);
      double s = child.getWidth();
      double xn = child.getXPosition();
      double yn = child.getYPosition();

      //first move the coordinates to the center, then scale, then move back (Graphics I rules)
      double xtemp = ((xn - xnode) * scaleFactor) + xnode;
      double ytemp = ((yn - ynode) * scaleFactor) + ynode;

      //set the child to the new position
      child.setPosition(xtemp,ytemp);

      //now scale the size
      double tempSize = s * scaleFactor;
      child.setWidth(tempSize);

      //now give the node a pretty bording colour
      fill((255 / depth) * (depth - child.getLevel()));
      //we should finally be done and can draw
      ellipse((float)xtemp,(float) ytemp,(float) tempSize,(float) tempSize);

      //fill(255,0,0);
      //text(child.getLabel(), (float)xtemp, (float) ytemp);

    //now that we're in the last coding pass we go recursively and do the same for all the child's children    
     int children = child.getChildCount();
        if(children > 0) drawChildren(child);
   }
}

void keyPressed()
{
  if (keyCode == LEFT)
  {
    phyl_angle -= 0.1;
    redraw();
  }
  else if (keyCode == RIGHT)
  {
    phyl_angle += 0.1;
    redraw();
  }
}