import processing.core.*; 
import processing.xml.*; 

import processing.core.*; 
import java.io.*; 
import de.bezier.data.sql.*; 
import controlP5.*; 
import processing.core.*; 
import processing.opengl.*; 
import javax.media.opengl.GL; 
import peasy.*; 

import java.applet.*; 
import java.awt.Dimension; 
import java.awt.Frame; 
import java.awt.event.MouseEvent; 
import java.awt.event.KeyEvent; 
import java.awt.event.FocusEvent; 
import java.awt.Image; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 












//import com.jamonapi.*;

public class SandeepBhatP3 extends PApplet{

	private static final long serialVersionUID = 1L; // SBK:warning fix in Eclipse
	PFont font10, font14, font18, font26, font48;
	MySQL msql;

	// Database constants
	//-------------------------------------------------------------------------
	String gUser      = "mat259";
	String gPassword  = "V1sual1zat1on";
	String gDatabase  = "spl_all";//"spl_years";
	String gTableName = "transactionsallID";//"transactions2010";
	int    gYearIdx   = 0;
	int    gYearIdx2Disp = 0;
	String[] gYearNames  = {"2010","2009","2008"};//,"2007","2006","2005"};
	String[] gMonthNames = {"December", "November", "October", "September", 
                            "August", "July", "June", "May",
                            "April", "March", "February", "January"
	                       };
	String[] gMonthNamesShort = {"Dec", "Nov", "Oct", "Sep", 
				                 "Aug", "Jul", "Jun", "May",
				                 "Apr", "Mar", "Feb", "Jan"
				                };
	String[] gMonths  = {//---------------------------------
			             "2010 Dec","2010 Nov","2010 Oct",
			             "2010 Sep","2010 Aug","2010 Jul",
			             "2010 Jun","2010 May","2010 Apr",
			             "2010 Mar","2010 Feb","2010 Jan",
			             //---------------------------------
						 "2009 Dec","2009 Nov","2009 Oct",
			             "2009 Sep","2009 Aug","2009 Jul",
			             "2009 Jun","2009 May","2009 Apr",
			             "2009 Mar","2009 Feb","2009 Jan",
			             //---------------------------------
						 "2008 Dec","2008 Nov","2008 Oct",
			             "2008 Sep","2008 Aug","2008 Jul",
			             "2008 Jun","2008 May","2008 Apr",
			             "2008 Mar","2008 Feb","2008 Jan"
			             //---------------------------------
//						 "2007 Dec","2007 Nov","2007 Oct",
//			             "2007 Sep","2007 Aug","2007 Jul",
//			             "2007 Jun","2007 May","2007 Apr",
//			             "2007 Mar","2007 Feb","2007 Jan",
//			             //---------------------------------
//						 "2006 Dec","2006 Nov","2006 Oct",
//			             "2006 Sep","2006 Aug","2006 Jul",
//			             "2006 Jun","2006 May","2006 Apr",
//			             "2006 Mar","2006 Feb","2006 Jan",
//			             //---------------------------------
//						 "2005 Dec","2005 Nov","2005 Oct",
//			             "2005 Sep","2005 Aug","2005 Jul",
//			             "2005 Jun","2005 May","2005 Apr"
			            };
	int[] gMonthsPerYear = {12, 12, 12};//, 12, 12, 9};
	static int C_LIMIT_PER_QUERY = 1000;
	// Specify for transactionsallID table of spl_all DB only
	//static int[] C_START_ID = {40498544, 29391189, 19087751, 11561925, 4407072, 1};
	// Found using query "select count(*) from transactionsallID";
	// "select id,title,deweyClass from transactionsallID where (year(ckinDateTime)=2009) limit 1";
	//static int[] C_END_ID   = {50846071, 40498544, 29391189, 19087751, 11561925, 4407072};
	// Found after setting C_FIND_MONTH_IDS == true;
	static int[] C_START_ID =
						{
		                    // 2010
							50006655, 49184631, 48307861, 47570001,
							46751044, 45866535, 44958336, 44082689,
							43187916, 42220862, 41396714, 40498544,
							// 2009
							39620823, 38742128, 37771543, 37003118,
							36067250, 35089551, 34123347, 33183558,
							32274641, 31270531, 30356163, 29391189,
							// 2008
							28531660, 27785020, 26850531, 25959326,
							25017734, 24087520, 23207326, 22352935,
							21570317, 20711260, 19930167, 19087751
//							// 2007
//							18325734, 17556740, 17060169, 16352503,
//							15579156, 14843530, 14286530, 13649665,
//							12926094, 12212059, 11856971, 11561925,
//							// 2006
//							10982535, 10344761,  9648590,  9076524,
//							 8348333,  7698502,  7021026,  6619979,
//							 6082070,  5403857,  4858217,  4407072,
//							// 2005
//							 3824098,  3227676,  2653016,  2149288,
//							 1775281,  1333560,   780761,   279394,
//							       1
	                    };
	static int[] C_END_ID =
						{
					        // 2010
							50846071, 50006655, 49184631, 48307861,
							47570001, 46751044, 45866535, 44958336,
							44082689, 43187916, 42220862, 41396714,
							// 2009
							40498544, 39620823, 38742128, 37771543,
							37003118, 36067250, 35089551, 34123347,
							33183558, 32274641, 31270531, 30356163,
							// 2008
							29391189, 28531660, 27785020, 26850531,
							25959326, 25017734, 24087520, 23207326,
							22352935, 21570317, 20711260, 19930167
//							// 2007
//							19087751, 18325734, 17556740, 17060169,
//							16352503, 15579156, 14843530, 14286530,
//							13649665, 12926094, 12212059, 11856971,
//							// 2006
//							11561925, 10982535, 10344761,  9648590,
//							 9076524,  8348333,  7698502,  7021026,
//							 6619979,  6082070,  5403857,  4858217,
//							// 2005
//						     4407072,  3824098,  3227676,  2653016,
//							 2149288,  1775281,  1333560,   780761,
//							 279394
					    };

	// Global constants
	//-------------------------------------------------------------------------
	static boolean C_LOAD_PREV_COUNT = true;  // Counter will be loaded to previously computed value
	static int     C_PREV_COUNT      = 0;  // Item number till which counting was done previously
	static boolean C_COMPUTE_COUNT   = false; // True=>Continue counting from C_PREV_COUNT item
	static File    C_FILE; // Used for getting file separator in platform independent manner
	static int     C_PRINT_EVERY     = 10000;  // decides when item count is printed
	static int     C_SAVE_EVERY      = 100000; // decides when the deweyletter counter is saved to file
	static boolean C_DB_WITH_ID      = true;
	static boolean C_DRAW_GRAPH      = true; // True calls a "do nothing" draw function
	static boolean C_FIND_MONTH_IDS  = false; // True finds the starting IDs for a month

	// Select words to ignore and words to include
	//-------------------------------------------------------------------------
	String[] ignoreKeyWords = {"in","of","the", "a", "an","on"};
	String[][] includeKeyWords =
	      {
	        {"war", "hate", "evil", "bad"},
	        {"love", "like", "good", "peace"}
//	        {"photography", "camera", "SLR", "digital","lens"}
	      };
	String[] displayKeyWords = {"all","hate","love"};//,"photography"}; // "all" should be first
	int gDispKeyWordsIdx = 0;

	// For mouse hovering
	//-------------------------------------------------------------------------
	float gridMinX;
	float gridMinY;
	float gridMaxX;
	float gridMaxY;

	// For plotting
	//-------------------------------------------------------------------------
	DeweyLetterMatrix[][] DLM; // [displayKeyWords][gMonths]
	DeweyLetterStrings DLS;
	Cell[][][][] deweyLetterGrid;
	int x0 = 20;
	int y0 = 20;
	int z0 = 0;
	int gGridValMax  = 2000;
	int gGridValMin  = 0;
	int gNumBins     = 256;
	int baseHue      = 100;
	// For Column Bar Graph
	int[][][] rowAvg;
	int[][][] rowMax;
	int[][][] rowMin;
	int[][][] rowSum;
	// For Row Bar Graph
	int[][][] colAvg;
	int[][][] colMax;
	int[][][] colMin;
	int[][][] colSum;
	// Labelling
	//String[] DeweyNamesLong;
	//String[] DeweyText;
	//String[] LettersText;
	//String[] DeweyInfoText;
	// Window layout
	static int cCellHt    = 16;
	static int cCellHtGap = 2;
	static int cCellWd    = 8;
	static int cCellWdGrp = 10;
	static int cCellWdGap = 2;
	static int cColBarGap = 16;
	static int cColBarMaxHt = 60;
	static int cColBarMaxWd = 4;
	static int cRowBarGap   = 20;
	static int cRowBarMaxWd = 68;
	static int cRowBarMaxHt = 8;
	static int cInfoBoxWd   = 200;
	static int cTitleBoxHt  = 80;

	static int cColorMapAlpha = 191;
	ColorMaps colorMap;
	
	// Controlling of multiple screens
	//-------------------------------------------------------------------------
	int gScreenType = 0; // 0 = Main menu, 1 = 2D, 2 = 3D, 3 = Animation
	
	// Camera parameters
	//-------------------------------------------------------------------------
	PeasyCam camera;
	PMatrix3D currCameraMatrix;
	PGraphics3D g3;
	float[] rotations; // Rotation needed to face the camera in space
	// For 3D Screen
	double[] s3DCamRot  = {0.62577736f, -0.5616385f, 0.3131453f};
//	float s3DCamPitch  = (float)0.62577736;
//	float s3DCamYaw    = (float)-0.5616385;
//	float s3DCamRoll   = (float)0.3131453;
	double s3DCamDist  = 2800;
	long s3DresetTime  = 1500;
	int[] s3Dlook = {1150, 1270, 605};
    // For Animation Screen
	double[] sAnimCamRot  = {1.6247946f, -0.58396703f, -3.0836608f};
//	float sAnimCamPitch  = (float)1.6247946;
//	float sAnimCamYaw    = (float)-0.58396703;
//	float sAnimCamRoll   = (float)-3.0836608;
	double sAnimCamDist  = 2100;
	int[] sAnimLook = {1000, 1389, 290};
	
	// Main Menu variables
	//-------------------------------------------------------------------------
	ControlP5 controlP5;
	ControlWindow controlWindow2D;
	ControlWindow controlWindow3D;
	ControlWindow controlWindowAnim;
	ControlFont cFont;
	int gMainMenuButWd = 400;
	int gMainMenuButHt = 300;
	int gMainMenuX = 160;
	int gMainMenuY = 60;
	int gMainMenuGapX = 60;
	int gMainMenuGapY = 60;
	
	// 2D Screen variables
	//-------------------------------------------------------------------------
    int g2DControlWindowVisible = 0;
    RadioButton r2Dyear;
	RadioButton r2Dmonth;
	// Radio general specs
    int s2DradioActiveCol = color(255);
    int s2DradioLabelCol  = color(255);
    int s2DradioHt        = 15;
    int s2DradioWd        = 15;
	// Radio for Year selection
	String s2DradioYear   = "radio2DYear";
    int s2DradioYearX   = 20;
    int s2DradioYearY   = 20;
    // Radio for Month selection
    String s2DradioMonth   = "radio2DMonth";
    int s2DradioMonthX   = 150;
    int s2DradioMonthY   = 20;
    // Button general specs
    int s2DbuttonForeCol   = color(170);
    int s2DbuttonBackCol   = color(120);
    int s2DbuttonHt        = 30;
    int s2DbuttonWd        = 50;
    // Buttons for Next Prev and Top
    String s2DnextButton   = "Next2D";
    String s2DprevButton   = "Prev2D";
    String s2DtopButton    = "Top2D";
	
	// 3D Screen variables
  //-------------------------------------------------------------------------
	int gPlaneSpacing = 200;   
    int g3DControlWindowVisible = 0;
	RadioButton r3Dyear;
	RadioButton r3Dmonth;
	Slider s3Dalpha;
	// Radio general specs
    int s3DradioActiveCol = color(255);
    int s3DradioLabelCol  = color(255);
    int s3DradioHt        = 15;
    int s3DradioWd        = 15;
	// Radio for Year selection
	String s3DradioYear   = "radio3DYear";
    int s3DradioYearX   = 20;
    int s3DradioYearY   = 20;
    // Radio for Month selection
    String s3DradioMonth   = "radio3DMonth";
    int s3DradioMonthX   = 150;
    int s3DradioMonthY   = 20;
    // Button general specs
    int s3DbuttonForeCol   = color(170);
    int s3DbuttonBackCol   = color(120);
    int s3DbuttonHt        = 30;
    int s3DbuttonWd        = 50;
    // Buttons for Next Prev and Top
    String s3DnextButton   = "Next3D";
    String s3DprevButton   = "Prev3D";
    String s3DtopButton    = "Top3D";
    String s3DresetButton  = "Reset3D";
    // Slider for alpha
    String s3DalphaSlider  = "Alpha3D";
    int s3DSliderWd        = 160;
    int s3DSliderHt        = 16;
    // Active and non-active planes alpha
    int s3DactiveAlpha  = 150;
    int s3DdefaultAlpha = 30;
    int s3DcurAlpha     = s3DdefaultAlpha;
    int s3DmaxAlpha     = 70; // For slider
    int s3DminAlpha     = 10;  // For slider
    int s3DalphaStep    = 10;  // For slider
    int Alpha3D         = s3DdefaultAlpha;
    // Axis related
    boolean gShowAxis3D = false; 
	
	// Animation Screen variables
    //-------------------------------------------------------------------------
	int gMaxBuildingHeight = 500;
    int gTransactionsCountMax = 0;  
    int gAnimControlWindowVisible = 0;
    Integrator[][] smoothBuildings;
    // Button general specs
    int sAnimButtonForeCol   = color(170);
    int sAnimButtonBackCol   = color(120);
    int sAnimButtonHt        = 30;
    int sAnimButtonWd        = 50;
    // Buttons
    String sAnimNextButton   = "NextAnim";
    String sAnimPrevButton   = "PrevAnim";
    String sAnimTopButton    = "TopAnim";
    String sAnimPlayButton   = "Play";
    String sAnimPauseButton  = "Pause";
    String sAnimResetButton  = "ResetAnim";
    // Animation Timing
    float prevMillisAnim = 0;
    float animInterval   = 1000;
    float animAttract    = (float)0.5f;
    float animDamp       = (float)0.2f;
    int sAnimActiveAlpha = 255;
    // Axis related
    boolean gShowAxisAnim = false;
    boolean gShowTimeLine = true;
    boolean gDoAnimation = true;
    

    ////////////////////////////////////////////////////////////////////////////
	// Print the IDs for each month in database
	public void findAndPrintMonthIDs(String database, String user,
                              String password, String tableName)
	{
		// Connect to the database
	    String TheQuery ="";
	    msql = new MySQL( this, "tango.mat.ucsb.edu", database, user, password );

	    if(!msql.connect())
	    {
	      println("ERROR: Could not connect to database " + database);
	      return;
	    }
	    println("Connected to the database " + database);

	    // Get total number of transactions in the database
	    TheQuery = "SELECT count(*) FROM " + tableName;
	    msql.query(TheQuery);
	    msql.next();
	    int totalTrans = msql.getInt(1);
	    println( "Number of transactions: " + totalTrans );

	    for(int yearIdx = 2005; yearIdx <= 2010; yearIdx++)
	    {
	    		int monthStart = 1;
		    	if(yearIdx == 2005)
	    		{
		    		monthStart = 4; // 2005 only has data from April
	    		}
		    	for(int monthIdx = monthStart; monthIdx <= 12; monthIdx++)
		    	{
		    		TheQuery = "SELECT id FROM " + tableName +
	                       " WHERE (year(ckinDateTime)=" + yearIdx +
	                       ") and " + "(month(ckinDateTime)=" + monthIdx +
	                       ") limit 1";
		    		System.out.println(TheQuery);
		    		msql.query(TheQuery);
		    		msql.next();
			    int id = msql.getInt(1);
			    System.out.println(yearIdx + " : " + monthIdx + " : " + id);
		    	}
	    }
	}
	

    ////////////////////////////////////////////////////////////////////////////
	// Compute DewLet transactions count for each year and dataset.
	public void computeDeweyLetterMatrixFromDB(String database, String user,
	                                    String password, String tableName,
	                                    int yearIdx)
	{
	    // Connect to the database
	    String TheQuery ="";
	    if(yearIdx == 0)
	    {
	      msql = new MySQL( this, "tango.mat.ucsb.edu", database, user, password );
	    }
	    else
	    {
	      println("Continuing queries on the open database");
	    }

	    if (!msql.connect())
	    {
	      println("ERROR: Could not connect to database " + database);
	      return;
	    }
	    println("Connected to the database " + database);

	    // Get total number of transactions in the database
	    TheQuery = "SELECT count(*) FROM " + tableName;
	    msql.query(TheQuery);
	    msql.next();
	    int totalTrans = msql.getInt(1);
	    println( "Number of transactions: " + totalTrans );

	    int startVal = 0;
	    if(C_LOAD_PREV_COUNT)
	    {
	      startVal = C_PREV_COUNT;
	    }
	    if(C_DB_WITH_ID)
	    {
	      startVal += C_START_ID[yearIdx];
	    }
	    int endVal = C_END_ID[yearIdx];//totalTrans;//startVal+10;//
	    int loopPrintCnt = 0;
	    for(int startID = startVal; startID < endVal; startID+=C_LIMIT_PER_QUERY)
	    {
	      // Get title and dewey for ith row in DB
	      //      TheQuery = "SELECT title,deweyClass FROM " + tableName +
	      //                 " LIMIT " + C_LIMIT_PER_QUERY + " OFFSET " + i;
	      int endID = (endVal > (startID+C_LIMIT_PER_QUERY)) ? (startID+C_LIMIT_PER_QUERY) : endVal;
	      TheQuery = "SELECT title,deweyClass FROM " + tableName +
	                 " WHERE (id >= " + startID + " and " + "id < " + endID + ")";
	      msql.query(TheQuery);

		  for(int j = 0; j < (endID-startID); j++)
		  {
		      msql.next();
		      String title        = msql.getString(1);
		      String dewey        = msql.getString(2);
		      //println("Title: " + title);
		      //println("Dewey " + dewey);

		      for(int dispWordIdx = 0; dispWordIdx < displayKeyWords.length; dispWordIdx++)
			  {
					String deweyStr     = DLM[dispWordIdx][yearIdx].getPrimaryDeweyStr(dewey);
					Character firstChar;
					if(dispWordIdx != 0)
					{
					  firstChar = DLM[dispWordIdx][yearIdx].getFirstCharacter(title, ignoreKeyWords, includeKeyWords[dispWordIdx-1]);
					}
					else
					{
					  firstChar = DLM[dispWordIdx][yearIdx].getFirstCharacter(title, ignoreKeyWords);
					}
					DLM[dispWordIdx][yearIdx].updateCount(deweyStr, firstChar);



			  }
		  }

		  println(gMonths[yearIdx] + " : " + PApplet.nfc((loopPrintCnt++)*(endID-startID)));
		  if((startID-startVal)%C_SAVE_EVERY == 0)
			{
			  for(int dispWordIdx = 0; dispWordIdx < displayKeyWords.length; dispWordIdx++)
			  {
				  String fileName = "." + File.separator + "results" +  File.separator + "temp" + File.separator +
				             gTableName + "_" + DLM[dispWordIdx][yearIdx].numFirstCharacters +
				             "x" + DLM[dispWordIdx][yearIdx].numDeweyClasses + "_noexclude_" +
				             ((startID-startVal+C_PREV_COUNT)+1) + "titles" + gMonths[yearIdx] + ".txt";
				  DLM[dispWordIdx][yearIdx].saveDeweyMatrix(fileName);
			  }
			}

	    }

	    for(int dispWordIdx = 0; dispWordIdx < displayKeyWords.length; dispWordIdx++)
	    {
			String only = "only";
			if(dispWordIdx == 0)
			{
			 only = "";
			}

			String fileName = ".." + File.separator + "results" +  File.separator +
			              gTableName + "_" +
			              DLM[dispWordIdx][yearIdx].numFirstCharacters +
			              "x" + DLM[dispWordIdx][yearIdx].numDeweyClasses + "_" +
			              displayKeyWords[dispWordIdx] + only + "_" +
			              gMonths[yearIdx] + ".txt";
			//println("--------------------------------------------------------");
			//println("Dewey Matrix For " + gMonths[j]);
			//DLM[dispWordIdx][j].printDeweyMatrix();
			DLM[dispWordIdx][yearIdx].saveDeweyMatrix(fileName);
	    }
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// PApplet setup routine
	public void setup()
	{
	  DateUtils.printSystemDateTime();
	  String dataDir = ".." + File.separator + "data" + File.separator;
	  size(1180,780, OPENGL); // Size of the window on which to display
	  hint(DISABLE_OPENGL_2X_SMOOTH);
	  hint(ENABLE_OPENGL_4X_SMOOTH); // Checkout this code reference
	  g3 = (PGraphics3D)g;
	  noStroke();
	  smooth();
	  frameRate(60);
	  
	  // Setup 3D camera
	  int lookAtX = 1150;
	  int lookAtY =	1270;
	  int lookAtZ = 605;
	  int camDistance = (int)s3DCamDist;
	  
		  
	  camera = new PeasyCam(this, s3Dlook[0], s3Dlook[1], s3Dlook[2], camDistance);
	  camera.setMinimumDistance(100);
	  camera.setMaximumDistance(4000);
	  camera.setResetOnDoubleClick(false); // default true
	  //camera.setActive(false);
	  
	  // Load all required fonts
	  font10 = loadFont(dataDir + "HelveticaNeue-10.vlw");
	  font14 = loadFont(dataDir + "HelveticaNeue-14.vlw");
	  font18 = loadFont(dataDir + "HelveticaNeue-18.vlw");
	  font26 = loadFont(dataDir + "HelveticaNeue-26.vlw");
	  font48 = loadFont(dataDir + "HelveticaNeue-48.vlw");
	  
	  // Create ControlP5
	  controlP5 = new ControlP5(this);
	  controlP5.setAutoDraw(false);
	  
	  // Set custom font
	  PFont p = createFont("Verdana",14); // Choose Browser-safe fonts only 
	  controlP5.setControlFont(p,14);
	  controlP5.setColorLabel(color(255,128));
	  controlP5.setAutoDraw(false);
	  
	  createScreen2Dcontroller();
	  createScreen3Dcontroller();
	  createScreenAnimcontroller();

	  if(C_FIND_MONTH_IDS)
	  {
		  findAndPrintMonthIDs(gDatabase, gUser, gPassword, gTableName);
		  return;
	  }


	  DLS = new DeweyLetterStrings();

	  DLM    = new DeweyLetterMatrix[displayKeyWords.length][gMonths.length];
	  for(int i = 0; i < displayKeyWords.length; i++)
	  {
	    for(int j = 0; j < gMonths.length; j++)
	    {
	      DLM[i][j] = new DeweyLetterMatrix(this);
	    }
	  }

	  //Load count from a previously saved file
	  if(C_LOAD_PREV_COUNT)
	  {
	    for(int i = 0; i < displayKeyWords.length; i++)
	    {
	      String only = "only";
	      if(i == 0)
	      {
	        only = "";
	      }
	      for(int j=0; j < gMonths.length; j++)
	      {
	        String fileName = ".." + File.separator + "results" +  File.separator +
	                          gTableName + "_" + DLM[i][j].numFirstCharacters +
	                          "x" + DLM[i][j].numDeweyClasses + "_" +
	                          displayKeyWords[i] + only + "_" +
	                          gMonths[j] + ".txt";
	        DLM[i][j].loadDeweyMatrix(fileName);
	        //println("--------------------------------------------------------");
	        //println("Dewey Matrix For " + gMonths[j]);
	        //DLM[i][j].printDeweyMatrix();
	      }
	    }
	  }

	  //Compute count from the database
	  if(C_COMPUTE_COUNT)
	  {
	    for(int j=0; j < gMonths.length; j++)
	    {
	      computeDeweyLetterMatrixFromDB(gDatabase, gUser,
	    		                         gPassword, gTableName,j);
	    }
	  }

	  // Display statistics for the years
	  for(int i = 0; i < displayKeyWords.length; i++)
	  {
	    for(int j=0; j < gMonths.length; j++)
	    {
	      int curMax = DLM[i][j].getDeweyOnlyMatrixMax(); // Ignores non-dewey classes
	      println("--------------------------------------------------------");
	      println("Statistics for " + gMonths[j]);
	      println("Mean " + DLM[i][j].getDeweyMatrixMean());
	      println("SD "   + DLM[i][j].getDeweyMatrixSD());
	      println("Max "  + DLM[i][j].getDeweyMatrixMax());
	      println("Min "  + DLM[i][j].getDeweyMatrixMin());
	      println("Sum "  + DLM[i][j].getDeweyMatrixSum());
	      //gGridValMax = DLM[i][j].getDeweyMatrixMax(); // Commented to prevent color from getting screwed-up
	      if(curMax > gTransactionsCountMax)
	      {
	    	  gTransactionsCountMax = curMax;
	      }
	    }
	    println("--------------------------------------------------------");
	  }
	  println("--------------------------------------------------------");

	  // Setup the plot/grid
	  int gridRows = DLM[0][0].numFirstCharacters;
	  int gridCols = DLM[0][0].numDeweyClasses;
	  deweyLetterGrid = new Cell[displayKeyWords.length][gMonths.length][gridRows][gridCols];
	  colorMap = new ColorMaps();
	  
	  // Animation setup
	  smoothBuildings = new Integrator[gridRows][gridCols];
	  for (int j = 0; j < gridRows; j++)         
      {
		  for (int i = 0; i < gridCols; i++)
          {
        	  smoothBuildings[j][i] = new Integrator((float)0,animAttract, animDamp);
          }
      }

	  updateGrid();
	  updateRowBarGraph();
	  updateColBarGraph();
	  DateUtils.printSystemDateTime();
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Convert radio indices to gMonths[] indices
	public int radioToYearIdx(int radioYearIdx, int radioMonthIdx)
	{
		radioYearIdx  = (gYearNames.length - 1 - radioYearIdx);
		radioMonthIdx = (gMonthNames.length - 1 - radioMonthIdx);
		int yearIdx = radioYearIdx*12 + radioMonthIdx;
		// Sanity check
		if(yearIdx >= gMonths.length)
		{
			yearIdx = gMonths.length-1;
		}
		if(yearIdx < 0)
		{
			yearIdx = 0;
		}
		return yearIdx;
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Convert gMonths[] indices to radio indices
	public int yearIdxToRadioMonth(int yearIdx)
	{
		int radioMonthIdx = yearIdx%12;
		return (gMonthNames.length - 1 - radioMonthIdx);
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Convert gMonths[] indices to radio indices
	public int yearIdxToRadioYear(int yearIdx)
	{
		int radioYearIdx = yearIdx/12;
		return (gYearNames.length - 1 - radioYearIdx);
	}
		
    ////////////////////////////////////////////////////////////////////////////
	// Create the controller window for 2D screen
	public void createScreen2Dcontroller()
	{
		  // Create control window
		  controlWindow2D = controlP5.addControlWindow("s2Dcontroller",950,200,300,400);	      
	      controlP5.window("s2Dcontroller").hide(); g2DControlWindowVisible = 0;
		  controlWindow2D.hideCoordinates();
	      controlWindow2D.setBackground(color(40));
	      controlWindow2D.setTitle("Year and Month to Display");   
	     
		  // Add radio buttons for year
	      r2Dyear = createRadioButton(s2DradioYear, 
	    		                      s2DradioYearX, s2DradioYearY,
	    		                      gYearNames, 
	    		                      yearIdxToRadioYear(gYearIdx2Disp),
	    		                      controlWindow2D);
	      
	      // Add radio buttons for month
          r2Dmonth = createRadioButton(s2DradioMonth, 
        		                       s2DradioMonthX, s2DradioMonthY,
                                       gMonthNames, 
                                       yearIdxToRadioMonth(gYearIdx2Disp),
 	    		                       controlWindow2D);
          
          // Add buttons for navigation
          int butX = 50;
          int butGapX = 10;
          int butY = 300;
          
          createNavButton(s2DnextButton, (int)'>', "Next",
        		          butX, butY, controlWindow2D);
          butX+= butGapX+s2DbuttonWd;
          createNavButton(s2DprevButton, (int)'<', "Prev",
        		          butX, butY, controlWindow2D);
          butX+= butGapX+s2DbuttonWd;
          createNavButton(s2DtopButton, (int)'^',  " Top",
        		          butX, butY, controlWindow2D);
          
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Create the controller window for 3D screen
	public void createScreen3Dcontroller()
	{
		  // Create control window
		  controlWindow3D = controlP5.addControlWindow("s3Dcontroller",950,200,300,400);	      
	      controlP5.window("s3Dcontroller").hide(); g3DControlWindowVisible = 0;
		  controlWindow3D.hideCoordinates();
	      controlWindow3D.setBackground(color(40));
	      controlWindow3D.setTitle("3D Display Controller");   
	     
		  // Add radio buttons for year
	      r3Dyear = createRadioButton(s3DradioYear, 
	    		                      s3DradioYearX, s3DradioYearY,
	    		                      gYearNames, 
	    		                      yearIdxToRadioYear(gYearIdx2Disp),
	    		                      controlWindow3D);
	      
	      // Add radio buttons for month
          r3Dmonth = createRadioButton(s3DradioMonth, 
        		                       s3DradioMonthX, s3DradioMonthY,
                                       gMonthNames, 
                                       yearIdxToRadioMonth(gYearIdx2Disp),
 	    		                       controlWindow3D);
          // Add slider for changing alpha of non-active planes
          int slideX = 30;
          int slideY = 270;
          s3Dalpha = controlP5.addSlider(s3DalphaSlider,s3DminAlpha,s3DmaxAlpha,
        		                         slideX, slideY, 
        		                         s3DSliderWd, s3DSliderHt);         
          s3Dalpha.moveTo(controlWindow3D);
          s3Dalpha.setCaptionLabel("Alpha");
          s3Dalpha.setColorCaptionLabel(color(255));
          
          // Add buttons for navigation
          int butX = 30;
          int butGapX = 10;
          int butY = 340;
          
          createNavButton(s3DnextButton, (int)'>', "Next",
        		          butX, butY, controlWindow3D);
          butX+= butGapX+s3DbuttonWd;
          createNavButton(s3DprevButton, (int)'<', "Prev",
        		          butX, butY, controlWindow3D);
          butX+= butGapX+s3DbuttonWd;
          createNavButton(s3DtopButton, (int)'^',  " Top",
        		          butX, butY, controlWindow3D);
          butX+= butGapX+s3DbuttonWd;
          createNavButton(s3DresetButton, (int)'R',  "Reset",
        		          butX, butY, controlWindow3D);
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Create the controller window for Animation screen
	public void createScreenAnimcontroller()
	{
		  // Create control window
		  controlWindowAnim = controlP5.addControlWindow("sAnimController",820,680,400,100);	      
	      controlP5.window("sAnimController").hide(); gAnimControlWindowVisible = 0;
	      controlWindowAnim.hideCoordinates();
	      controlWindowAnim.setBackground(color(40));
	      controlWindowAnim.setTitle("Animation Controller");
          
          // Add buttons for navigation
          int butX = 20;
          int butGapX = 10;
          int butY = 30;
          
          createNavButton(sAnimPrevButton, (int)'<', "Prev",
        		          butX, butY, controlWindowAnim);
          butX+= butGapX+sAnimButtonWd;
          createNavButton(sAnimPlayButton, (int)'^',  "Play",
        		          butX, butY, controlWindowAnim);
          butX+= butGapX+sAnimButtonWd;
          createNavButton(sAnimPauseButton, (int)'^',  "Stop",
        		          butX, butY, controlWindowAnim);
          butX+= butGapX+sAnimButtonWd;
          createNavButton(sAnimNextButton, (int)'>', "Next",
		          butX, butY, controlWindowAnim);
          butX+= butGapX+sAnimButtonWd;
          createNavButton(sAnimResetButton, (int)'R',  "Reset",
        		          butX, butY, controlWindowAnim);
          butX+= butGapX+sAnimButtonWd;
          createNavButton(sAnimTopButton, (int)'P',  " Top",
        		          butX, butY, controlWindowAnim);
          
          
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Given the updated gYearIdx2Disp, update the active radio items
	public void updateRadioButtonsScreen2D()
	{
		r2Dyear.activate(yearIdxToRadioYear(gYearIdx2Disp));
		r2Dmonth.activate(yearIdxToRadioMonth(gYearIdx2Disp));
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Given the updated gYearIdx2Disp, update the active radio items
	public void updateRadioButtonsScreen3D()
	{
		r3Dyear.activate(yearIdxToRadioYear(gYearIdx2Disp));
		r3Dmonth.activate(yearIdxToRadioMonth(gYearIdx2Disp));
		s3Dalpha.setValue(s3DcurAlpha);
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Add item to radio button
	public void createNavButton(String butName, int butVal, String butCaption, 
			             int butX, int butY, 
                         ControlWindow curControlWindow)
	{
		controlP5.Button b;
		b = controlP5.addButton(butName, butVal, butX, butY,
                                s2DbuttonWd, s2DbuttonHt);
		b.moveTo(curControlWindow);
		b.setCaptionLabel(butCaption);
		b.setColorBackground(s2DbuttonBackCol);
		b.setColorForeground(s2DbuttonForeCol);
		b.setColorCaptionLabel(color(255));
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Add item to radio button
	public void addToRadioButton(RadioButton theRadioButton, String theName, 
			              int theValue, String theCaption) 
	{
		  Toggle t = theRadioButton.addItem(theName,theValue);
		  t.setCaptionLabel(theCaption);
		  t.captionLabel().style().movePadding(2,0,-1,2);
		  t.captionLabel().style().moveMargin(-2,0,0,-3);
		  t.captionLabel().style().backgroundWidth = 46;
	}
	
	////////////////////////////////////////////////////////////////////////////
	// Function creates Radio Button Arrays for use in screen controllers
	public RadioButton createRadioButton(String radioName, int radioX, int radioY,
			                      String[] itemName, int activeItem,
			                      ControlWindow curControlWindow)
	{
		RadioButton curRadioButton = 
			              controlP5.addRadioButton(radioName, radioX, radioY);
		curRadioButton.moveTo(curControlWindow);
		curRadioButton.setColorBackground(color(120));
		curRadioButton.setColorActive(s2DradioActiveCol);
		curRadioButton.setColorLabel(s2DradioLabelCol);
		curRadioButton.setItemsPerRow(1);
		curRadioButton.setSpacingColumn(120);
		curRadioButton.setItemHeight(s2DradioHt);
		curRadioButton.setItemWidth(s2DradioWd);
	    for(int i = 0; i < itemName.length; i++)
	    {
	  	  addToRadioButton(curRadioButton, radioName+itemName[itemName.length-1-i], 
	  			           i, " "+itemName[itemName.length-1-i]);
	    }
	    curRadioButton.activate(activeItem);
	    
	    return curRadioButton;
		
	}

	
    ////////////////////////////////////////////////////////////////////////////
	// Get the color indices to be used for different bins
	public int getColorMapIdx(int maxVal, int minVal, int numBins, int curVal)
	{
	  double binSize = (maxVal - minVal)/numBins;
	  for(int i = 0; i < numBins; i++)
	  {
	    if((curVal >= i*binSize) && (curVal < (i+1)*binSize))
	    {
	      return i;
	    }
	  }
	  return (numBins-1);
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Update the DewLet grid. Call after changing dataset, year or month
	public void updateGrid()
	{
	  // Plotting the Dewey Letter Matrix Grid
	  // Create the grid
	  int gridRows = DLM[0][0].numFirstCharacters;
	  int gridCols = DLM[0][0].numDeweyClasses;

	  // Setup the Grid
	  int tempStroke = 128;
	  int tempAlpha = cColorMapAlpha;
	  for(int dkey = 0; dkey < displayKeyWords.length; dkey++)
	  {
	      for(int yr = 0; yr < gMonths.length; yr++)
	      {
	        for (int i = 0; i < gridCols; i++)
	        {
	            for (int j = 0; j < gridRows; j++)
	            {
	            // Initialize each object
	            int xi = x0 + (i*cCellWd) + floor(i/cCellWdGrp)*cCellWdGap;
	            int yj = y0 + (j*(cCellHt+cCellHtGap));
	            int zj = z0 + ((yr%12)*gPlaneSpacing);
	            int cIdx = getColorMapIdx(gGridValMax, gGridValMin, gNumBins, DLM[dkey][yr].count[j][i]);
	            float cellDepth = map(DLM[dkey][yr].count[j][i], 0, gTransactionsCountMax, 0, gMaxBuildingHeight);
	            deweyLetterGrid[dkey][yr][j][i] = 
	            				new Cell(xi,yj,zj,
	            						 cCellWd,cCellHt,cellDepth,
	            						 tempStroke,
                                         colorMap.getColor(cIdx,0),
                                         colorMap.getColor(cIdx,1),
                                         colorMap.getColor(cIdx,2),
                                         tempAlpha,this);
	            }
	        }
	      }
	  }
	  gridMinX = deweyLetterGrid[0][0][0][0].x;
	  gridMinY = deweyLetterGrid[0][0][0][0].y;
	  gridMaxX = deweyLetterGrid[0][0][gridRows-1][gridCols-1].x +
	             deweyLetterGrid[0][0][gridRows-1][gridCols-1].w;
	  gridMaxY = deweyLetterGrid[0][0][gridRows-1][gridCols-1].y +
	             deweyLetterGrid[0][0][gridRows-1][gridCols-1].h;
	}

    ////////////////////////////////////////////////////////////////////////////
	// Update the Row graphs. Call after changing dataset, year or month
	public void updateRowBarGraph()
	{
	  // Plotting the Dewey Letter Matrix Grid
	  // Create the grid
	  int gridCols = DLM[0][0].numDeweyClasses;

	  // Setup the row bar graph
	  colAvg = new int[displayKeyWords.length][gMonths.length][DLM[0][0].numDeweyClasses];
	  colMax = new int[displayKeyWords.length][gMonths.length][DLM[0][0].numDeweyClasses];
	  colMin = new int[displayKeyWords.length][gMonths.length][DLM[0][0].numDeweyClasses];
	  colSum = new int[displayKeyWords.length][gMonths.length][DLM[0][0].numDeweyClasses];
	  for(int dkey = 0; dkey < displayKeyWords.length; dkey++)
	  {
	      for(int yr = 0; yr < gMonths.length; yr++)
	      {
	        for(int i = 0; i < gridCols; i++)
	        {
	          StatCalc colStat = DLM[dkey][yr].computeDeweyMatrixStatCol(i);
	          colAvg[dkey][yr][i] = round((float)colStat.getMean());
	          colMax[dkey][yr][i] = (int)colStat.getMax();
	          colMin[dkey][yr][i] = (int)colStat.getMin();
	          colSum[dkey][yr][i] = (int)colStat.getSum();
	        }
	      }
	  }
	}

    ////////////////////////////////////////////////////////////////////////////
	// Update the Col graphs. Call after changing dataset, year or month
	public void updateColBarGraph()
	{
	  // Plotting the Dewey Letter Matrix Grid
	  // Create the grid
	  int gridRows = DLM[0][0].numFirstCharacters;

	  // Setup the col bar graph
	  rowAvg = new int[displayKeyWords.length][gMonths.length][DLM[0][0].numFirstCharacters];
	  rowMax = new int[displayKeyWords.length][gMonths.length][DLM[0][0].numFirstCharacters];
	  rowMin = new int[displayKeyWords.length][gMonths.length][DLM[0][0].numFirstCharacters];
	  rowSum = new int[displayKeyWords.length][gMonths.length][DLM[0][0].numFirstCharacters];
	  for(int dkey = 0; dkey < displayKeyWords.length; dkey++)
	  {
	      for(int yr = 0; yr < gMonths.length; yr++)
	      {
	        for(int j = 0; j < gridRows; j++)
	        {
	          StatCalc rowStat = DLM[dkey][yr].computeDeweyMatrixStatRow(j);
	          rowAvg[dkey][yr][j] = round((float)rowStat.getMean());
	          rowMax[dkey][yr][j] = (int)rowStat.getMax();
	          rowMin[dkey][yr][j] = (int)rowStat.getMin();
	          rowSum[dkey][yr][j] = (int)rowStat.getSum();
	        }
	      }
	   }
	}

    ////////////////////////////////////////////////////////////////////////////
	// PApplet draw routine
	public void draw()
	{
	  rotations = camera.getRotations();
	  if(C_DRAW_GRAPH)
	  {
		  hint(ENABLE_DEPTH_TEST);
		  switch(gScreenType)
		  {
		  case 0: // Main Menu
			  camera.beginHUD();
			  drawMainMenu();
			  camera.endHUD();
			  break;
		  case 1: // 2D Screen
			  camera.beginHUD();
			  draw2DScreen();
			  camera.endHUD();
			  break;
		  case 2: // 3D Screen
			  draw3DScreen();
			  break;
		  case 3: // Animation
			  drawAnimScreen();
			  break;
		  default:
			  break;	  
		  }
		  hint(DISABLE_DEPTH_TEST);
	  }
	  currCameraMatrix = new PMatrix3D(g3.camera);
	  camera();
	  controlP5.draw();
	  g3.camera = currCameraMatrix;
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Draw the 2D screen
	public void drawMainMenu()
	{
		int textX = gMainMenuX;
		int textY = gMainMenuY;
		
		background(255);
		fill(128);
		textFont(font48, 48);
		fill(128);
		textAlign(CENTER, CENTER);
		text("Dewey Classes, First Letters and Transactions", 
			 textX, textY, gMainMenuButWd, gMainMenuButHt);
		noFill();
		stroke(220);
		rect(textX, textY, gMainMenuButWd, gMainMenuButHt);
		textX = xPosForMainMenuBox(1);
		textY = yPosForMainMenuBox(1);
		
		textAlign(CENTER, CENTER);
		if(isMouseInThisBox(textX, textY, gMainMenuButWd, gMainMenuButHt))
		{
			fill(150);
			stroke(0, 100, 255);
		}
		else
		{
			fill(180);
			stroke(220);
		}
		text("Transactions in 2-Dimensions", 
		     textX, textY, gMainMenuButWd, gMainMenuButHt);		
		noFill();
		rect(textX, textY, gMainMenuButWd, gMainMenuButHt);
		textX = xPosForMainMenuBox(2);
		textY = yPosForMainMenuBox(2);
		
		textAlign(CENTER, CENTER);
		if(isMouseInThisBox(textX, textY, gMainMenuButWd, gMainMenuButHt))
		{
			fill(150);
			stroke(0, 100, 255);
		}
		else
		{
			fill(180);
			stroke(220);
		}
		text("Transactions in 3-Dimensions", 
			 textX, textY, gMainMenuButWd, gMainMenuButHt);
		noFill();		
		rect(textX, textY, gMainMenuButWd, gMainMenuButHt);
		textX = xPosForMainMenuBox(3);
		textY = yPosForMainMenuBox(3);
		
		textAlign(CENTER, CENTER);
		if(isMouseInThisBox(textX, textY, gMainMenuButWd, gMainMenuButHt))
		{
			fill(150);
			stroke(0, 100, 255);
		}
		else
		{
			fill(180);
			stroke(220);
		}
		text("Transactions Animation", 
			 textX, textY, gMainMenuButWd, gMainMenuButHt);
		noFill();
		rect(textX, textY, gMainMenuButWd, gMainMenuButHt);

		textFont(font18, 18);
		fill(128);
		textAlign(LEFT, CENTER);
		textX = gMainMenuX;
		textY = height - 20;
		text("Source: Seattle Public Library", 
				 textX, textY);
		
		textFont(font18, 18);
		fill(128);
		textAlign(LEFT, CENTER);
		String dispStr = "Design: Sandeep Bhat, MAT259, Winter 2011";
		textX = gMainMenuX+2*gMainMenuButWd+gMainMenuGapX - (int)(textWidth(dispStr));
		text(dispStr, textX, textY);
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Returns the button on which the mouse is currently
	public boolean isMouseInThisBox(int x, int y, int w, int h)
	{
		return((mouseX > x && mouseX < x+w) && (mouseY > y && mouseY < y+h));
		       
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Returns the x position for give main menu button 
	public int xPosForMainMenuBox(int boxIdx)
	{
		int xPos = 0;
		switch(boxIdx)
		{
		case 1:
		case 3:	
			xPos = gMainMenuX+gMainMenuButWd+gMainMenuGapX;
			break;
		case 2:
			xPos = gMainMenuX;
			break;
		}
		
		return xPos;
		       
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Returns the y position for give main menu button 
	public int yPosForMainMenuBox(int boxIdx)
	{
		int yPos = 0;
		switch(boxIdx)
		{
		case 2:
		case 3:	
			yPos = gMainMenuY+gMainMenuButHt+gMainMenuGapY;
			break;
		case 1:
			yPos = gMainMenuY;
			break;
		}
		
		return yPos;
		       
	}
	
	
    ////////////////////////////////////////////////////////////////////////////
	// Draw the 2D screen
	public void draw2DScreen()
	{
		int gridRows = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numFirstCharacters;
		int gridCols = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numDeweyClasses;
		boolean displayDynamic = false;
	    background(255);
    	for (int j = 0; j < gridRows; j++)
  	    {
    	  for (int i = 0; i < gridCols; i++)
	      {
	        boolean mouseOnCell;
	        mouseOnCell = 
	        	deweyLetterGrid[gDispKeyWordsIdx][gYearIdx2Disp][j][i].
	        	                                  display2D(gridMinX, gridMinY,
	                                                        gridMaxX, gridMaxY);
	        if(mouseOnCell == true && displayDynamic == false)
	        {
	           if(deweyLetterGrid[gDispKeyWordsIdx][gYearIdx2Disp][j][i].isSelected(gridMinX, gridMinY, gridMaxX, gridMaxY))
	           {
	             displayDynamicText(j, i);
	             displayDynamic = true;
	           }
	        }
	      }
	    }

	    drawRowBarGraph();
	    drawColBarGraph();
	    drawStaticText();
	    drawColorBar();
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Draw the 3D screen
	public void draw3DScreen()
	{
		int gridRows = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numFirstCharacters;
		int gridCols = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numDeweyClasses;
		background(255);
		int curYear = (gYearIdx2Disp/12);
		for(int yearIdx = curYear*12; yearIdx < ((curYear*12)+12); yearIdx++)
		{
			int alpha = (yearIdx == gYearIdx2Disp) ? s3DactiveAlpha : s3DcurAlpha;
			for(int j = 0; j < gridRows; j++)
			{
				for(int i = 0; i < gridCols; i++)
				{
					deweyLetterGrid[gDispKeyWordsIdx][yearIdx][j][i].display3D(alpha);
				}
			}
		}
		
		if(gShowAxis3D)
		{
			// Draw the axis
			int maxX = x0 + (gridCols*cCellWd) + floor(gridCols/cCellWdGrp)*cCellWdGap;
	        int maxY = y0 + (gridRows*(cCellHt+cCellHtGap));
	        int maxZ = z0 + (((gMonths.length-1)%12)*gPlaneSpacing);
	        int extraLen = 100;
	        
	        stroke(0);
	        line(0, 0, 0, maxX+extraLen, 0,             0);             // X-axis
	        line(0, 0, 0, 0,             maxY+extraLen, 0);             // Y-axis
	        line(0, 0, 0, 0,             0,             maxZ+extraLen); // Z-axis
	        
	        textFont(font18, 18);
	  	    // Draw X text
	        int textX = x0;
			int textY = -20;
			int textZ = 0;
			for(int i = 0; i < DLS.DeweyText.length-1; i++)
			{
			  fill(128);   
			  textX = x0 + (i*((cCellWd * cCellWdGrp)+cCellWdGap)) 
			             + floor(cCellWdGrp*cCellWd/2);
			  textAlign(CENTER, CENTER);
			  textFaceCamera(DLS.getDeweyText(i), textX, textY, textZ);
			  fill(255);
			  line(textX, -8, 0, textX, 8, 0); // tick marks
			  
			}
			
			// Draw Y text
			textX = -16;
			for(int i = 0; i < DLS.LettersText.length; i++)
			{
			  int fillVal = (i%2==0) ? 128 : 180;
			  fill(fillVal);
			  textY = y0 + (i*(cCellHt+cCellHtGap)) + 8;
			  textAlign(CENTER, CENTER);
			  textFaceCamera(DLS.getLettersText(i), textX, textY, textZ);
			  fill(255);
			  if(i%5 == 0)
			  {
				  line(-8, textY, 0, 8, textY, 0); // tick marks
			  }
			}
			
			// Draw Z ticks
			textZ = 0;
			textY = y0 + 10;
			textX = maxX + extraLen;
			curYear = (gYearIdx2Disp/12);
			for(int i = 0; i < 12; i++)
			{
				fill(255);
				line(0, 8, textZ, 0, -8, textZ); // tick marks
				fill(128);   
			    textAlign(CENTER, CENTER);
			    textFaceCamera(gMonths[curYear*12+i], textX, textY, textZ);
			    textZ += gPlaneSpacing;  
			}
		}
	   
        
        // Heads-up display text
		camera.beginHUD();
		
		drawHUDText3D();			
		drawHUDSignature("Transactions 3D: '");
		drawColorBar();
		
		camera.endHUD();
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Draw signature at the bottom of screen
	public void drawHUDText3D()
	{
		int textX = x0;
		int textY = y0;
		int textGap = 4;
		textFont(font26, 26);
		fill(128);
		textAlign(LEFT, CENTER);
		
		text(gMonths[gYearIdx2Disp], textX, textY);
		textY += (textAscent()+textDescent()+textGap);
		
		textFont(font18, 18);
		text("Max: " + nfc(DLM[gDispKeyWordsIdx][gYearIdx2Disp].getDeweyMatrixMax()), 
		     textX, textY);
		textY += (textAscent()+textDescent()+textGap);
		
		text("Min: " + nfc(DLM[gDispKeyWordsIdx][gYearIdx2Disp].getDeweyMatrixMin()), 
			     textX, textY);
		textY += (textAscent()+textDescent()+textGap);
		
		text("Mean: " + nfc((int)DLM[gDispKeyWordsIdx][gYearIdx2Disp].getDeweyMatrixMean()), 
			     textX, textY);
		textY += (textAscent()+textDescent()+textGap);
		
		text("SD: " + nfc((int)DLM[gDispKeyWordsIdx][gYearIdx2Disp].getDeweyMatrixSD()), 
			     textX, textY);
		textY += (textAscent()+textDescent()+textGap);
		
		text("Total: " + nfc(DLM[gDispKeyWordsIdx][gYearIdx2Disp].getDeweyMatrixSum()), 
			     textX, textY);
		//textY += (textAscent()+textDescent()+textGap);
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Draw signature at the bottom of screen
	public void drawHUDTextAnim()
	{
		int textX = x0;
		int textY = y0;
		int textGap = 4;
		textFont(font26, 26);
		fill(128);
		textAlign(LEFT, CENTER);
		
		text(gMonths[gYearIdx2Disp], textX, textY);
		textY += (textAscent()+textDescent()+textGap);
		
		textFont(font18, 18);
		text("Max: " + nfc(DLM[gDispKeyWordsIdx][gYearIdx2Disp].getDeweyOnlyMatrixMax()), 
		     textX, textY);
		textY += (textAscent()+textDescent()+textGap);
		
		text("Min: " + nfc(DLM[gDispKeyWordsIdx][gYearIdx2Disp].getDeweyOnlyMatrixMin()), 
			     textX, textY);
		textY += (textAscent()+textDescent()+textGap);
		
		text("Mean: " + nfc((int)DLM[gDispKeyWordsIdx][gYearIdx2Disp].getDeweyOnlyMatrixMean()), 
			     textX, textY);
		textY += (textAscent()+textDescent()+textGap);
		
		text("SD: " + nfc((int)DLM[gDispKeyWordsIdx][gYearIdx2Disp].getDeweyOnlyMatrixSD()), 
			     textX, textY);
		textY += (textAscent()+textDescent()+textGap);
		
		text("Total: " + nfc(DLM[gDispKeyWordsIdx][gYearIdx2Disp].getDeweyOnlyMatrixSum()), 
			     textX, textY);
		//textY += (textAscent()+textDescent()+textGap);
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Draw signature at the bottom of screen
	public void drawHUDSignature(String screenTitle)
	{
		int textX = x0;
		int textY = y0;
		int textGap = 4;
		fill(128);
		textAlign(LEFT, CENTER);	
		
		textFont(font14, 14);
		String dispStr = "Design: Sandeep Bhat, MAT259, Winter 2011";
		textY = (height - (int)(textAscent()+textDescent()+textGap)-1);
		
		text(dispStr, textX, textY);
		textY -= 1.5f*(textAscent()+textDescent()+textGap);
				
		textFont(font26, 26);
		text(screenTitle + displayKeyWords[gDispKeyWordsIdx] + "' titles", textX, textY);
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Draw the Animation screen
	public void drawAnimScreen()
	{
		int gridRows = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numFirstCharacters;
		int gridCols = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numDeweyClasses-1;
		float curMillis = millis();
		
		background(255);
		if(gDoAnimation)
		{
			if(curMillis > prevMillisAnim+animInterval)
			{
				for(int j = 0; j < gridRows; j++)
				{
					for(int i = 0; i < gridCols; i++)
					{
	                    smoothBuildings[j][i].target(deweyLetterGrid[gDispKeyWordsIdx][gYearIdx2Disp][j][i].d);
					}
				}
				gYearIdx2Disp--;
				if(gYearIdx2Disp < 0)
				{
				  gYearIdx2Disp = gMonths.length-1;
				}
				prevMillisAnim = millis();
			}
		}
		
		for(int j = 0; j < gridRows; j++)
		{
			int alpha = sAnimActiveAlpha;
			for(int i = 0; i < gridCols; i++)
			{
				if(gDoAnimation)
				{
					smoothBuildings[j][i].update();
					deweyLetterGrid[gDispKeyWordsIdx][gYearIdx2Disp][j][i].
					           displayAnim(alpha, (int)smoothBuildings[j][i].value);
				}
				else
				{
					deweyLetterGrid[gDispKeyWordsIdx][gYearIdx2Disp][j][i].
						displayAnim(alpha, (int)deweyLetterGrid[gDispKeyWordsIdx][gYearIdx2Disp][j][i].d);
					// Set target and integrator value to the same value
					smoothBuildings[j][i].target(deweyLetterGrid[gDispKeyWordsIdx][gYearIdx2Disp][j][i].d);
					smoothBuildings[j][i].set(deweyLetterGrid[gDispKeyWordsIdx][gYearIdx2Disp][j][i].d);
				}
			}
		}
		
		if(gShowAxisAnim)
		{
			// Draw the axis
			int maxX = x0 + (gridCols*cCellWd) + floor(gridCols/cCellWdGrp)*cCellWdGap;
	        int maxY = y0 + (gridRows*(cCellHt+cCellHtGap));
	        int extraLen = 100;
	        
	        stroke(0);
	        line(0, 0, 0, maxX+extraLen, 0,             0);             // X-axis
	        line(0, 0, 0, 0,             maxY+extraLen, 0);             // Y-axis
	        line(0, 0, 0, 0,             0,             gMaxBuildingHeight); // Z-axis
	        
	        textFont(font14, 14);
	  	    // Draw X text
	        int textX = x0;
			int textY = -20;
			int textZ = 0;
			for(int i = 0; i < DLS.DeweyText.length-1; i++)
			{
			  fill(128);   
			  textX = x0 + (i*((cCellWd * cCellWdGrp)+cCellWdGap)) 
			             + floor(cCellWdGrp*cCellWd/2);
			  textAlign(CENTER, CENTER);
			  textFaceCamera(DLS.getDeweyText(i), textX, textY, textZ);
			  fill(255);
			  line(textX, -8, 0, textX, 8, 0); // tick marks
			  
			}
			
			// Draw Y text
			textX = -16;
			for(int i = 0; i < DLS.LettersText.length; i++)
			{
			  int fillVal = (i%2==0) ? 128 : 180;
			  fill(fillVal);
			  textY = y0 + (i*(cCellHt+cCellHtGap)) + 8;
			  textAlign(CENTER, CENTER);
			  textFaceCamera(DLS.getLettersText(i), textX, textY, textZ);
			  fill(255);
			  if(i%5 == 0)
			  {
				  line(-8, textY, 0, 8, textY, 0); // tick marks
			  }
			}
			
			// Draw Z ticks
			textZ = 0;
			textY = 2;
			textX = -10;
			for(int i = 0; i < gMaxBuildingHeight; i+=(gMaxBuildingHeight/5))
			{
				fill(255);
				line(0, 8, i, 0, -8, i); // tick marks
				fill(128);   
			    textAlign(CENTER, CENTER);
			    int numDisp = ((gTransactionsCountMax/gMaxBuildingHeight)*i);
			    textFaceCamera(nfc(numDisp), textX, textY, i);
			}
		}
		
		// Heads-up display text
		camera.beginHUD();
		
		if(gShowTimeLine)
		{
			drawAnimTimeline(gYearIdx2Disp);
		}
		else
		{
			drawHUDTextAnim();
		}
		
		drawHUDSignature("Transactionapolis: '");
	    drawColorBar();
		
		camera.endHUD();
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Draw the animation time line for given time point
	public void drawAnimTimeline(int curYearIdx)
	{
		int borderX = 100;
		int borderY = y0+10;
		int tLineMaxWd = width - 2*borderX;
		int tLineHt = 20;
		
		
		
		textFont(font14, 14);
		textAlign(LEFT, CENTER);
		int textW = (int)map(curYearIdx, 0, gMonths.length-1, 0, tLineMaxWd);
			//tLineMaxWd - (curYearIdx*tLineMaxWd/gMonths.length);
		int textX = borderX;
		int textY = borderY + (int)(textAscent()+textDescent()+1) + tLineHt;
		
		noFill();
		stroke(0);
		rect(textX, textY, tLineMaxWd, tLineHt);
		
		//textX = borderX + tLineMaxWd - textW;
		
		fill(128);
		rect(textX, textY, tLineMaxWd - textW, tLineHt);
		
		for(int i = 0; i < gMonths.length; i++)
		{
			// Ticks
			textW = (int)map(i, 0, gMonths.length-1, 0, tLineMaxWd);
			textX = borderX + tLineMaxWd - textW;
			stroke(50);
			line(textX,textY,textX,textY+tLineHt);
			
			// Month Text
			textFont(font14, 14);
			int fillVal = ((i != curYearIdx) ? 200 : 50);
			fill(fillVal);
			text(gMonthNamesShort[i%12], 
			     textX-(textWidth(gMonthNamesShort[i%12])/2), 
			     textY-(int)(textAscent()+textDescent()+1));
			
			// Year Text
			if(i%12 == 11) // Jan only
			{
				textFont(font18, 18);
				fillVal = ((i/12 != curYearIdx/12) ? 200 : 50);
				fill(fillVal);
				text(gYearNames[i/12], 
					 textX-(textWidth(gMonthNamesShort[i%12])/2), 
				     textY-(int)2*(textAscent()+textDescent()+1));
			}
		}
		textY -= (int)(textAscent()+textDescent()+1);
	}
	

    ////////////////////////////////////////////////////////////////////////////
	// PApplet mousePressed routine
	public void mousePressed()
	{
	  System.out.println("Mouse pressed "+gScreenType+" "+camera.isActive());
	  if(gScreenType == 0)
	  {
		  for(int boxIdx = 1; boxIdx <= 3; boxIdx++)
		  {
			  if(isMouseInThisBox(xPosForMainMenuBox(boxIdx), 
					              yPosForMainMenuBox(boxIdx), 
					              gMainMenuButWd, gMainMenuButHt))
			  {
				  gScreenType = boxIdx;
				  break;
			  }
		   }
		  g2DControlWindowVisible = 0;
		  g3DControlWindowVisible = 0;
		  gAnimControlWindowVisible = 0;
		  switchToCurrentControlWindow();
	  }
	  printCameraPosition();
	}

    ////////////////////////////////////////////////////////////////////////////
	// PApplet keyPressed routine
	public void keyPressed()
	{
	  /**
	  KeyBoard Shortcuts
	  <ul>
	  <li>Press <b>]</b> to select Next Month </li>
	  <li>Press <b>[</b> to select Prev Month </li>
	  <li>Press <b>C</b> to change Color Map </li>
	  <li>Press <b>D</b> to change Datasets </li>
	  </ul>
	  */
	  char keyVal = key;
	  //println("Detected keypress " + Character.toString(key));
	  if (key == CODED)
	  {
	    if (keyCode == LEFT)
	    {
	      keyVal = '[';
	    }
	    else if (keyCode == RIGHT)
	    {
	      keyVal = ']';
	    }
	  }
	  switch(keyVal)
	  {
	    case '1':
		  gScreenType = 1;
		  g2DControlWindowVisible = 1;
		  switchToCurrentControlWindow();
		  break;
	    case '2':
	      gScreenType = 2;
		  g3DControlWindowVisible = 1;
		  switchToCurrentControlWindow();
	      break;
	    case '3':
	      gScreenType = 3;
		  gAnimControlWindowVisible = 1;
		  switchToCurrentControlWindow();
	      break;  
	    case ']':
	      gYearIdx2Disp--;
	      if(gYearIdx2Disp < 0)
	      {
	        gYearIdx2Disp = gMonths.length-1;
	      }
		  updateRadioButtonsScreen2D();
		  updateRadioButtonsScreen3D();
	      break;
	    case '[':
	      gYearIdx2Disp++;
	      if(gYearIdx2Disp >= gMonths.length)
	      {
	        gYearIdx2Disp = 0;
	      }
		  updateRadioButtonsScreen2D();
		  updateRadioButtonsScreen3D();
	      break;
	    case 'C':
	    case 'c':
	      colorMap.changeColorMap();
	      updateGrid();
	      break;
	    case 'D':
	    case 'd':
	      gDispKeyWordsIdx++;
	      if(gDispKeyWordsIdx >= displayKeyWords.length)
	      {
	        gDispKeyWordsIdx = 0;
	      }
	      updateGrid();
	      updateRowBarGraph();
	      updateColBarGraph();
	      break;
	    case 'A':
	    case 'a':
	    	if(gScreenType == 2)
	    	{
	    		gShowAxis3D = !(gShowAxis3D);
	    	}
	    	else if(gScreenType == 3)
	    	{
	    		gShowAxisAnim = !(gShowAxisAnim);
	    	}
	        break;
	    case 'T':
	    case 't':
	    	if(gScreenType == 3)
	    	{
	    		gShowTimeLine = !(gShowTimeLine);
	    	}
	    	break;
	    case '.':
	    	switchToCurrentControlWindow();
	    	break;
	      
	  }
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Switch ON/OFF the controller window for current screen
	public void switchToCurrentControlWindow()
	{
		switch(gScreenType)
		{
		case 0:
			g2DControlWindowVisible = 0;
			g3DControlWindowVisible = 0;
			gAnimControlWindowVisible = 0;
			controlP5.window("s2Dcontroller").hide();
			controlP5.window("s3Dcontroller").hide();
			controlP5.window("sAnimController").hide();
			resetCameraAndPosition(s3DCamRot[0], s3DCamRot[1], s3DCamRot[2], 
					               s3Dlook[0], s3Dlook[1], s3Dlook[2],
					               s3DCamDist, s3DresetTime);			
			camera.setActive(false);
			break;
		case 1:
			g2DControlWindowVisible = 1 - g2DControlWindowVisible;
			if(g2DControlWindowVisible == 1)
			{
				controlP5.window("s2Dcontroller").show();
			}
			else
			{
				controlP5.window("s2Dcontroller").hide();	
			}
			g3DControlWindowVisible = 0;
			gAnimControlWindowVisible = 0;
			controlP5.window("s3Dcontroller").hide();
			controlP5.window("sAnimController").hide();
			resetCameraAndPosition(s3DCamRot[0], s3DCamRot[1], s3DCamRot[2], 
		                           s3Dlook[0], s3Dlook[1], s3Dlook[2],
		                           s3DCamDist, s3DresetTime);
			camera.setActive(false);
			break;
		case 2:
			g3DControlWindowVisible = 1 - g3DControlWindowVisible;
			if(g3DControlWindowVisible == 1)
			{
				controlP5.window("s3Dcontroller").show();
			}
			else
			{
				controlP5.window("s3Dcontroller").hide();	
			}
			g2DControlWindowVisible = 0;
			gAnimControlWindowVisible = 0;
			controlP5.window("s2Dcontroller").hide();
			controlP5.window("sAnimController").hide();
			resetCameraAndPosition(s3DCamRot[0], s3DCamRot[1], s3DCamRot[2], 
                                   s3Dlook[0], s3Dlook[1], s3Dlook[2],
                                   s3DCamDist, s3DresetTime);
            camera.setActive(true);
			break;
		case 3:
			gAnimControlWindowVisible = 1 - gAnimControlWindowVisible;
			if(gAnimControlWindowVisible == 1)
			{
				controlP5.window("sAnimController").show();
			}
			else
			{
				controlP5.window("sAnimController").hide();	
			}
			g2DControlWindowVisible = 0;
			g3DControlWindowVisible = 0;
			controlP5.window("s2Dcontroller").hide();
			controlP5.window("s3Dcontroller").hide();
			resetCameraAndPosition(sAnimCamRot[0], sAnimCamRot[1], sAnimCamRot[2], 
					               sAnimLook[0], sAnimLook[1], sAnimLook[2],
					               sAnimCamDist, s3DresetTime);
			camera.setActive(true);
			break;
		default:
			System.out.printf("Screentype %d not supported\n", gScreenType);
			break;
		}
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// ControlP5 controlEvent routine.
	public void controlEvent(ControlEvent theEvent) 
	{
		if(theEvent.isGroup())
		{
		  print("\ngot group event from "+theEvent.group().name()+"\t");
		  if(s2DradioYear.equals(theEvent.group().name()) ||
		     s3DradioYear.equals(theEvent.group().name()))
		  {
			  print("\t "+theEvent.group().value());
			  int curYear  = (int)theEvent.group().value();
			  int curMonth = yearIdxToRadioMonth(gYearIdx2Disp);
			  gYearIdx2Disp = radioToYearIdx(curYear, curMonth);
		  }
		  else if(s2DradioMonth.equals(theEvent.group().name()) ||
				  s3DradioMonth.equals(theEvent.group().name()))
		  {
			  print("\t "+theEvent.group().value());
			  int curYear  = yearIdxToRadioYear(gYearIdx2Disp);
			  int curMonth = (int)theEvent.group().value();
			  gYearIdx2Disp = radioToYearIdx(curYear, curMonth);  
		  }
		}
		else if(theEvent.isController())
		{			
			print("\ngot controller event from "+theEvent.controller().name()+"\t");
			if(s2DprevButton.equals(theEvent.controller().name()) ||
			   s3DprevButton.equals(theEvent.controller().name()) ||
			   sAnimPrevButton.equals(theEvent.controller().name()))
			{
			   print("\t "+theEvent.controller().value());
			   gYearIdx2Disp++;
			    if(gYearIdx2Disp >= gMonths.length)
			    {
			      gYearIdx2Disp = 0;
			    }
			    updateRadioButtonsScreen2D();
			    updateRadioButtonsScreen3D();
			}
			else if(s2DnextButton.equals(theEvent.controller().name()) ||
					s3DnextButton.equals(theEvent.controller().name()) ||
					sAnimNextButton.equals(theEvent.controller().name()))
			{
			    print("\t "+theEvent.controller().value());
			    gYearIdx2Disp--;
			    if(gYearIdx2Disp < 0)
			    {
			      gYearIdx2Disp = gMonths.length-1;
			    }
			    updateRadioButtonsScreen2D();
			    updateRadioButtonsScreen3D();  
			}
			else if(s2DtopButton.equals(theEvent.controller().name()) ||
					s3DtopButton.equals(theEvent.controller().name()) ||
					sAnimTopButton.equals(theEvent.controller().name()))
			{
			    print("\t "+theEvent.controller().value());
			    gScreenType = 0;
			    switchToCurrentControlWindow();
			}
			else if(s3DresetButton.equals(theEvent.controller().name()))
			{
			    print("\t "+theEvent.controller().value());
			    resetCameraAndPosition(s3DCamRot[0], s3DCamRot[1], s3DCamRot[2], 
				                       s3Dlook[0], s3Dlook[1], s3Dlook[2],
				                       s3DCamDist, s3DresetTime);
                s3DcurAlpha = s3DdefaultAlpha;
			    updateRadioButtonsScreen3D();
			}
			else if(sAnimResetButton.equals(theEvent.controller().name()))
			{
			    print("\t "+theEvent.controller().value());
				resetCameraAndPosition(sAnimCamRot[0], sAnimCamRot[1], sAnimCamRot[2], 
						               sAnimLook[0], sAnimLook[1], sAnimLook[2],
						               sAnimCamDist, s3DresetTime);
			}
			else if(sAnimPauseButton.equals(theEvent.controller().name()))
			{
			    print("\t "+theEvent.controller().value());
			    gDoAnimation = false;
			}
			else if(sAnimPlayButton.equals(theEvent.controller().name()))
			{
			    print("\t "+theEvent.controller().value());
			    gDoAnimation = true;
			}
			else if(s3DalphaSlider.equals(theEvent.controller().name()))
			{
			    print("\t "+theEvent.controller().value());
			    s3DcurAlpha = (int)theEvent.controller().value();
			}
		} 
	}

    ////////////////////////////////////////////////////////////////////////////
	// Draw Row graphs
	public void drawRowBarGraph()
	{
	  int gridRows     = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numFirstCharacters;
	  int gridCols     = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numDeweyClasses;
	  int rowBarGraphY = y0 + (gridRows*(cCellHt + cCellHtGap)) + cColBarGap;
	  int maxBarHt     = cColBarMaxHt; // pixels
	  int barWd        = cColBarMaxWd;  // pixels

	  // Find maximum average to adjust bar heights
	  StatCalc barStat = new StatCalc();
	  for(int i = 0; i < gridCols-1; i++)
	  {
	    barStat.enter(colAvg[gDispKeyWordsIdx][gYearIdx2Disp][i]);
	  }

	  for(int i = 0; i < gridCols-1; i++)
	  {
	    int rowBarGraphX = x0 + (i*cCellWd) + floor(i/cCellWdGrp)*cCellWdGap + 2;
	    float barHt = map((float)colAvg[gDispKeyWordsIdx][gYearIdx2Disp][i], 0,
	                      (float)barStat.getMax(), 0, (float)maxBarHt);
	    stroke(0);
	    fill(128);
	    rect(rowBarGraphX, rowBarGraphY, barWd, barHt);

	  }
	}

    ////////////////////////////////////////////////////////////////////////////
	// Draw Col graphs
	public void drawColBarGraph()
	{
	  int gridRows     = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numFirstCharacters;
	  int gridCols     = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numDeweyClasses;
	  int rowBarGraphX = x0 + (gridCols*cCellWd) +
	                     floor(gridCols/cCellWdGrp)*cCellWdGap + cRowBarGap;
	  int maxBarWd     = cRowBarMaxWd; // pixels
	  int barHt        = cRowBarMaxHt;  // pixels

	  // Find maximum average to adjust bar heights
	  StatCalc barStat = new StatCalc();
	  for(int j = 0; j < gridRows; j++)
	  {
	    barStat.enter(rowAvg[gDispKeyWordsIdx][gYearIdx2Disp][j]);
	  }

	  for(int j = 0; j < gridRows; j++)
	  {
	    int rowBarGraphY = y0 + (j*(cCellHt+cCellHtGap)) + 4;
	    float barWd = map((float)rowAvg[gDispKeyWordsIdx][gYearIdx2Disp][j], 0,
	                      (float)barStat.getMax(), 0, (float)maxBarWd);
	    stroke(0);
	    fill(128);
	    rect(rowBarGraphX, rowBarGraphY, barWd, barHt);

	  }
	}

    ////////////////////////////////////////////////////////////////////////////
	// Draw Static Text on 2D screen
	public void drawStaticText()
	{
	  int gridRows     = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numFirstCharacters;
	  int gridCols     = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numDeweyClasses;
	  float textX;
	  float textY;
	  textFont(font14, 14);
	  fill(128);

	  // Draw row text
	  textX = 10;
	  for(int i = 0; i < DLS.LettersText.length; i++)
	  {
	    textY = y0 + (i*(cCellHt+cCellHtGap)) + 8;
	    textAlign(CENTER, CENTER);
	    text(DLS.getLettersText(i), textX, textY);
	  }

	  // Draw column text
	  textY = 10;
	  for(int i = 0; i < DLS.DeweyText.length-1; i++)
	  {
	    textX = x0 + (i*((cCellWd * cCellWdGrp)+cCellWdGap)) + floor(cCellWdGrp*cCellWd/2);
	    textAlign(CENTER, CENTER);
	    text(DLS.getDeweyText(i), textX, textY);
	  }
	  textX = x0 + (DLS.DeweyText.length-1)*((cCellWd * cCellWdGrp)+cCellWdGap) + 4;
	  textAlign(CENTER, CENTER);
	  text(DLS.getDeweyText(DLS.DeweyText.length-1),
	       textX, textY);

	  // Bargraph Text
	  rotate(radians(270));
	  textAlign(CENTER, CENTER);
	  textX = x0 + (gridCols*cCellWd) +
	          floor(gridCols/cCellWdGrp)*cCellWdGap + floor(cRowBarGap/2);
	  textY = -(y0 + (gridRows*floor(cCellHt + cCellHtGap)/2));
	  text("ROW   AVERAGES", textY, textX);

	  // Bargraph Text
	  rotate(radians(-270));
	  textAlign(CENTER, CENTER);
	  textX = x0 + floor(((gridCols*cCellWd) +
	                      floor(gridCols/cCellWdGrp)*cCellWdGap)/2);
	  textY = (y0 + (gridRows*(cCellHt + cCellHtGap)) + floor(cColBarGap/2));
	  text("COLUMN   AVERAGES", textX, textY);

	  // Data information
	  textY += 75;
	  textAlign(LEFT, CENTER);
	  text("Source: Seattle Public Library", textX, textY);

	  textFont(font14, 14);
	  textX = x0;
	  textAlign(LEFT, CENTER);
	  if(gDispKeyWordsIdx == 0)
	  {
	    text("*Considers all titles", textX, textY);
	  }
	  else
	  {
	    text("*Considers \"" + displayKeyWords[gDispKeyWordsIdx] + "\" titles only", textX, textY);
	  }


	  // Info Box Text Title
	  int infoX0 = (x0 + (gridCols*cCellWd) +
	                floor(gridCols/cCellWdGrp)*cCellWdGap +
	                cRowBarMaxWd + 24);
	  int infoY0 = y0;

	  textFont(font18, 18);
	  fill(64);

	  textAlign(LEFT, CENTER);
	  textX = infoX0 + 2;
	  textY = infoY0 - 8;
	  text("DEWEY NUMBERS, FIRST LETTERS & TRANSACTIONS",
	       textX, textY, cInfoBoxWd-4, cTitleBoxHt-4);

	  textFont(font26, 26);
	  fill(0, 0, 128);
	  textAlign(LEFT, CENTER);
	  textY = infoY0 + (cTitleBoxHt-4);
	  text(gMonths[gYearIdx2Disp], textX, textY);
	}

    ////////////////////////////////////////////////////////////////////////////
	// Draw dynamic Text on 2D screen. Called with mouse hover
	public void displayDynamicText(int selectedRow, int selectedCol)
	{
	  //int gridRows     = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numFirstCharacters;
	  int gridCols     = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numDeweyClasses;
	  float textX;
	  float textY;

	  // Info Box Text
	  int infoX0 = (x0 + (gridCols*cCellWd) +
	                floor(gridCols/cCellWdGrp)*cCellWdGap +
	                cRowBarMaxWd + 24);
	  int infoY0 = y0 + 64;

	  textX = infoX0 + 2;
	  textY = infoY0 + (cTitleBoxHt-4);

	  textFont(font14, 14);
	  fill(128);
	  textAlign(LEFT, CENTER);
	  textY += (14+14+2);
	  text("Number of transactions: ", textX, textY);

	  textY += (14+18+2);
	  text("Title begins with: ", textX, textY);

	  textY += (14+18+2);
	  text("Dewey Number Range: ", textX, textY);

	  textY += (14+18+2);
	  text("Category: ", textX, textY);

	  //textY += ((14+18)*3+16);


	  textY += ((14+18)*3+8);
	  text("Row Average: ", textX, textY);

	  textY += (14+18+2);
	  text("Row Maximum: ", textX, textY);

	  textY += (14+18+2);
	  text("Row Minimum: ", textX, textY);

	  textY += ((14+18)*2+2);
	  text("Column Average: ", textX, textY);

	  textY += (14+18+2);
	  text("Column Maximum: ", textX, textY);

	  textY += (14+18+2);
	  text("Column Minimum: ", textX, textY);

	  //Actual Information
	  textY = infoY0 + (cTitleBoxHt-4);
	  textFont(font18, 18);
	  fill(64);
	  textAlign(LEFT, CENTER);
	  textY += (14+14+4+14);
	  text(nfc(DLM[gDispKeyWordsIdx][gYearIdx2Disp].count[selectedRow][selectedCol]),
	       textX, textY); // Total transactions

	  textY += (14+18+2);
	  text(DLS.getLettersText(selectedRow), textX, textY); // Letter Selected

	  textY += (14+18+2);
	  text(DLS.getDeweyInfoText(selectedCol), textX, textY); //Dewey Range

	  textAlign(LEFT, TOP);
	  textY += (14+18+8);
	  text(DLS.getDeweyNamesLong(selectedCol),
	       textX, textY-14,
	       (cInfoBoxWd-4), ((14+18)*3+2)); // Dewey Category Info

	  //textY += ((14+18)*3+16);
	  textAlign(LEFT, CENTER);
	  textY += ((14+18)*3+2);
	  text(nfc(rowAvg[gDispKeyWordsIdx][gYearIdx2Disp][selectedRow]), textX, textY); // Row Average

	  textY += (14+18+2);
	  text(nfc(rowMax[gDispKeyWordsIdx][gYearIdx2Disp][selectedRow]), textX, textY); //Row Max

	  textY += (14+18+2);
	  text(nfc(rowMin[gDispKeyWordsIdx][gYearIdx2Disp][selectedRow]), textX, textY); // Row Min

	  textY += ((14+18)*2+2);
	  text(nfc(colAvg[gDispKeyWordsIdx][gYearIdx2Disp][selectedCol]), textX, textY); // Column Average

	  textY += (14+18+2);
	  text(nfc(colMax[gDispKeyWordsIdx][gYearIdx2Disp][selectedCol]), textX, textY); // Column Max

	  textY += (14+18+2);
	  text(nfc(colMin[gDispKeyWordsIdx][gYearIdx2Disp][selectedCol]), textX, textY); // Column Min
	}

    ////////////////////////////////////////////////////////////////////////////
	// Draw color index bar
	public void drawColorBar()
	{
	  int gridRows     = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numFirstCharacters;
	  int gridCols     = DLM[gDispKeyWordsIdx][gYearIdx2Disp].numDeweyClasses;
	  float textX;
	  float textY;

	  int localX0 = (x0 + (gridCols*cCellWd) +
	                 (floor(gridCols/cCellWdGrp)*cCellWdGap) +
	                 cRowBarGap + 4);
	  int localY0 = (y0 + (gridRows*(cCellHt + cCellHtGap)) +
	                 cColBarGap + 4);

	  for(int i = 0; i < colorMap.mapLength; i++)
	  {
	    int curX = localX0 + 1*i;
	    stroke(255,0);
	    fill(colorMap.getColor(i,0), colorMap.getColor(i,1),
	         colorMap.getColor(i,2),cColorMapAlpha);
	    rect(curX, localY0, 1, cCellHt);
	  }
	  noFill();
	  stroke(128);
	  rect(localX0, localY0, colorMap.mapLength, cCellHt);

	  textFont(font14, 14);
	  fill(128);

	  textX = localX0;
	  textY = localY0 + cCellHt + 8;
	  textAlign(CENTER, CENTER);
//	  fill(colorMap.getColor(0,0), colorMap.getColor(0,1),
//	       colorMap.getColor(0,2),cColorMapAlpha);
	  text("LOW", textX, textY);

	  textX = localX0+(colorMap.mapLength - 1);
//	  fill(colorMap.getColor((colorMap.mapLength-1),0),
//	       colorMap.getColor((colorMap.mapLength-1),1),
//	       colorMap.getColor((colorMap.mapLength-1),2),cColorMapAlpha);
	  text("HIGH", textX, textY);

	  fill(128);
	  textX = localX0;
	  textY = localY0 + cCellHt*2 + 31;
	  textAlign(LEFT, CENTER);
	  text("Design: Sandeep Bhat, MAT259, Winter 2011", textX, textY);

	//  textFont(font14, 14);
	//  textX = localX0;
	//  textY = textY + 20;
	//  text("Considers photography titles only", textX, textY);
	}
	
//    ////////////////////////////////////////////////////////////////////////////
//	// Set the absolute camera position in space
//	// Code taken from following link:
//	// http://forum.processing.org/topic/rotation-looking-through-a-vector-help
//	void setCameraPosition(float x, float y, float z)
//	{
////		PVector CamVector = new PVector(x,y,z);
////				  
////		PVector CVY = new PVector(0, CamVector.y, CamVector.z);
////		PVector CVX = new PVector(1,0,0);
////		PVector CVZ = CVX.cross(CVY);
////		  
////		CVX.normalize();
////		CVY.normalize();
////		CVZ.normalize();
////		
////		PVector PV = new PVector( CVX.dot( CamVector )
////		                         , CVY.dot( CamVector )
////		                         , CVZ.dot( CamVector ));
////		 
////		float pitch = atan2( CamVector.z, CamVector.y ) - HALF_PI;
////		float yaw   = atan2( PV.x, PV.y );
////		float roll  = 0;
////		 
////		camera.setRotations(pitch,yaw,roll);
////		printCameraPosition();
//	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Print the current position of the camera
	public void printCameraPosition()
	{
		  float[] position = camera.getPosition();
		  System.out.println("Camera XYZ at ["+position[0]+", "+position[1]+", "+position[2]+"]");
		  
		  float[] rotations = camera.getRotations();
		  System.out.println("Camera PYR at ["+rotations[0]+", "+rotations[1]+", "+rotations[2]+"]");
		  
		  float[] lookPt = camera.getLookAt();
		  System.out.println("Camera looks at ["+lookPt[0]+", "+lookPt[1]+", "+lookPt[2]+"]");
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Reset the camera and its position in the given animation time
	public void resetCameraAndPosition(double pitch, double yaw, double roll, 
			                    int lookX, int lookY, int lookZ,
			                    double distance, 
			                    long animationTime)
	{
		//camera.reset(animationTime);		 
		camera.setRotations(pitch,yaw,roll);
		camera.lookAt(lookX, lookY, lookZ,animationTime);
		camera.setDistance(distance);
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Render the text on a plane facing the camera
	public void textFaceCamera(String dispStr, int textX, int textY, int textZ)
	{
		pushMatrix();
		translate(textX, textY, textZ);
	    rotateX(rotations[0]); 
        rotateY(rotations[1]); 
        rotateZ(rotations[2]);
	    text(dispStr, 0, 0, 0);
		popMatrix();
	}
	
    ////////////////////////////////////////////////////////////////////////////
	// Reset the animation integrators to zero
	public void integratorReset()
	{
		int gridRows = DLM[0][0].numFirstCharacters;
		int gridCols = DLM[0][0].numDeweyClasses;
		  
		for (int j = 0; j < gridRows; j++)         
	      {
			  for (int i = 0; i < gridCols; i++)
	          {
	        	  smoothBuildings[j][i].set(0);
	          }
	      }
	}
	
}



