Index: /gs3-extensions/vishnu/trunk/README
===================================================================
--- /gs3-extensions/vishnu/trunk/README	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/README	(revision 21054)
@@ -0,0 +1,82 @@
+Vishnu package for ...
+
+If you are running this as part of Greenstone, just installing Greenstone will install Vishnu.
+
+If you want to run it standalone:
+
+You need to have Java 1.4 or higher and Ant installed. The environment variable JAVA_HOME needs to be set. 
+You need to have MG installed - this should be the version from Greenstone, and you need the mg.jar file in your CLASSPATH and the libmgjni.so in your LD_LIBRARY_PATH.
+You need the lucene 1.4 or higher jar file in your CLASSPATH.
+Other jar files needed in CLASSPATH: xercesImpl.jar, servlet-api.jar.
+These jar files all come as part of the Greenstone download. Some in gsdl3/lib/java, and servlet.jar in gsdl3/comms/jakarta/tomcat/lib (when unpacked).
+
+Edit the build.xml file and change the prefix property to be the current directory.
+
+Then run 'ant' (or "ant all').
+
+
+COLLECTION BUILDING:
+
+Make a directory called collections inside the main vishnu directory. Inside this, create a new directory for your collection. The name of this directory is the collection identifier. Inside the collection directory create a new direcotry called import. Inside here put all the documents that you want in your collection. Documents must be plain text, xml or html.
+
+Then you can build the collection:
+
+Command line: 
+java vishnu.builder.CollectionBuilder -collsHome (directory where collections live: path-to-vishnu/collections) -collection (directory name of collection) -name (descriptive name for collection) -description (collection description) -index ("mg" or "luc")
+
+If you want to use views, add the option "-use-views" to the above command. This will create a separate index for each sub-directory in the import folder, and you can search each sub-view independently.
+
+Using a GUI tool:
+java vishnu.builder.IndexerGUI
+
+Fill in the fields as appropriate, and click "Start indexing".
+
+Once the collection is built, you need to rename the building directory to index. Collections are built into the building directory, but served from the index directory. This allows you to serve a built collection while rebuilding.
+
+For collections in the Greenstone 3 context, you will need to create the files etc/collectionConfig.xml and index/buildConfig.xml.
+These should look like the following:
+
+etc/collectionConfig.xml:
+<collectionConfig>
+  <metadataList>
+    <metadata name="creator">creator's email address</metadata>
+  </metadataList>
+  <displayItemList>
+    <displayItem name='name' lang='en'>Collection name</displayItem>
+    <displayItem name='description' lang='en'>Collection description
+    </displayItem>
+  </displayItemList>
+</collectionConfig>
+
+index/buildConfig.xml:
+<buildConfig>
+  <metadataList/>
+  <serviceRackList>
+    <serviceRack name="Visualizer">
+      <engineType name="MG"/> <!-- or LUCENE, depending on how you built the collection -->
+    </serviceRack>
+  </serviceRackList>
+</buildConfig>
+
+Note that you cannot use views in the Greenstone context yet.
+
+RUNNING THE SERVLET:
+
+
+If you are running this standalone, you need to run a servlet container for VisServlet.
+We have provided an example configuration for use with Tomcat (version 4.1).
+
+The vishnu/web directory is setup to be the context directory for vishnu in Tomcat. 
+You need to edit Tomcat's conf/server.xml file, and add in the context for vishnu. Add the following element after the tomcat root context element:
+
+<Context path="/vishnu" docBase="full-path-to-vishnu/web" debug="1" reloadable="true"><Resources allowLinking='true'/></Context>
+(replacing "full path-to-vishnu" with the path to the vishnu directory.)
+Edit the vishnu/web/WEB-INF/web.xml file and put in the correct path to your collections directory.
+
+The vishnu.jar file needs to be in your CLASSPATH, and make sure that Tomcat is using your CLASSPATH.
+
+RUNNING THE APPLET:
+
+The file vishnu/web/index.html is an introductory page about vishnu, and contains the applet. Make sure that the library parameter has the correct address for your visualiser servlet. The default is http://localhost:8080/vishnu/server. This will be correct if you have installed Tomcat to use port 8080, and have set the vishnu servlet up as described above. But note, if you are using Mozilla, it may not like localhost here, so if you have problems, try putting the full name of the server running Tomcat in place of localhost.
+
+If you are running Tomcat as described above, you can view this index file at http://localhost:8080/vishnu/index.html
Index: /gs3-extensions/vishnu/trunk/build.xml
===================================================================
--- /gs3-extensions/vishnu/trunk/build.xml	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/build.xml	(revision 21054)
@@ -0,0 +1,91 @@
+<?xml version="1.0"?>
+
+<!-- ====================================================================== 
+     April 2005                                                    
+
+     Vishnu build script
+                   
+     kjdon                                                                
+     ====================================================================== -->
+<project name="vishnu" default="all" basedir=".">
+
+  <property environment="env"/>
+  <property name="build.dir" value="${basedir}/build"/>
+  <property name="src.dir" value="${basedir}/src"/>
+
+  <!-- if vishnu is installed as a package inside greenstone -->
+  <property name="prefix" value="../.."/>
+  <!-- use if vishnu is standalone -->
+  <property name="prefix" value="${basedir}"/>
+  <property name="catalina.home" value="${env.CATALINA_HOME}"/>
+  <property name="web.lib" value="${prefix}/web/WEB-INF/lib"/>
+  <property name="web.classes" value="${prefix}/web/WEB-INF/classes"/>
+
+
+<!--  ==================== Compilation Control Options ==================== -->
+
+<!--
+
+  These properties control option settings on the Javac compiler when it
+  is invoked using the <javac> task.
+
+  compile.debug        Should compilation include the debug option?
+
+  compile.deprecation  Should compilation include the deprecation option?
+
+  compile.optimize     Should compilation include the optimize option?
+
+-->
+
+  <property name="compile.debug"       value="true"/>
+  <property name="compile.deprecation" value="true"/>
+  <property name="compile.optimize"    value="true"/>
+
+  <!-- classpath : this is for vishnu with Greenstone. For standalone vishnu,
+  make sure all the specified jar files are on the environment CLASSPATH -->
+  <path id="compile.classpath">
+    <pathelement location="${prefix}/lib/jni/mg.jar"/>
+    <pathelement location="${web.lib}/lucene-1.4.1.jar"/>
+    <pathelement location="${catalina.home}/common/lib/servlet-api.jar"/>
+    <pathelement location="${catalina.home}/common/endorsed/xercesImpl.jar"/>
+    <pathelement path="${env.CLASSPATH}"/>
+    <!-- if not usign this with greenstone, don't need this -->
+    <pathelement location="${web.lib}/gsdl3.jar"/>
+  </path>
+
+  <path id="ld.library.path">
+    <pathelement location="${prefix}/lib/jni"/>
+  </path>
+  <target name="all" depends="clean,compile"/>
+
+  <target name="clean" depends=""
+    description="Remove the compiled code">
+    <delete dir="${build.dir}"/>
+  </target>
+  
+  <target name="compile" depends=""
+    description="Compile the code">
+    <mkdir dir="${build.dir}"/>
+    <javac srcdir="${src.dir}"
+      destdir="${build.dir}"
+      debug="${compile.debug}"
+      deprecation="${compile.deprecation}"
+      optimize="${compile.optimize}">
+      <classpath>
+	<path refid="compile.classpath"/>
+      </classpath>
+      <!-- uncomment this if not in a greenstone context -->
+      <!--<exclude name="org/**"/>-->
+    </javac>
+    <jar destfile="${build.dir}/vishnu.jar">
+      <fileset dir="${build.dir}">
+	<include name="vishnu/**"/>
+	<include name="org/**"/>
+      </fileset>
+      <manifest>
+	<attribute name="Built-By" value="${user.name}" />
+      </manifest>
+    </jar>      
+    <copy file="${build.dir}/vishnu.jar" todir="${web.lib}"/>
+  </target>
+</project>
Index: /gs3-extensions/vishnu/trunk/src/org/greenstone/gsdl3/service/Visualizer.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/org/greenstone/gsdl3/service/Visualizer.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/org/greenstone/gsdl3/service/Visualizer.java	(revision 21054)
@@ -0,0 +1,295 @@
+/*
+ *    Visualiser.java
+ *    Copyright (C) 2004 New Zealand Digital Library, http://www.nzdl.org
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package org.greenstone.gsdl3.service;
+
+import org.greenstone.gsdl3.util.*;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node; 
+import org.w3c.dom.Element; 
+import org.w3c.dom.Text; 
+import java.util.Vector;
+import java.util.HashMap;
+import java.io.File;
+import java.io.*;
+import vishnu.server.*;
+import vishnu.server.Search.*;
+import vishnu.datablock.*;
+import vishnu.util.Base64;
+
+public class Visualizer
+    extends ServiceRack {
+    
+    // the services on offer
+    private static final String VIS_SERVICE = "VisApplet";
+
+    // other internal strings
+    private static final String ENGINE_TYPE_ELEM = "engineType";
+    private static final String LUCENE_ENGINE = "LUCENE";
+    private static final String MG_ENGINE = "MG";
+
+    private Element applet_description = null;
+    
+    private String engine_type = LUCENE_ENGINE; // lucene is default
+    private String collection_home = null;
+    private SearchInterface engine = null;
+    public Visualizer () {
+
+    }
+    public boolean configure(Element info, Element extra_info)
+    {
+	if (!super.configure(info, extra_info)){
+	    return false;
+	}
+
+	Element e = this.doc.createElement(GSXML.SERVICE_ELEM);
+	e.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_APPLET);
+	e.setAttribute(GSXML.NAME_ATT, VIS_SERVICE);
+	short_service_info.appendChild(e);
+
+	applet_description = this.doc.createElement(GSXML.SERVICE_ELEM);
+        applet_description.setAttribute(GSXML.TYPE_ATT, GSXML.SERVICE_TYPE_APPLET);
+        applet_description.setAttribute(GSXML.NAME_ATT, VIS_SERVICE);
+
+
+	String app_info = "<"+GSXML.APPLET_ELEM+" CODEBASE='lib' CODE='vishnu.testvis.visual.VishnuSingle.class' ARCHIVE='vishnu.jar,gsdl3.jar,xercesImpl.jar,xml-apis.jar' WIDTH='1000' HEIGHT='800'>";
+	app_info += "<PARAM NAME='library' VALUE=''/>"; // filled in by receptionist
+	app_info += "<PARAM NAME='viscgi' VALUE='?";
+	app_info += GSParams.ACTION +"=a&amp;"+GSParams.REQUEST_TYPE +"=r&amp;"+GSParams.SERVICE+"="+VIS_SERVICE+"&amp;"+GSParams.OUTPUT+"=xml&amp;"+GSParams.RESPONSE_ONLY+"=1'/>";
+	app_info += "<PARAM NAME='collection'   VALUE='" + this.cluster_name + "'/>";
+	app_info += "<PARAM NAME='engine'   VALUE='GSDLEngine' />";
+	// add view info if appropriate
+	app_info += "The visualization applet.</"+GSXML.APPLET_ELEM+">";
+	   
+	Document dom = converter.getDOM(app_info);
+	if (dom == null) {
+	    System.err.println("Visualizer.configure Error: Couldn't parse applet info");
+	    return false;
+	}
+	Element app_elem = dom.getDocumentElement();
+	applet_description.appendChild(this.doc.importNode(app_elem, true));
+
+	// get engine type from config file
+	Element engine_elem = (Element)GSXML.getChildByTagName(info, ENGINE_TYPE_ELEM);
+	if (engine_elem != null) {
+	    engine_type = engine_elem.getAttribute(GSXML.NAME_ATT);
+	    if (engine_type.equals("")) {
+		engine_type = LUCENE_ENGINE;
+	    }
+	}
+	
+	collection_home = this.site_home + File.separator + "collect"+ File.separator + this.cluster_name + File.separator;
+	if (engine_type.equals(LUCENE_ENGINE)) {
+	    // make the full path the arg to MGSearcher??
+	    engine = new LUCSearcher(collection_home, null);
+	} else if (engine_type.equals(MG_ENGINE)) {
+	    engine = new MGSearcher(collection_home, this.cluster_name, null);
+	} else {
+	    System.err.println("Visualiser: invalid engine type: "+engine_type);
+	    return false;
+	}
+	return true;
+    }	
+    
+    
+    
+    protected Element getServiceDescription(String service, String lang, String subset) {
+	if (!service.equals(VIS_SERVICE)) {
+	    return null;
+	}
+	Element describe = (Element) applet_description.cloneNode(true);
+	describe.appendChild(GSXML.createDisplayTextElement(this.doc, GSXML.DISPLAY_TEXT_NAME,  
+							    getTextString(VIS_SERVICE+".name", lang)));
+	describe.appendChild(GSXML.createDisplayTextElement(this.doc, GSXML.DISPLAY_TEXT_DESCRIPTION,  
+							    getTextString(VIS_SERVICE+".description", lang)));
+	return describe;
+    }
+    
+    protected Element processVisApplet(Element request)
+    {
+
+	Element param_elem = (Element)GSXML.getChildByTagName(request, GSXML.PARAM_ELEM+GSXML.LIST_MODIFIER);
+	HashMap params = GSXML.extractParams(param_elem, false);
+
+	String type = (String)params.get("type");
+
+	// the result element
+	Element result = this.doc.createElement(GSXML.RESPONSE_ELEM);
+	result.setAttribute(GSXML.FROM_ATT, VIS_SERVICE);
+	result.setAttribute(GSXML.TYPE_ATT, GSXML.REQUEST_TYPE_PROCESS);
+
+	// applet result info must be in appletInfo element
+	Element applet_data = this.doc.createElement(GSXML.APPLET_DATA_ELEM);
+	result.appendChild(applet_data);
+	Element vis_data = this.doc.createElement("visData");
+	applet_data.appendChild(vis_data);
+
+	String results = "";
+	if (type.equals("search")) {
+
+	    //Element cluster_results = this.doc.createElement("cluster");
+	    //vis_data.appendChild(cluster_results);
+	    //Element description_results = this.doc.createElement("descriptions");
+	    //vis_data.appendChild(description_results);
+
+	    String query = (String)params.get("q");
+	    System.err.println("the query was "+query);
+	    
+	    Vector doc_nums = new Vector();
+	    Vector descriptions = new Vector();
+	    
+	    engine.search(query);
+	    doc_nums = engine.getDocIdentifiers();
+	    descriptions = engine.getDocDescriptions();
+	    
+	    DataBlock db = generateDataBlock(collection_home, null, doc_nums, descriptions);
+	    System.err.println("got back data, now converting to string");
+	    try {
+		results = Base64.encodeObject(db);
+	    } catch (Exception e) {
+		System.err.println("trying to base64 encode the datablock, but exception happened: "+e);
+	    }
+	    System.err.println("after converting to string");
+// 	    // serialise the data block
+// 	    try {
+// 		ByteArrayOutputStream baos = new ByteArrayOutputStream();
+// 		ObjectOutputStream oos = new ObjectOutputStream(baos);
+// 		oos.writeObject(db);
+// 		oos.close();
+		
+// 		//results = baos.toString("UTF-8");
+// 		results = Base64.encode(baos.toByteArray());
+// 	    } catch (Exception e) {
+// 		System.err.println("Visualizer serialise data block error: "+e);
+// 	    }
+	    //System.err.println("num docs = "+doc_nums.size());
+	    //Cluster c = new Cluster();
+	    //results = c.getCluster(collection_home,doc_nums);
+	    //results.trim(); 
+	    //System.err.println("results = "+results);
+
+	    Text t = this.doc.createTextNode(results);
+	    vis_data.appendChild(t);
+
+// 	    for (int i=0; i<descriptions.size(); i++) {
+// 		String d = (String)descriptions.get(i);
+// 		Element de = GSXML.createTextElement(this.doc, "desc", d);
+// 		description_results.appendChild(de);
+// 	    }
+	    System.err.println("end of search");
+	} else if (type.equals("fetch")) {
+	    String docNum = (String)params.get("d");
+	
+	    Vector doc = engine.getDocContent(Integer.parseInt(docNum));
+  	    for (int i=0; i<doc.size(); i++) {
+		results += (String)doc.get(i) +"\n";
+	    }
+	    Text t = this.doc.createTextNode(results);
+	    vis_data.appendChild(t);
+	} else {
+	    System.err.println("invalid type sent to Visualiser process: "+type);
+	    return result;
+	}
+	System.err.println("returning result");
+	return result;
+    }
+
+    private DataBlock generateDataBlock(String collection_home, String view, Vector docNums, Vector descriptions) {
+
+	if (docNums.size()==0) {
+	    return null;
+	}
+	DataBlock data = new DataBlock();
+
+	CKServer ck_server = new CKServer(collection_home, view);
+	
+	/**** set data fields one by one and pass one what ever gets assembled ****/
+		
+	try{
+	    
+	    ck_server.setDescriptions(descriptions);
+	    
+	    /**** get candidate keywords ****/
+	    
+	    String[] keywords = ck_server.computeKeywords(docNums);
+	    
+	    data.words = keywords;
+	    
+	    
+	    /**** get sparse document * keyword matrix ****/
+	    
+	    SparseMatrix matrix = ck_server.getSparseMatrix(docNums);
+	    
+	    data.matrix = matrix;
+	    
+	    
+	    /**** get document indices, this is a subset of the original ****/
+	    /**** those without keywords are excluded                    ****/
+	    
+	    int[] docs = ck_server.getHitDocuments();
+	    
+	    data.docs = docs;
+	    
+	    
+	    String[] desc = ck_server.getHitDescriptions();
+	    
+	    data.descriptions = desc;
+	    
+	    
+	    /**** get 10 or so clusters ****/
+	    
+	    Vector[] clusters = ck_server.getClusters();
+	    
+	    data.clusters = clusters;
+	    
+	    
+	    double[][] centroids = ck_server.getCentroids();
+	    
+	    
+	    /**** send their centroids through sammon mapping ****/
+	    
+	    Point2D[] sammon = ck_server.getSammonMap(centroids);
+	    
+	    System.err.println("Num descriptions: " + descriptions.size());
+	    System.err.println("Num docs: " + docs.length);
+	    System.err.println("Num desc: " + desc.length);
+	    
+	    data.sammon = sammon;
+	    System.err.println("end of try in generatedatablock");
+	} catch (Exception e) {
+	    System.err.println("VisServlet: computing clustering Error: "+e);
+	    e.printStackTrace();
+	}
+	ck_server = null;
+	System.err.println("returning data");
+	return data;
+    }
+
+}
+
+
+
+
+
+
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/builder/CKIndexer.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/builder/CKIndexer.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/builder/CKIndexer.java	(revision 21054)
@@ -0,0 +1,395 @@
+package vishnu.builder;
+
+import java.io.*;
+import java.util.*;
+import java.util.Hashtable;
+import vishnu.util.*;
+
+public class CKIndexer extends Indexer
+{
+
+    static private String delimiter = "()<>.,?!'\" "; 
+
+
+    private static void usage()
+    {
+        System.out.println("java Indexer/CKIndexer \n -i InputDirectory [$COLL_HOME/import/]) \n -o OutputDirectory [$COLL_HOME/index/ck_index/java] \n -r root directory of collection \n -list (name of file listing the docs to be indexed)[docs.lst]\n -d string containing chars to be eliminated from words\n");
+	System.exit(0);
+    }
+    
+    
+    public static void main(String[] args)
+    {
+        String in = null;
+        String out = null;
+        //String COLL_HOME = null;
+	//String docList = null;
+	
+        if( args.length == 0 ){
+            usage();
+            System.exit(0);
+        }
+	
+        int a = 0;
+        while( a < args.length ){
+            if( args[a].equals("-i") )
+                in = args[++a];
+            else if( args[a].equals("-o") )
+                out = args[++a];
+            //else if( args[a].equals("-r") )
+            //    COLL_HOME = args[++a];
+	    //else if( args[a].equals("-list") )
+	    //	docList = args[++a];
+	    else if( args[a].equals("-d") )
+		delimiter = args[++a];
+            a++;
+        }
+	
+	CKIndexer cki = new CKIndexer();
+
+	if (out==null || in==null) {
+	    usage();
+	    System.exit(0);
+	}
+	
+	//if( COLL_HOME == null ) usage();
+	//cki.setRootDirectory(COLL_HOME);
+	
+	//if( out == null ) out = COLL_HOME + "/index/ck_index/java/";
+	cki.setOutputDirectory(out);
+	
+	//if( in == null ) in = COLL_HOME + "/import/";
+	cki.setInputDirectory(in);
+	
+	//if( docList != null ) cki.setDocList(docList);
+	
+	cki.startIndexing();
+    }
+    
+    
+    public CKIndexer(){
+	super("CKIndexer");
+    }
+    
+    
+//     public void setDocList(String doclist)
+//     {
+// 	docList = doclist;
+//     }
+
+
+    public void startIndexing(){
+	
+	initialize();
+	/**** read in archive ****/
+	
+	Vector docs = getDocList(inputDir); //readDocList(docList);
+	
+	System.out.println(docs.size() + " documents read");
+	
+	/****  hash of ALL words of corpus + their document frequency ****/
+	
+	Hashtable corpus_hash = new Hashtable();
+	
+	
+	/**** readDocuments and populate hash ****/
+
+	System.out.println("First run: getting document frequencies of all words");
+	 
+	getDocFreqs(docs, corpus_hash);
+	
+	
+	/**** prune words to get candidate keywords ****/
+
+	int min = 3;
+	int max = (int)(0.3*docs.size());
+	int minLength = 4;
+	
+	int num_ck = pruneKeywords(corpus_hash, minLength, min, max);
+	
+	
+	/**** sort hashtable by value ****/
+	
+	Enumeration enumer = corpus_hash.keys();
+	String[] words_ck = new String[num_ck];
+	int[] freqs_ck = new int[num_ck];
+	
+	int count = 0;
+	while( enumer.hasMoreElements() ){
+	    words_ck[count] = (String)enumer.nextElement();
+	    freqs_ck[count] = -((Integer)corpus_hash.get(words_ck[count])).intValue();
+	    count++;
+	}
+	
+	Misc.quickSort(freqs_ck,words_ck);
+	
+	
+	/**** normalize document frequencies ****/
+	
+	float[] freqs_f = new float[num_ck];
+	
+	for( int i = 0; i < num_ck; i++ ){
+	    freqs_f[i] = (float)(-1*freqs_ck[i])/docs.size();
+	}
+	
+	
+	/**** save document frequencies and candidate keywords ****/
+	
+	printToFile(freqs_f,outputDir + "doc_freq");
+	printToFile(words_ck,outputDir + "ck_words");	
+	
+	
+	/**** create (ck_word, index hash) ****/
+	
+	Hashtable ck_hash = new Hashtable();
+	
+	for( int i = 0; i < words_ck.length; i++ ){
+	    ck_hash.put(words_ck[i],new Integer(i));
+	}
+	
+	
+	/**** read documents and store for each document the ck words it contains and their frequencies ****/
+
+	System.out.println("Second run: getting term frequencies of candidate keywords");
+	
+	Vector termFreq = getTermFreqs(docs,ck_hash);
+	
+	
+	/**** save term frequency vector ****/ 
+	
+	printToFile(termFreq,outputDir + "term_freq");
+    }
+    
+    
+    private int pruneKeywords(Hashtable corpus_hash, int minLength, int min, int max){
+	Set ck_set = corpus_hash.keySet();
+	Enumeration enumer = corpus_hash.keys();
+	
+	int count = 0;
+	int pcount = 0;
+      	
+	while( enumer.hasMoreElements() ) {
+	    
+	    String word = (String)enumer.nextElement();
+	    int freq = ((Integer)corpus_hash.get(word)).intValue();
+	    
+	    
+	    /**** if word is a number remove ****/
+	    
+	    try{
+		int i = Integer.parseInt(word);
+		corpus_hash.remove(word);
+		ck_set.remove(word);
+		continue;
+	    }catch(NumberFormatException ex){}
+
+
+	    /**** if word too rare or too freq remove ****/
+	    
+	    if( word.length() < minLength || freq < min || freq > max ){
+		corpus_hash.remove(word);
+		ck_set.remove(word);
+		continue;
+	    }
+	}
+	return corpus_hash.size();
+    }
+    
+    
+    private Vector getTermFreqs(Vector docs, Hashtable ck_hash)
+    {
+	Vector termFreqVector = new Vector();
+	
+	for( int i = 0; i < docs.size(); i++ ){
+
+	    Hashtable doc_ck_hash = new Hashtable();
+	    String doc = (String)docs.elementAt(i);
+	    
+	    try{
+		BufferedReader dis = new BufferedReader(new InputStreamReader(new FileInputStream(doc)));
+		String line = "";
+		StringTokenizer strtok;
+		
+		while( (line=dis.readLine())!=null ){
+		    
+		    /**** read word by word ****/
+		    
+		    strtok = new StringTokenizer(line,delimiter);
+		    
+		    while( strtok.hasMoreTokens() ){
+			String word = strtok.nextToken();
+			Integer index = null;
+			if( (index = (Integer)ck_hash.get(word)) != null ){
+			    Integer count;
+			    
+			    if( (count = (Integer)doc_ck_hash.get(index)) != null ){
+				int newCount = count.intValue() + 1;
+				doc_ck_hash.put(index,new Integer(newCount));
+			    }else{
+				doc_ck_hash.put(index,new Integer(1));				
+			    }
+			}
+		    }
+		}
+		dis.close();
+	    }catch(Exception ex){ex.printStackTrace();}
+	
+	
+	    
+	    termFreqVector.addElement(doc_ck_hash);
+	}
+	
+	return termFreqVector;
+    }
+    
+
+    private void getDocFreqs(Vector docs, Hashtable corpus_hash)
+    {
+	for( int i = 0; i < docs.size(); i++ ){
+	    
+	    String doc = (String)docs.elementAt(i);
+	    
+	    Hashtable doc_hash = new Hashtable();
+	    
+	    try{
+		BufferedReader dis = new BufferedReader(new InputStreamReader(new FileInputStream(doc)));
+		String line = "";
+		StringTokenizer strtok;
+		
+		while( (line=dis.readLine())!=null ){
+		    
+		    
+		    /**** read word by word ****/
+		    
+		    strtok = new StringTokenizer(line,delimiter);
+		    
+		    while( strtok.hasMoreTokens() ){
+			String word = strtok.nextToken();
+			
+			
+			if( (Object)doc_hash.get(word) == null ){
+			    
+			    
+			    /**** make a note that there is at least one occurrence ****/
+			    
+			    doc_hash.put(word,word);
+			    
+			    
+			    /**** add it to corpus_hash if necessary or increment the counter ****/
+			    
+			    Integer count;
+			    if( (count = (Integer)corpus_hash.get(word)) == null ){
+				
+				
+				/**** add it to corpus_hash ****/
+				
+				corpus_hash.put(word,new Integer(1));
+			    }
+			    else{
+				
+				/**** increment counter in doc_freq ****/
+				
+				Integer newCount = new Integer(count.intValue()+1);
+				corpus_hash.put(word,newCount);
+			    }
+			}
+		    }
+		}
+		dis.close();
+		
+	    } catch(IOException ex){ex.printStackTrace();}
+	}
+    }
+    
+    private Vector getDocList(String input_dir_name) {
+
+	File input_dir = new File(input_dir_name);
+	if (!input_dir.exists()) {
+	    System.err.println("CKIndexer Error: input directory "+input_dir_name+ " does not exist!");
+	    return null;
+	}
+
+	Vector file_vector = new Vector();
+	processDirectory(input_dir, file_vector);
+	return file_vector;
+    }
+    private void processDirectory(File dir, Vector file_list) {
+	File [] files = dir.listFiles();
+	for (int i=0; i<files.length; i++) {
+	    if (files[i].isDirectory()) {
+		processDirectory(files[i], file_list);
+	    } else {
+		file_list.add(files[i].getPath());
+	    }
+	}
+
+    }
+//     private Vector readDocList(String list)
+//     {
+// 	list = inputDir + "/" + "docs.lst";
+	
+// 	File docList = new File(list);
+	
+// 	if (!docList.exists()){
+// 	    System.out.println("No documents found");
+// 	    System.exit(0);
+// 	}
+	
+// 	Vector docs = new Vector();
+	
+// 	try{
+//             BufferedReader dis = new BufferedReader(new InputStreamReader(new FileInputStream(docList)));
+// 	    String line = "";
+//             while( (line=dis.readLine())!=null ){
+// 		docs.addElement(new String(inputDir+"/" + line));
+// 	    }
+//             dis.close();
+// 	} catch(IOException ex){ex.printStackTrace();}
+	
+// 	return docs;
+//     }
+    
+    
+    private void printToFile(Object obj, String name){
+	
+	FileOutputStream fos = null;
+	ObjectOutputStream out = null;
+	
+	try{
+	    fos = new FileOutputStream(name);
+	    out = new ObjectOutputStream(fos);
+	    out.writeObject(obj);
+	    out.close();
+	}
+	catch(IOException ex){
+	    ex.printStackTrace();
+	}
+    }
+}
+
+
+	/*Hgram hg = new Hgram();
+	hg.setBinNumber(50);
+	hg.setLabel("Document frequency");
+	hg.setData(freqs_f,Hgram.RAW_DATA);
+	hg.update();*/
+
+ /*    private Map.Entry[] sort(Hashtable h)
+    {
+	Set set = h.entrySet();
+	Map.Entry [] entries = 
+	    (Map.Entry[])set.toArray(
+				     new Map.Entry[set.size()]);
+	Arrays.sort(entries, new Comparator() {
+		public int compare(Object o1, Object o2) {
+		    int i1 = ((Integer)((Map.Entry)o1).getValue()).intValue();
+		    int i2 = ((Integer)((Map.Entry)o2).getValue()).intValue();
+		    if( i1 < i2 ) return 0;
+		    else return 1;
+		}
+		public boolean equals(Object that) {
+		    return this==that;
+		}
+	    });
+	return entries;
+	}*/
Index: /gs3-extensions/vishnu/trunk/src/vishnu/builder/CollectionBuilder.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/builder/CollectionBuilder.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/builder/CollectionBuilder.java	(revision 21054)
@@ -0,0 +1,252 @@
+package vishnu.builder;
+
+import java.util.*;
+import java.io.*;
+
+public class CollectionBuilder
+{
+    //String site;
+    String collID = null; // directory name
+    String collHome = null; // full path to collections directory
+    String collName = null; // descriptive name
+    String collDesc = null; // descriptive description :-)
+    String indexer = null; // mg or lucene
+    boolean use_views = false;
+    long numDocs=0;
+    Vector views = null;
+    private static void usage()
+    {
+	System.out.println("java vishnu.builder.CollectionBuilder\n -collsHome (directory where collections live) \n -collection (directory name of collection)\n -name (descriptive name for collection) \n -description (collection description)\n -index (\"mg\" or \"luc\")\n [-use_views ]");
+    }
+    
+    
+    public static void main(String[] args)
+    {
+	if( args.length == 0 ){
+	    usage();
+	    System.exit(0);
+	}
+	
+	String collID = null;
+	String collsHome = null;
+	String collName = "";
+	String collDesc = "";
+	String indexer = null;
+	boolean use_views = false;
+	int a = 0;
+	while( a < args.length ){
+	    if (args[a].equals("-collsHome"))
+		collsHome = args[++a];
+	    else if (args[a].equals("-collection"))
+		collID = args[++a];
+	    else if( args[a].equals("-name") )
+		collName = args[++a];
+	    else if( args[a].equals("-description") )
+		collDesc = args[++a];
+	    else if( args[a].equals("-index") )
+		indexer = args[++a];
+	    else if (args[a].equals("-use_views")) 
+		use_views = true;
+	    a++;
+	}
+	
+	if (collsHome==null || collID==null || indexer==null) {
+	    usage();
+	    System.exit(0);
+	}
+	
+	if (collName.equals("")) {
+	    collName = collID;
+	}
+	if( !indexer.equals("mg") && !indexer.equals("luc") )
+	    indexer = "luc";
+	
+	if (!collsHome.endsWith(File.separator)) {
+	    collsHome += File.separator;
+	}
+	String coll_home = collsHome + collID + File.separator;
+	
+	CollectionBuilder cb = new CollectionBuilder(coll_home, collID, collName, collDesc, indexer, use_views);
+    }
+    
+    
+    
+    public CollectionBuilder(String coll_home, String coll_id, String coll_name,String coll_desc,  String ind, boolean use_views)
+    {
+	this.collHome = coll_home;
+	this.collID = coll_id;
+	this.collName = coll_name;
+	this.collDesc = coll_desc;
+	this.indexer = ind;
+	this.use_views = use_views;
+	
+	if (use_views) {
+	    views = new Vector();
+	}
+	if (!setupCollection()) {
+	    System.err.println("Couldn't setup the collection. Quitting build");
+	    return;
+	}
+	
+	/**** get text annotation ****/
+
+	FTIndexing();
+
+
+	/**** extract keywords ****/
+
+	CKIndexing();
+
+	/**** write out the config file ****/
+	printConfig();
+    }
+
+
+
+    private boolean setupCollection()
+    {
+	// check the import directory
+	File import_dir = new File(collHome + "import");
+	if (!import_dir.isDirectory()) {
+	    System.err.println("the import directory "+import_dir.getPath()+" doesn't exist. Quitting build");
+	    return false;
+	}
+	
+	
+	// create the building directory
+	try {
+	    File index_dir = new File(collHome + "building");
+	    if (!index_dir.exists()) {
+		index_dir.mkdir();
+	    } else if (!index_dir.isDirectory()) {
+		// index is present but is not a directory
+		System.err.println("Trying to create building dir but a file already exists at "+index_dir.getPath());
+		return false;
+	    }
+	} catch (Exception e) {
+	    System.err.println("CollectionBuilder.setupCollection Error: "+e);
+	    return false;
+	}
+
+	// create the view list
+	if (use_views) {
+	    // create a new view for each sub directory in import
+	    File [] sub_dirs = import_dir.listFiles();
+	    for (int i=0; i<sub_dirs.length; i++) {
+		if (sub_dirs[i].isDirectory()) {
+		    views.add(sub_dirs[i].getName());
+		}
+	    }
+	    if (views.size()==0) {
+		// we haven't found any subdirectories
+		System.err.println("no sub directories were found in the import dorectory ("+import_dir.getPath()+"), not using views");
+		use_views = false;
+
+	    }
+	}
+	return true;
+    }
+
+    private void printConfig() {
+
+	String index_type="";
+	if (indexer.equals("luc")) {
+	    index_type = "LUCENE";
+	} else if (indexer.equals("mg")) {
+	    index_type = "MG";
+	}
+	
+	try{
+	    File target = new File(collHome + "config.xml");
+	    PrintWriter out = new PrintWriter(new FileWriter(target),true);
+	
+	    StringBuffer buffer = new StringBuffer();
+	    buffer.append("<collection name=\"");
+	    buffer.append(collName);
+	    buffer.append("\" description=\"");
+	    buffer.append(collDesc);
+	    buffer.append("\" engine=\"");
+	    buffer.append(index_type);
+	    buffer.append("\" size=\"");
+	    buffer.append(String.valueOf(numDocs));
+	    buffer.append("\">\n");
+	    
+	    // add in views
+	    if (use_views) {
+		buffer.append("<viewList>\n");
+		for (int i=0; i<views.size(); i++) {
+		    buffer.append("<view name=\"");
+		    buffer.append(views.get(i));
+		    buffer.append("\"/>");
+		}
+		buffer.append("\n</viewList>\n");
+	    } 
+	    buffer.append("</collection>\n");
+		
+	    out.print(buffer.toString());
+	    out.close();
+
+	} catch (IOException e){ System.out.println("Error writing config file: " + e.toString());
+	}
+    }
+    
+
+    private void CKIndexing()
+    {
+	CKIndexer cki = new CKIndexer();
+	cki.setCollectionName(collID);
+	String in = collHome + "import"+File.separator;
+	String out = collHome + "building"+File.separator+"ck_index"+File.separator;
+	if (use_views) {
+	    for (int i=0; i<views.size(); i++) {
+		String view = (String)views.get(i);
+		System.out.println("calculating candidate keywords for view "+view);
+		cki.setOutputDirectory(out+view+File.separator);
+		cki.setInputDirectory(in+view+File.separator);
+		cki.startIndexing();
+	    }
+	} else {
+	    System.out.println("calculating candidate keywords");
+	    // just do the import dir as a whole
+	    cki.setOutputDirectory(out);
+	    cki.setInputDirectory(in);
+	    cki.startIndexing();
+	}
+    }
+    
+
+
+    private void FTIndexing()
+    {
+	String in = collHome + "import"+File.separator;
+	String out = collHome + "building"+File.separator + indexer + "_index"+File.separator;
+	Indexer the_indexer;
+	if (indexer.equals("luc") ) {
+	    the_indexer = new LuceneWrapper();
+	} else {
+	    the_indexer = new MGWrapper();
+	}
+	the_indexer.setCollectionName(collID);
+
+	if (use_views) {
+	    for (int i=0; i<views.size(); i++) {
+		String view = (String)views.get(i);
+		System.out.println("indexing the text for view "+view);
+		the_indexer.setOutputDirectory(out+view+File.separator);
+		the_indexer.setInputDirectory(in+view+File.separator);
+		the_indexer.startIndexing();
+	    }
+
+	} else {
+	    System.out.println("indexing the text");
+	    the_indexer.setOutputDirectory(out);
+ 	    the_indexer.setInputDirectory(in);
+ 	    the_indexer.startIndexing();
+	}
+
+    }
+}
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/builder/FileDocument.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/builder/FileDocument.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/builder/FileDocument.java	(revision 21054)
@@ -0,0 +1,57 @@
+package vishnu.builder;
+
+import java.io.File;
+import java.io.Reader;
+import java.io.FileInputStream;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.DateField;
+
+/** A utility for making Lucene Documents from a File. */
+
+public class FileDocument {
+  /** Makes a document for a File.
+    <p>
+    The document has three fields:
+    <ul>
+    <li><code>path</code>--containing the pathname of the file, as a stored,
+    tokenized field;
+    <li><code>modified</code>--containing the last modified date of the file as
+    a keyword field as encoded by <a
+    href="lucene.document.DateField.html">DateField</a>; and
+    <li><code>contents</code>--containing the full contents of the file, as a
+    Reader field;
+    */
+  public static Document Document(File f, String relative_path)
+       throws java.io.FileNotFoundException {
+	 
+    // make a new, empty document
+    Document doc = new Document();
+
+    // Add the path of the file as a field named "path".  Use a Text field, so
+    // that the index stores the path, and so that the path is searchable
+    //doc.add(Field.Text("path", f.getPath()));
+    doc.add(Field.Text("path", relative_path));
+    // Add the last modified date of the file a field named "modified".  Use a
+    // Keyword field, so that it's searchable, but so that no attempt is made
+    // to tokenize the field into words.
+    doc.add(Field.Keyword("modified",
+			  DateField.timeToString(f.lastModified())));
+
+    // Add the contents of the file a field named "contents".  Use a Text
+    // field, specifying a Reader, so that the text of the file is tokenized.
+    // ?? why doesn't FileReader work here ??
+    FileInputStream is = new FileInputStream(f);
+    Reader reader = new BufferedReader(new InputStreamReader(is));
+    doc.add(Field.Text("contents", reader));
+
+    // return the document
+    return doc;
+  }
+
+  private FileDocument() {}
+}
+    
Index: /gs3-extensions/vishnu/trunk/src/vishnu/builder/Indexer.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/builder/Indexer.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/builder/Indexer.java	(revision 21054)
@@ -0,0 +1,89 @@
+package vishnu.builder;
+
+import java.io.File;
+abstract public class Indexer
+{ 
+    
+    // views stuff
+//     public static final int DEFAULT = 0;
+//     public static final int DIP = 1;
+//     public static final int REG = 2;
+//     public static final int LEM = 3;
+//     public static final int SYN = 4;
+
+//     public static final String[] types = new String[]{"full","dip","reg","lem","syn"};
+    
+//     boolean use_views = false;
+//     int type = -1;
+    String name = null;
+    //String limiter = null;
+    String outputDir = null;
+    String inputDir = null;
+    String collName = null;
+
+    public Indexer(String n)
+    {
+	name = n;		
+    }
+    
+    
+//     public void setUseViews(boolean use_v) {
+// 	use_views = use_v;
+//     }
+    
+//     public void setIndexingType(int t)
+//     {
+// 	type = t;
+//     }    
+    public void setOutputDirectory(String s)
+    {
+	outputDir = s;
+    }
+    
+//     public void setLimiter(String s)
+//     {
+//         limiter = s;
+//     }
+    
+    public void setInputDirectory(String s)
+    {
+        inputDir = s;
+    }
+    
+     
+    public void setCollectionName(String c)
+    {
+	collName = c;
+    }
+
+    // this should be called at the start of startIndexing()
+    protected void initialize() {
+
+	if( inputDir == null || outputDir == null ){
+	    System.err.println("Input dir or output dir is null");
+	    System.exit(0);
+	}
+	
+	/* check if index dir exists */
+	File in = new File(inputDir);
+	if (!in.isDirectory()) {
+	    System.err.println("input directory ("+inputDir+") not a directory, exiting...");
+	    System.exit(0);
+	}
+
+	/**** check if output directory exists, if not create it ****/	
+        File f = new File(outputDir);
+        if( !f.exists() ){
+	    try {
+		f.mkdirs();
+	    } catch (Exception e) {
+		System.err.println("Couldn't create output directory "+outputDir);
+		System.err.println(e);
+		System.exit(0);
+	    }
+	}
+	
+    }
+    
+    abstract public void startIndexing();
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/builder/IndexerGUI.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/builder/IndexerGUI.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/builder/IndexerGUI.java	(revision 21054)
@@ -0,0 +1,126 @@
+package vishnu.builder;
+
+import java.awt.event.*;
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.SpringLayout;
+import java.io.File;
+import vishnu.util.SpringUtilities;
+
+public class IndexerGUI {
+    public final static boolean RIGHT_TO_LEFT = false;
+    public Toolkit toolkit = Toolkit.getDefaultToolkit();
+    private static JTextField[] textFields; 
+    private static JTextArea ja;
+
+    public static void addComponentsToPane(Container pane)
+    {
+	String [] labels = {"Collections directory: ", "Collection id (directory name): ", "Collection name: ","Collection description: ", "Indexer (\"mg\" or \"luc\"): ", "Use views (\"yes\" or \"no\"): "};
+        int numPairs = labels.length;
+        textFields = new JTextField[numPairs];
+	
+        pane.setLayout(new BoxLayout(pane,BoxLayout.Y_AXIS));
+	
+        JPanel p = new JPanel(new SpringLayout());
+	
+        for (int i = 0; i < numPairs; i++) {
+	    JLabel l = new JLabel(labels[i], JLabel.TRAILING);
+	    p.add(l);
+	    textFields[i] = new JTextField(30);
+	    l.setLabelFor(textFields[i]);
+	    p.add(textFields[i]);
+	}
+	
+	SpringUtilities.makeCompactGrid(p,numPairs, 2, 6, 6, //initX, initY
+                                6, 6);       //xPad, yPad
+	
+	pane.add(p);
+	
+	JPanel butPan = new JPanel();
+	butPan.setLayout(new BoxLayout(butPan,BoxLayout.X_AXIS));
+
+	Dimension minSize = new Dimension(10, 50);
+	Dimension prefSize = new Dimension(10, 50);
+	Dimension maxSize = new Dimension(Short.MAX_VALUE, 50);
+	butPan.add(new Box.Filler(minSize, prefSize, maxSize));
+	
+	JButton button = new JButton(" Start indexing ");
+        button.setBorder(BorderFactory.createBevelBorder(0));
+	button.addActionListener(new ActionListener(){
+                public void actionPerformed(ActionEvent e){
+			build();
+                }
+            });
+        butPan.add(button);
+
+	butPan.add(new Box.Filler(minSize, prefSize, maxSize));
+	
+        butPan.add(new Box.Filler(minSize, prefSize, maxSize));
+
+	pane.add(butPan);
+
+	butPan.add(new Box.Filler(minSize, prefSize, maxSize));
+
+	ja = new JTextArea(5,5);
+        JScrollPane scrollingArea = new JScrollPane(ja);
+	scrollingArea.setBorder(BorderFactory.createBevelBorder(1));
+	pane.add(scrollingArea);
+     }
+
+    private static void build()
+    {
+	// get data
+	String colls_home = textFields[0].getText();
+	String coll_id = textFields[1].getText();
+	String coll_name = textFields[2].getText();
+	String coll_desc = textFields[3].getText();
+	String indexer = textFields[4].getText();
+	String use_views_str = textFields[5].getText();
+	String coll_home = colls_home;
+	if (!coll_home.endsWith(File.separator)) {
+	    coll_home += File.separator;
+	}
+	coll_home += coll_id + File.separator;
+
+	if (!indexer.equals("mg") || !indexer.equals("luc")) {
+	    indexer = "mg";
+	}
+	boolean use_views = false;
+	if (use_views_str.equals("yes")) {
+	    use_views = true;
+	}
+	String output = "Launching Collection Builder with \n collections home = "+colls_home + "\n collection id = "+coll_name+"\n indexer = "+indexer;
+	if (use_views) {
+	    output += "\n using views";
+	} else {
+	    output += "\n not using views";
+	}
+	ja.setText(output);
+
+	CollectionBuilder builder = new CollectionBuilder(coll_home, coll_id, coll_name, coll_desc, indexer, use_views); 
+	output += "\n Indexing Finished";
+	ja.setText(output);
+    }
+
+    private static void createAndShowGUI() {
+        JFrame.setDefaultLookAndFeelDecorated(true);
+        JFrame frame = new JFrame("Indexing Tool");
+        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        addComponentsToPane(frame.getContentPane());
+
+        frame.pack();
+
+	Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
+        int w = frame.getSize().width;
+        int h = frame.getSize().height;
+        int x = (dim.width-w)/2;
+        int y = (dim.height-h)/2;
+        frame.setLocation(x,y);
+
+	frame.setVisible(true);
+    }
+
+    public static void main(String[] args) {
+	createAndShowGUI();
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/builder/LuceneWrapper.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/builder/LuceneWrapper.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/builder/LuceneWrapper.java	(revision 21054)
@@ -0,0 +1,162 @@
+package vishnu.builder;
+
+import java.io.*;
+import java.util.*;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.index.IndexWriter;
+
+
+public class LuceneWrapper extends Indexer
+{
+    
+    private static void usage()
+    {
+	System.out.println("java LucenceWrapper -i InputDirectory \n -o OutputDirectory \n -c CollectionName");
+    }
+    
+    
+    public static void main(String[] args)
+    {
+	String in = null;
+	String out = null;
+	String coll = null;
+
+	if( args.length == 0 ){
+	    usage();
+	    System.exit(0);
+	}
+	
+	int a = 0;
+	while( a < args.length ){
+	    if( args[a].equals("-i") )
+		in = args[++a];
+	    if( args[a].equals("-o") )
+		out = args[++a];
+	    if( args[a].equals("-c") )
+		coll = args[++a];
+	    a++;
+	}
+	if (out==null || in==null|| coll==null) {
+	    usage();
+	    System.exit(0);
+	}
+	if (!out.endsWith(File.separator)) {
+	    out += File.separator;
+	}
+	if (!in.endsWith(File.separator)) {
+	    in += File.separator;
+	}
+
+	LuceneWrapper luc = new LuceneWrapper();
+	
+	out = out + "luc_index";
+	luc.setCollectionName(coll);
+        luc.setOutputDirectory(out);
+	luc.setInputDirectory(in);
+	luc.startIndexing();
+    }
+    
+    
+    public LuceneWrapper()
+    {
+	super("Lucene Indexer");
+    }
+    
+    public void startIndexing() {
+	initialize();
+	try {
+	    IndexWriter writer = new IndexWriter(outputDir, new StandardAnalyzer(), true);
+	    // this is a bit of a hack putting import here
+	    indexFiles(writer, new File(inputDir), "import/");
+	    writer.optimize();
+	    writer.close();
+	} catch (Exception e) {
+	    System.err.println("Error creating the Lucene index: "+e);
+	    System.exit(0);
+	}
+
+    }
+
+    private void indexFiles(IndexWriter writer, File input_dir, String current_path) {
+	File [] doc_list = input_dir.listFiles();
+	for (int i=0; i<doc_list.length; i++) {
+	    File f = doc_list[i];
+	    if (f.isDirectory()) {
+		indexFiles(writer, f, current_path+f.getName()+File.separator);
+	    } else {
+		try {
+		    writer.addDocument(FileDocument.Document(f, current_path+f.getName()));	
+		} catch (Exception e) {
+		    System.err.println("Error indexing document "+current_path+f.getName()+": "+e);
+		}
+	    }
+	}
+
+    }
+
+	
+    public void startIndexingOld()
+    {
+	
+	if( inputDir == null || outputDir == null ){
+	    usage();
+	    System.exit(0);
+	}
+	
+	
+	/**** check if output directory exists, if not create it ****/
+	
+	
+        File f = new File(outputDir);
+        if( !f.exists() ){
+            System.out.println("Creating output directory..");
+	    
+            StringTokenizer strt = new StringTokenizer(outputDir,"/");
+            String d = "";
+            while(strt.hasMoreTokens()){
+                String str = strt.nextToken();
+                if( str.indexOf(".") == -1 ){
+		    d += "/" + str;
+		    if( !(new File(d)).exists() ){
+			try{
+			    Runtime rt = Runtime.getRuntime();
+			    Process proc = rt.exec("mkdir " + d);
+			    int exitVal = proc.waitFor();
+			}catch(Exception ex){ex.printStackTrace();}
+		    }
+		}
+	    }
+        }	
+	
+	File docList = new File(inputDir + "/docs.lst");
+	
+	if (!docList.exists()){
+	    System.out.println("No documents found");
+	    System.exit(0);
+	}
+	
+	Vector docs = new Vector();
+	
+	try{
+            BufferedReader dis = new BufferedReader(new InputStreamReader(new FileInputStream(docList)));
+	    String line = "";
+            while( (line=dis.readLine())!=null ){
+		docs.addElement(new String(inputDir+"/" + line));
+	    }
+            dis.close();
+	} catch(IOException ex){ex.printStackTrace();}
+	
+	try{
+	    
+	    IndexWriter writer = new IndexWriter(outputDir, new StandardAnalyzer(), true);
+	    
+	    for( int i = 0; i < docs.size(); i++ ){
+		File file = new File((String)docs.elementAt(i));		
+		//writer.addDocument(FileDocument.Document(file));
+	    }
+	    writer.optimize();
+	    writer.close();
+        }catch(Exception ex){ex.printStackTrace();}
+    }
+}
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/builder/MGWrapper.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/builder/MGWrapper.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/builder/MGWrapper.java	(revision 21054)
@@ -0,0 +1,123 @@
+package vishnu.builder;
+
+import java.io.*;
+import java.util.*;
+
+public class MGWrapper extends Indexer
+{
+    private static void usage()
+    {
+	System.out.println("java Indexer MGWrapper -i InputDirectory \n -o OutputDirectory \n -c CollectionName");
+    }
+    
+    
+    public static void main(String[] args)
+    {
+	String in = null;
+	String out = null;
+	String coll = null;
+
+	if( args.length == 0 ){
+	    usage();
+	    System.exit(0);
+	}
+	
+	int a = 0;
+	while( a < args.length ){
+	    if( args[a].equals("-i") )
+		in = args[++a];
+	    if( args[a].equals("-o") )
+		out = args[++a];
+	    if( args[a].equals("-c") )
+		coll = args[++a];
+	    a++;
+	}
+	
+	if (out==null || in==null|| coll==null) {
+	    usage();
+	    System.exit(0);
+	}
+	if (!out.endsWith(File.separator)) {
+	    out += File.separator;
+	}
+	if (!in.endsWith(File.separator)) {
+	    in += File.separator;
+	}
+	
+	out = out + "mg_index" + File.separator;
+	
+	MGWrapper mgw = new MGWrapper();
+	mgw.setCollectionName(coll);
+        mgw.setOutputDirectory(out);
+	mgw.setInputDirectory(in);
+	mgw.startIndexing();
+    }
+    
+    
+    public MGWrapper()
+    {
+	super("Managing Gigabytes Indexer");
+    }
+    
+    
+    public void startIndexing()
+    {
+
+	initialize();
+	// new mg requires a .mg_getrc to work
+	// lets put it in the output dir
+	File rc_file = new File(outputDir, ".mg_getrc");
+	if (!rc_file.exists()) {
+	    // if its there, assume it is correct
+	    try {
+		rc_file.createNewFile();
+		BufferedWriter rc_out = new BufferedWriter(new FileWriter(rc_file, true));
+		String data_line = collName+"\tDIR2\t"+inputDir;
+		rc_out.write(data_line, 0, data_line.length());
+		rc_out.newLine();
+		rc_out.flush();
+		rc_out.close();
+	    } catch (Exception ex) {
+		System.err.println(ex);
+		System.err.println("Unable to create file "+rc_file.getPath()+", cancelling build");
+		System.exit(0);
+	    }
+	}
+	/*
+	if( !f.exists() ){
+	    System.out.println("Creating output directory..");
+	    
+            StringTokenizer strt = new StringTokenizer(outputDir,"/"); 
+            String d = "";
+            while(strt.hasMoreTokens()){
+                String str = strt.nextToken();
+                if( str.indexOf(".") == -1 ){
+		    d += "/" + str;
+		    if( !(new File(d)).exists() ){
+			try{
+			    Runtime rt = Runtime.getRuntime();
+			    Process proc = rt.exec("mkdir " + d);
+			    int exitVal = proc.waitFor();
+			}catch(Exception ex){ex.printStackTrace();}
+		    }
+		}
+	    }	
+	}
+	*/
+	try{
+	    //String command = /*GSDL3_HOME +*/ "/packages/VIS/" + "MG/mg/mg-1.2.1/bin/mgbuild -d " + outputDir + " -i " + inputDir + " " + collName;
+	    //String command = "mgbuild -d " + outputDir + " -i " + inputDir + " " + collName;
+	    // requires .mg_getrc file
+	    String []env = new String [1];
+	    env[0] = "MG_GETRC="+outputDir+".mg_getrc";
+	    String command = "mgbuild -d " + outputDir + " " + collName;
+	    System.out.println("Executing.. " + command);
+	    Process proc = Runtime.getRuntime().exec(command, env);
+	    proc.waitFor();
+	    System.out.println(proc.exitValue());
+	}
+	catch(IOException ex){ex.printStackTrace();}
+	catch (InterruptedException ex){ System.out.println(ex); ex.printStackTrace();
+	} 
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/AveLink.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/AveLink.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/AveLink.java	(revision 21054)
@@ -0,0 +1,9 @@
+package vishnu.cluster;
+
+public class AveLink extends LinkFunc
+{
+    float link(float a, float b)
+	{
+  		return (a+b)/2;
+	}
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Cluster.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Cluster.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Cluster.java	(revision 21054)
@@ -0,0 +1,76 @@
+package vishnu.cluster;
+
+import java.util.*;
+
+public class Cluster
+{
+    public int id;
+    public Cluster  parent = null;
+    public Cluster  child_1 = null;
+    public Cluster  child_2 = null;
+    
+    public double children_sim;
+    
+    // size of cluster
+    public int items;
+    
+    public int split;
+    
+    // [] -> centroid for each vector element ?
+    public float[] centroid=null;
+    
+    public Vector iter_items = null;
+    public int iter_items_num = 0;
+    
+    public int x = 0;
+    public int y = 0;
+    
+    public float[] getCentroid(){return centroid;}
+    
+    public Cluster(int id, Cluster parent, Cluster c1, Cluster c2, double children_sim, int items)
+    {
+	this.id               = id;
+	this.children_sim     = children_sim;
+	this.items            = items;
+	split                 = 0;
+	this.parent           = parent;
+        iter_items            = new Vector(0);
+        
+        // probably to get a balanced tree
+	if (c1 != null && c2 != null){
+	    if (c2.items > c1.items){
+		child_1 = c1;
+		child_2 = c2;
+	    }else{
+	        child_1 = c2;
+		child_2 = c1;
+	    }
+	}else{ // if either or both are null
+	    child_1 = c1;
+	    child_2 = c2;
+	}
+    }
+    
+    // a nice recursive function to determine the depth of the hierarchy
+    public int depth()
+    {
+	if (id != -1)
+	    return 0;
+	else
+	    return Math.max(1 + child_1.depth(), 1 + child_2.depth());
+    }
+    
+    
+    void addChildrenToOutputVector(int []coding, Vector output)
+    {
+	if (id != -1){
+    	    Integer ii = new Integer(coding[id]);
+            output.addElement(ii);
+	}else{
+      	    child_1.addChildrenToOutputVector(coding,output);
+      	    child_2.addChildrenToOutputVector(coding,output);
+	}
+    }
+}
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/ClusterArray.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/ClusterArray.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/ClusterArray.java	(revision 21054)
@@ -0,0 +1,85 @@
+package vishnu.cluster;
+
+import java.util.*;
+
+
+public class ClusterArray
+{
+    Vector contents;
+    
+    ClusterArray(int num_of_clusters)
+    {
+	contents = new Vector(num_of_clusters);
+	
+	for(int i = 0; i < num_of_clusters; i++)
+	    contents.addElement(new Cluster(i, null, null, null, 0, 1));
+    }
+    
+    
+    // counts the number of clusters that are less dissimilar than disim
+    private void sweep_tree (Cluster cluster, double disim, int min_c_items, int[] clusters)
+    {
+	cluster.split = 0;
+	
+	if (cluster.id == -1){
+	    if (cluster.children_sim <= disim && cluster.items >= min_c_items){
+		cluster.split = 1;
+		contents.addElement(cluster);
+		clusters[0]++;
+	    }else{
+		sweep_tree (cluster.child_1, disim, min_c_items, clusters);
+		sweep_tree (cluster.child_2, disim, min_c_items, clusters);
+	    }
+    	}else{
+	    if (cluster.items >= min_c_items){
+		cluster.split = 1;
+		contents.addElement(cluster);
+		clusters[0]++;
+	    }
+	}
+    }
+
+
+    double split (Cluster cluster, double[] heights, int n, int target, int min_c_items, int[] clusters)
+    {
+	
+	/*
+	  Try to split 'cluster', made up of 'n' vectors, into 'target'
+	  clusters by incrementing iteratively the mimimum required
+	  similarity between the 'cluster\'s' children. The result is an
+	  array of clusters, 'cluster_ary' which contains 'clusters'
+	  clusters.  Obviously the final number of clusters may not be the
+	  same as the desired number of clusters. The function return the
+	  threshold similarity which resulted in the clusters being split
+	  in 'clusters' clusters.
+	*/
+	
+	int i;
+	
+	if (target < 2) target = 2;
+	
+	if (target > n) target = n;
+	
+	// height store minimum similarity values
+	// this is then the iterative incrementing loop
+	for (i = target - 2; i <= n - 1; i++){
+	    clusters[0] = 0;
+	    contents.removeAllElements();
+	    sweep_tree (cluster, heights[i], min_c_items, clusters);
+	    
+	    if (clusters[0] >= target) break;
+	}
+	
+	// if target - 2 > n - 1, i.e. if the previous loop has not been 
+	// entered at all
+	if (i > n - 1){
+	    /* ignore the min_c_size requirement */
+	    i = target - 1;
+	    clusters[0] = 0;
+	    contents.removeAllElements();
+	    sweep_tree (cluster, heights[i], 1, clusters);
+	}
+	
+	return heights[i];
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Clustering.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Clustering.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Clustering.java	(revision 21054)
@@ -0,0 +1,128 @@
+package vishnu.cluster;
+
+import java.util.*;
+
+
+public class Clustering
+{
+    public static final int COMPLETE_LINKAGE = 0;
+    public static final int AVERAGE_LINKAGE  = 1;
+    public static final int SINGLE_LINKAGE   = 2;
+    public static final int BUCKSHOT = 0;
+    public static final int COMPLETE = 1;
+    
+    private Vector [] clusters;
+    private double [][] centroids;
+    
+    public Vector [] getClusters()
+    {
+        return clusters;
+    }
+
+    public double[][] getCentroids()
+    {
+        return centroids;
+    }
+    
+    
+    public Clustering (int linkage, float[][] KDMatrix, int min_c_items, int target_clusters, int limit)
+    {
+        SimMatrix sim_matrix;
+	LinkFunc linkage_func=null;
+	
+        int wordCount = KDMatrix[0].length;
+	int hitDocs = KDMatrix.length;
+        
+	Cluster toplevel_cluster;
+        ClusterArray splitclusters = null;
+	int[] n_clusters = new int[]{0};
+	
+        double threshold;
+	
+        Distance sim_func = new EuclideanDistance();
+	
+	double[] heights;
+
+        int[] coding = new int[hitDocs];
+        
+
+        for (int c = 0; c < hitDocs; c++) coding[c] = c;
+	
+	switch (linkage){
+	case COMPLETE_LINKAGE:
+	    linkage_func = new MaxLink();
+	    break;
+	case AVERAGE_LINKAGE:
+	    linkage_func = new AveLink();
+	    break;
+	case SINGLE_LINKAGE:
+	    linkage_func = new MinLink();
+	    break;
+	default:
+	    System.exit(0);
+	}
+	
+
+        /**** make sure upper bound does not exceed #docs ****/
+        
+	if(limit > hitDocs) limit = hitDocs;
+	limit = (int)hitDocs/2;
+	
+
+	/**** get similarity matrix of the first limit docs with each other ****/
+	
+        sim_matrix = new SimMatrix(KDMatrix, limit, sim_func);
+	
+	heights = new double[hitDocs];
+	
+        Hierarchical hierarchical = new Hierarchical(limit);
+        
+        toplevel_cluster = hierarchical.clustering(sim_matrix, limit, heights, linkage_func);
+	if( toplevel_cluster == null )
+	    return;
+	
+        System.out.println("Clustered items: " + toplevel_cluster.items);
+	Cluster c1 = toplevel_cluster.child_1;
+	Cluster c2 = toplevel_cluster.child_2;
+	System.out.println("Child1: " + c1.items);
+	System.out.println("Child2: " + c2.items);
+	
+	splitclusters = new ClusterArray(0);
+        
+	threshold = splitclusters.split(toplevel_cluster, heights, limit, target_clusters, min_c_items, n_clusters);
+	
+    	System.out.println(n_clusters[0] + " clusters");
+	System.out.println("Threshold: " + threshold);
+	
+        System.out.println("Target_clusters " + target_clusters);
+	System.out.println("Limit " + limit);
+	System.out.println("#docs: " + hitDocs);
+	
+        Iterative iter = new Iterative(wordCount);
+	
+	iter.clustering(splitclusters, n_clusters, KDMatrix, limit, hitDocs, sim_func);
+	
+	System.out.println("Finished iterative clustering");
+
+        clusters = new Vector[n_clusters[0]];
+        centroids = new double[n_clusters[0]][wordCount];
+
+	for (int i = 0; i < n_clusters[0]; i++){
+	    clusters[i]=new Vector();
+	    Cluster clust = (Cluster)splitclusters.contents.elementAt(i);
+	    clust.addChildrenToOutputVector(coding,clusters[i]);
+	    
+	    for (int j = 0; j < clust.iter_items_num; j++){
+		Integer ii = (Integer)clust.iter_items.elementAt(j);
+		Integer jj = new Integer (coding[ii.intValue()]);
+		clusters[i].addElement(jj);
+	    }
+	    
+	    float [] centroid = clust.getCentroid();
+	    
+	    for (int j = 0; j < wordCount; j++){
+		centroids[i][j]=centroid[j];
+	    }
+	}
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Distance.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Distance.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Distance.java	(revision 21054)
@@ -0,0 +1,10 @@
+package vishnu.cluster;
+
+// Abstract class
+
+public abstract class Distance
+{
+	public abstract float get(float[] v1, float[] v2);
+}
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/EuclideanDistance.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/EuclideanDistance.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/EuclideanDistance.java	(revision 21054)
@@ -0,0 +1,15 @@
+package vishnu.cluster;
+
+public class EuclideanDistance extends Distance
+{
+    public float get(float[] v1, float[] v2)
+    {
+  	float sum = 0;
+
+	for(int c=0;c<v1.length;c++){
+      	    float x = v1[c] - v2[c];
+	    sum += x*x;
+	}
+  	return (float)Math.sqrt(sum);
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Hierarchical.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Hierarchical.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Hierarchical.java	(revision 21054)
@@ -0,0 +1,234 @@
+package vishnu.cluster;
+
+public class Hierarchical
+{
+    public float[] minrow;
+    public int[] colind;
+    public TriangleIndex triangle;
+    
+    public Hierarchical(int matrix_rows){
+	// create Triangle with "limit" arg = maximum #clusters
+	// an object to help with indexing the cells 
+	// in a triangular matrix
+	triangle = new TriangleIndex(matrix_rows);
+    }
+    
+    
+    // set each minrow[i] to the value of the minimum element of that row
+    void set_minrow (SimMatrix m, int row)
+    {
+	// triangular matrix without diagonal, hence zeroth row is all zero
+        if(row == 0) return;
+	
+	int i;
+	int index_start, index_end;
+        
+        // first column
+	index_start = triangle.index (row, 0);
+
+	// last column (row(!)-1 because as many rows as columns)
+	index_end   = triangle.index (row, row - 1); 
+	
+	minrow[row] = m.matrix[index_start];
+	colind[row] = index_start;
+	
+	for (i = index_start + 1; i <= index_end; i++){
+	    if (m.matrix[i] < minrow[row]){
+		minrow[row] = m.matrix[i];
+		colind[row] = i;
+	    }
+	}
+    }
+    
+    
+    // return the ROW in which the minimum disimilarity resides
+    int get_minrow (int last_row){
+	int i;
+	int row;
+	float value;
+	
+	row   = 1;
+	value = minrow[1];
+	
+	for (i = 2; i <= last_row; i++){
+	    if (minrow[i] < value){
+		row = i;
+		value = minrow[i];
+	    }
+    	}
+	
+	return row;
+    }
+
+    
+    // num_of_rows is "limit" = max docs to be clustered in hierarchical clustering
+    Cluster clustering (SimMatrix sim_matrix, int num_of_rows, double[] heights, LinkFunc linkage_func)
+    {
+	Cluster child_1;
+	Cluster child_2;
+	Cluster parent=null;
+	ClusterArray cluster_ary;
+	
+        int min_row, index, row, col, i;
+	double sim;
+	
+        int last_row_index, sim_matrix_size;
+	
+        sim_matrix_size = num_of_rows *(num_of_rows - 1) / 2;
+	
+	// Place each of the documents in a cluster of each own 
+	// there are vector.length docs but only num_of_rows = limit places in the array
+	// may be two-stage clustering process, i.e. cluster subset of docs and then add the rest
+	cluster_ary = new ClusterArray (num_of_rows);
+	last_row_index = num_of_rows - 1;
+	
+	// minimum distance for each row of the sim_matrix
+	minrow = new float[num_of_rows];
+	
+	// column index for minimum distance entry
+	colind = new int[num_of_rows];
+	
+	for (i = 1; i < num_of_rows; i++) set_minrow(sim_matrix, i);
+	
+	// do all the hierarchical clustering by running over all rows, updating the matrix 
+	// as a new cluster is formed each time
+	for (i = 0; i < num_of_rows - 1; i++){
+
+	    // find the row with the lowest disimilarity
+	    min_row = get_minrow(last_row_index);
+	    sim = minrow[min_row];
+	    
+	    System.out.println("Min sim: " + sim);
+	    
+	    // the column with the lowest distance in that row
+	    index  = colind[min_row];
+	    
+	    // array of heights, DESCENDING disim, i.e. sim goes last
+	    heights[num_of_rows - 2 - i] = sim; 
+	    
+	    row = triangle.i_ind[index];
+	    col = triangle.j_ind[index];
+	    
+	    // merges rows and cols to create a smaller sim_matrix
+	    merge (row, col, sim_matrix, last_row_index, linkage_func);
+	    System.out.println("Merging: " + row + " and " + col);
+	    
+	    sim_matrix_size -= last_row_index;
+	    
+	    child_1 = (Cluster)cluster_ary.contents.elementAt(row);
+	    child_2 = (Cluster)cluster_ary.contents.elementAt(col);
+	    
+	    // parents get id = -1 to indicate that they are not representing single docs
+	    // all leaves have an id (1 - num_rows) that points to the doc they contain
+	    int sum = child_1.items + child_2.items;
+	    System.out.println("Create new parent with " + child_1.items + " + " + child_2.items + " leaves");
+	    parent = new Cluster (-1, null, child_1, child_2, sim, child_1.items + child_2.items);
+	    
+	    child_1.parent = parent;
+	    child_2.parent = parent;
+	    
+	    cluster_ary.contents.setElementAt(cluster_ary.contents.elementAt(last_row_index),row);
+	    cluster_ary.contents.setElementAt(parent,col);
+	    
+	    last_row_index--;
+	}
+	
+	return parent;
+    }
+    
+    // form a new cluster from the two most similar documents
+    void merge (int i, int j, SimMatrix m, int n, LinkFunc linkage_func)
+    {
+	
+	/* i and j are the matrix elements with maximum similarity         */
+	/* matrix is the triangle similarity matrix with last row index n  */
+	/* linkage_func is a pointer to the linkage function               */
+	
+	/* i > j */
+	
+	int c, k, index1, index2;
+	
+        // go over all rows
+	for (k = 0; k <= n; k++){
+	    
+    	    // if k < j the affected elements are on row j
+	    if (k < j){
+		index1 = triangle.index (k, j);
+		index2 = triangle.index (k, i);
+		m.matrix[index1] = linkage_func.link(m.matrix[index1], m.matrix[index2]);
+	    }else if (k > j && k < i){
+		/* if j < k < i then only one element is updated per row */
+		index1 = triangle.index (k, j);
+		index2 = triangle.index (k, i);
+		m.matrix[index1] = linkage_func.link(m.matrix[index1], m.matrix[index2]);
+		
+		if (index1 == colind[k]){
+		    /* the previous minimum of the row has been changed so recaculate minimum of whole row */
+		    set_minrow(m, k);
+		}else{
+		    /* if the changed element is smaller than the current minimum update minrow and colind */
+		    if (m.matrix[index1] < minrow[k]){
+			minrow[k] = m.matrix[index1];
+			colind[k] = index1;
+		    }
+		}
+	    }else if (k > i && k < n){
+		index1 = triangle.index (k, j);
+		index2 = triangle.index (k, i);
+		m.matrix[index1] = linkage_func.link(m.matrix[index1], m.matrix[index2]);
+		//printf ("3 - [%d, %d] = link ([%d, %d], [%d, %d]) = %f\n", k, j, k, i, k, j, matrix[index2]);
+		
+		if (index1 == colind[k]){
+		    /* the previous minimum of the row has been changed so recaculate minimum of whole row */
+		    set_minrow(m, k);
+		}else{
+		    /* if the changed element is smaller than the current minimum update minrow and colind */
+		    if (m.matrix[index1] < minrow[k]){
+			minrow[k] = m.matrix[index1];
+			colind[k] = index1;
+		    }
+		}
+		
+		index1 = triangle.index (k, i);
+		index2 = triangle.index (k, n);
+		m.matrix[index1] = m.matrix[index2];
+		
+		if (index1 == colind[k]){
+		    /* the previous minimum of the row has been changed so recaculate minimum of whole row */
+		    set_minrow(m, k);
+		}else{
+		    /* if the changed element is smaller than the current minimum update minrow and colind */
+		    if (m.matrix[index1] < minrow[k]){
+			minrow[k] = m.matrix[index1];
+			colind[k] = index1;
+		    }
+		}
+	    }else if(k == n  && i != n){
+		index1 = triangle.index (k, j);
+		index2 = triangle.index (k, i);
+		m.matrix[index1] = linkage_func.link(m.matrix[index1], m.matrix[index2]);
+		
+		if (index1 == colind[k]){
+		    /* the previous minimum of the row has been changed so recaculate minimum of whole row */
+		    set_minrow(m, k);
+		}else{
+		    /* if the changed element is smaller than the current minimum update minrow and colind */
+		    if (m.matrix[index1] < minrow[k]){
+			minrow[k] = m.matrix[index1];
+			colind[k] = index1;
+		    }
+		}
+		
+		for (c = 0; c < i; c++){
+		    index1 = triangle.index (c, i);
+		    index2 = triangle.index (c, n);
+		    m.matrix[index1] = m.matrix[index2];
+		}
+		
+	    }
+    	}
+	set_minrow(m, i);
+	set_minrow(m, j);
+    }
+}
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Iterative.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Iterative.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/Iterative.java	(revision 21054)
@@ -0,0 +1,102 @@
+package vishnu.cluster;
+
+import java.util.*;
+
+class Iterative
+{
+    Vector vector;
+    int wordCount;
+    
+    Iterative(int vsize)
+    {
+	wordCount = vsize;
+    }
+    
+    public void printVector(double[] a)
+    {
+        int i;
+	
+	for (i = 0; i < wordCount; i++)
+    	    System.out.println("v[" + i + "] = " + a[i]);
+    }
+
+
+    void cluster_center (Cluster cluster, float [][] vectors, float[] centroid)
+    {
+        int i;
+	
+      	if (cluster.id == -1){
+      	    cluster_center (cluster.child_1, vectors, centroid);
+      	    cluster_center (cluster.child_2, vectors, centroid);
+    	}else{
+            float[] doubleArray = vectors[cluster.id];
+      	    for (i = 0; i < doubleArray.length; i++)
+		centroid[i] += doubleArray[i];
+        }
+    }
+    
+    void compute_cluster_centers (ClusterArray clusters, int num_of_clusters, float [][] vectors)
+    {
+	int i, j; double ssum;
+	Cluster clust;
+	
+
+	/**** for each hierarchical cluster, compute centroid ****/
+	for (i = 0; i < num_of_clusters; i++){
+	    clust = (Cluster)clusters.contents.elementAt(i);
+	    clust.centroid = new float[wordCount];
+	    cluster_center (clust, vectors, clust.centroid);
+	    
+	    for (ssum = j = 0; j < wordCount; j++)
+		ssum += clust.centroid[j] * clust.centroid[j];
+	    
+	    if(ssum>0)                // normalise
+		for(ssum = 1.0/Math.sqrt(ssum), j = 0; j < wordCount; j++)
+		    clust.centroid[j] *= ssum;
+	}
+    }
+    
+    
+    void place_item (ClusterArray clusters, int num_of_clusters, float [][] vectors, int item_id,  Distance sim_func)
+    {
+        Cluster clust = (Cluster)clusters.contents.elementAt(0);
+	double minDist = sim_func.get(clust.centroid,vectors[item_id]);	
+	int x_minDist = 0;
+	
+	for (int i = 1; i < num_of_clusters; i++){
+            clust = (Cluster)clusters.contents.elementAt(i);
+      	    double dist = sim_func.get(clust.centroid,vectors[item_id]);
+	    
+            if (dist < minDist){
+		x_minDist = i;
+		minDist = dist;
+	    }
+    	}
+	
+	//System.out.println("in " + x_minDist); 
+	clust = (Cluster)clusters.contents.elementAt(x_minDist);
+        clust.iter_items.addElement(new Integer(item_id));
+	clust.iter_items_num++;
+    }
+    
+    
+    // add all documents not yet added to the existing cluster hierarchy (which is based on the 
+    // limit first documents. Hence there are vector.length - limit docs to add
+    // consequently index runs from limit (start_index) to vector.length (end_index)
+    void clustering (ClusterArray clusters, int[] num_of_clusters, float[][] vectors, int start_index, int end_index,Distance sim_func)
+    {
+        int i;
+
+	System.out.println("Start: " + start_index + " Finish: " + end_index);
+	
+	if (start_index >= end_index)
+	    return;
+	
+	compute_cluster_centers(clusters, num_of_clusters[0], vectors);
+	
+	for (i = start_index; i < end_index; i++){
+	    System.out.println("Placing document " + i);
+	    place_item(clusters, num_of_clusters[0], vectors, i, sim_func);
+	}
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/LinkFunc.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/LinkFunc.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/LinkFunc.java	(revision 21054)
@@ -0,0 +1,8 @@
+package vishnu.cluster;
+
+//Abstract linkfunctions class
+
+public abstract class LinkFunc
+{
+	abstract float link(float a, float b);
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/MaxLink.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/MaxLink.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/MaxLink.java	(revision 21054)
@@ -0,0 +1,9 @@
+package vishnu.cluster;
+
+public class MaxLink extends LinkFunc
+{
+	public float link(float a, float b)
+	{
+  		return (a > b? a*99+b: b*99+a)/100;
+	}
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/MinLink.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/MinLink.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/MinLink.java	(revision 21054)
@@ -0,0 +1,9 @@
+package vishnu.cluster;
+
+public class MinLink extends LinkFunc
+{
+	public float link(float a, float b)
+	{
+  		return (a < b? a*999+b: b*999+a)/1000;
+	}
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/SimMatrix.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/SimMatrix.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/SimMatrix.java	(revision 21054)
@@ -0,0 +1,70 @@
+package vishnu.cluster;
+
+import java.util.*;
+
+public class SimMatrix
+{
+    
+    public float[] matrix;
+    
+
+    public SimMatrix(float[][] KDMatrix, int limit, Distance simfunc){
+	
+	float iarray[];
+	
+	int hitDocs = KDMatrix.length;
+	int wordCount = KDMatrix[0].length;
+	int index = 0;
+	
+	// to exclude diagonal and conjugates
+	// - - - - -
+	//         |
+	// *       |
+	// * *     |
+	// * * *   |    
+	
+	int realSize = (hitDocs * (hitDocs - 1)/2);
+	matrix = new float[realSize];
+	
+	// this gets the sim matrix for all docs rather 
+	// than only the first limit - mistake?
+
+	for(int i = 1; i < hitDocs; i++){
+	    iarray = KDMatrix[i];
+	    for(int j = 0; j < i; j++){
+		matrix[index] = simfunc.get(iarray,KDMatrix[j]);
+	        index++;
+	    }
+	}
+    }
+
+    public void printMatrix()
+    {
+	int countA = 0;
+	int countB = 0;
+
+	for( int i = 0; i < matrix.length; i++ ){
+	    System.out.print(matrix[i] + " ");
+	    if( countA >= countB ){
+		System.out.println();
+		countB++;
+		countA = 0;
+	    }
+	    else countA++;
+	}
+    }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/TheInt.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/TheInt.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/TheInt.java	(revision 21054)
@@ -0,0 +1,11 @@
+package vishnu.cluster;
+
+public class TheInt
+{
+	public int val;
+
+	TheInt(int x)
+	{
+		val = x;
+	}
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/TheVectors.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/TheVectors.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/TheVectors.java	(revision 21054)
@@ -0,0 +1,79 @@
+package vishnu.cluster;
+
+import java.io.*;
+import java.util.*;
+
+public class TheVectors
+{
+    public Vector theData;
+    public int size;
+    public Vector coding;
+    public int zeros;
+
+
+    TheVectors(String file, int VECTOR_SIZE)
+    {
+        theData= new Vector(VECTOR_SIZE);
+	coding= new Vector(VECTOR_SIZE);
+        int index,code =0, zerolist=0;
+	double ssum,value;
+    	try
+	{
+      	    FileReader inFile = new FileReader(file);
+      	    BufferedReader inLines = new BufferedReader(inFile);
+	    size=0;
+            String line = null, tokenString;
+	    do
+	    {
+
+		line = inLines.readLine();
+		if (line!= null)
+		{
+
+		  double[] row= new double[VECTOR_SIZE];
+ 					StringTokenizer tokens = new StringTokenizer(line," ");
+                  ssum=0.0;
+   		  for (index=0; index<VECTOR_SIZE; index++)
+		  {
+                      if(tokens.hasMoreTokens())
+                      {
+                          tokenString = tokens.nextToken();
+           	          value = Double.parseDouble(tokenString);
+		          row[index]= value;
+		          ssum+= value*value;
+                      }
+                      else
+                      {
+                          row[index]=0.0;
+                      }
+	          }
+	          if(index==0) continue;
+                  ssum=Math.sqrt(ssum);
+	          if(ssum!=0.0)
+	          {
+	              for(int i=0;i<index;i++)
+		      row[i]/=ssum;
+		      theData.addElement(row);
+		      coding.addElement(new TheInt(code));
+                      size++;
+		  }
+		  else
+		  {
+		      if(zerolist==0)
+		      {
+			//System.out.print("0 ");
+	                  zerolist=1;
+		      }
+		      System.out.print(code + " ");
+		  }
+	      }
+	      code++;
+	  }while(line != null);
+	}
+	catch(IOException e)
+	{
+	    System.err.println("IO Exception in read_vectors");
+	    e.printStackTrace();
+        }
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/cluster/TriangleIndex.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/cluster/TriangleIndex.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/cluster/TriangleIndex.java	(revision 21054)
@@ -0,0 +1,85 @@
+package vishnu.cluster;
+
+class TriangleIndex
+{
+	short [] i_ind;    /* from index to i */
+	short [] j_ind;    /* from index to j */
+
+
+
+	TriangleIndex(int matrix_rows)
+	{
+
+  		short i, j;
+        int theIndex;
+
+  		int num_of_elements = matrix_rows * (matrix_rows - 1) / 2;
+
+  		/* Allocate space for lookup tables */
+  		i_ind = new short[num_of_elements];
+  		j_ind = new short [num_of_elements];
+
+	  	for (i = 1; i < matrix_rows; i++)
+    	{
+      		for (j = 0; j < i; j++)
+			{
+	  			theIndex = TriangleIndex.index (i, j);
+				i_ind[theIndex] = i;
+	  			j_ind[theIndex] = j;
+			}
+    	}
+	}
+
+	int row_size (int size)
+	{
+  /* Don't forget that we actually start counting in the second row, since the first one is assumed to be all zeroes */
+  		int i = 1;
+  		int sum = 0;
+
+  		while (sum != size)
+    		sum += i++;
+  		return i;
+	}
+
+	static int index (int i, int j)
+	{
+  /* Assumes lower triangular matrix, i.e. i>j */
+
+  		int temp;
+
+  /* The matrix in this case is symmetric so (i, j) = (j, i) */
+  		if (i < j)
+    	{
+      		temp = i; i = j; j = temp;
+    	}
+
+  		if (i != j)
+    		return (i*i-i)/2 + j;
+    		//(i - 1) * (i - 2) / 2 + i - 1 + j;
+  		else         /* The elements on the diagonal are all 0 */
+    	{
+      		return -1; /* so just return the dummy value -1      */
+    	}
+	}
+	static void print (double[] matrix, int row_size)
+	{
+  		int i, j, theIndex;
+
+  		for (i = 0; i < row_size; i++)
+    	{
+      		for (j = 0; j < row_size; j++)
+			{
+	  			if (i <= j)
+	    			System.out.print(" ");
+	  			else
+	    		{
+	      			theIndex = index (i, j);
+	      			System.out.print(matrix[theIndex] + " ");
+	    		}
+			}
+      		System.out.println();
+    	}
+	}
+
+}
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/datablock/Collection.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/datablock/Collection.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/datablock/Collection.java	(revision 21054)
@@ -0,0 +1,23 @@
+package vishnu.datablock;
+
+import java.io.*;
+
+public class Collection implements Serializable
+{
+    public String dir;
+    public String name;
+    public String desc;
+    public int size;
+    public String engine;
+    public String [] views;
+    
+    public Collection(String di, String n, String d, int s, String e, String [] v)
+    {
+	dir = di;
+	name = n; 
+	desc = d; 
+	size = s;
+	engine = e;
+	views = v;
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/datablock/DataBlock.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/datablock/DataBlock.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/datablock/DataBlock.java	(revision 21054)
@@ -0,0 +1,35 @@
+package vishnu.datablock;
+
+import java.io.*;
+import java.util.*;
+
+
+
+public class DataBlock implements Serializable
+{
+    public int [] docs = null;
+    public String [] descriptions = null;
+    public String [] words = null;
+    public SparseMatrix matrix = null;
+    public Vector [] clusters = null;
+    public Point2D [] sammon = null;
+
+    public DataBlock() {
+    }
+    public DataBlock(int[] docs,
+		     String [] descriptions,
+		     String [] words,
+		     SparseMatrix matrix,
+		     Vector [] clusters,
+		     Point2D [] sammon)
+    {
+	this.docs = docs;
+	this.descriptions = descriptions;
+        this.words = words;
+	this.matrix = matrix;
+	this.clusters = clusters;
+	this.sammon = sammon;
+    }
+    
+}
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/datablock/Node.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/datablock/Node.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/datablock/Node.java	(revision 21054)
@@ -0,0 +1,37 @@
+package vishnu.datablock;
+
+public class Node
+{
+    Node next;
+    int value;
+    
+    public Node(int i, Node n)
+    {
+        value = i;
+        next = n;
+    }
+    
+    public Node(int i)
+    {
+        value = i;
+        next = null;
+    }
+    
+    public Node push(Node n)
+	{
+        if (next != null) next.push(n);
+        else next = n;
+        return this;
+    }
+
+	public Node pop()
+	{
+		return next;
+	}
+	
+	public int getValue()
+	{
+	    return value;
+	}
+  
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/datablock/Point2D.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/datablock/Point2D.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/datablock/Point2D.java	(revision 21054)
@@ -0,0 +1,21 @@
+package vishnu.datablock;
+import java.io.*;
+/** 
+* Point2D.java: Class for points in logical coordinates.
+* From Section 1.5 of
+* @author Ammeraal, L. (1998) Computer Graphics for Java Programmers,
+* Chichester: John Wiley.
+*/       
+public class Point2D  implements Serializable
+{
+   /** x cartesian coordinate */
+   public float x;
+   /** y cartesian coordinate */
+   public float y;
+   /**
+   * constructor
+   * @param cartesian x coordinate
+   * @param cartesian y coordinate
+   */
+   public Point2D(float x, float y){this.x = x; this.y = y;}
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/datablock/Query.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/datablock/Query.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/datablock/Query.java	(revision 21054)
@@ -0,0 +1,69 @@
+/*********************************************************************
+ Copyright (C) 2003 Imperial College London
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+***********************************************************************/
+
+
+package vishnu.datablock;
+import java.io.*;
+
+public class Query implements Serializable
+{
+    public String query = null;
+    public String collection = null;
+    public int docNum = -1;
+    public String action = null;
+
+    public Query(){}
+
+    public Query(String q, String c, String s)
+    {
+	query = q;
+	collection = c;
+    }
+
+    public void setCollection(String c)
+    {
+	collection = c;
+    }
+
+    public void setDocNum(int id)
+    {
+	docNum = id;
+    }
+
+    public void setQuery(String q)
+    {
+	query = q;
+    }
+
+    public void setAction(String a)
+    {
+	action = a;
+    }
+
+}
+
+
+
+
+
+
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/datablock/ShortFileException.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/datablock/ShortFileException.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/datablock/ShortFileException.java	(revision 21054)
@@ -0,0 +1,6 @@
+package vishnu.datablock;
+public class ShortFileException extends Exception
+{
+    public ShortFileException(){super();}
+    public ShortFileException(String s){super(s);}
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/datablock/SparseMatrix.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/datablock/SparseMatrix.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/datablock/SparseMatrix.java	(revision 21054)
@@ -0,0 +1,81 @@
+package vishnu.datablock;
+import java.io.*;
+
+public class SparseMatrix implements Serializable
+{
+    private SparseNode [] nodes;
+    private int n_cols;
+    private int n_rows;
+    
+    public SparseMatrix(int rows, int cols)
+    {
+        n_rows = rows;
+	n_cols = cols;
+	
+        nodes = new SparseNode[n_rows];
+	
+        for (int c = 0; c < n_rows; c++)
+	    nodes[c] = null;
+    }
+    
+    
+    public void addDataItem(int row, int column, float value)
+    {
+	SparseNode node; 
+	node = new SparseNode(value, column);
+	nodes[row] = node.push(nodes[row]);
+    }
+    
+    
+    public SparseNode getRow(int row)
+    {
+	return nodes[row];
+    }
+
+
+    public int getRowCount()
+    {
+	    return n_rows;
+    }
+
+
+    public int getColumnCount()
+    {
+	    return n_cols;
+    }
+    
+
+    public float[][] getExpanded()
+    {
+        float[][] full = new float[n_rows][n_cols];
+	
+	SparseNode n;
+	
+	for(int r = 0; r < n_rows; r++){
+	    for (int c = 0; c < n_cols; c++)
+		full[r][c]=0;
+	    
+	    n=nodes[r];
+	    
+	    while(n!= null){
+		full[r][n.getColumn()]=n.getValue();
+		n=n.getNext();
+	    }
+	}
+	return full;
+    }
+    
+    
+    public void print()
+    {
+	for( int i = 0; i < nodes.length; i++ ){
+	    SparseNode n = getRow(i);
+	    while(n != null ){
+		System.out.print(n.getColumn()+ " ");
+		n = n.getNext();
+	    }
+	    System.out.println();
+	}
+    }
+}
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/datablock/SparseNode.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/datablock/SparseNode.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/datablock/SparseNode.java	(revision 21054)
@@ -0,0 +1,59 @@
+package vishnu.datablock;
+import java.io.*;
+
+public class SparseNode implements Serializable
+{
+    private float value;
+    
+    private int column;
+    private SparseNode next;
+    
+    
+    SparseNode(float f, int c)
+    {
+	value = f;
+	column = c;
+	next = null;
+    }
+    
+    
+    SparseNode(float f, int c, SparseNode n)
+    {
+	value = f;
+	column = c;
+	next = n;
+    }
+    
+
+    public SparseNode push(SparseNode n)
+    {
+        if (next != null) next.push(n);
+        else next = n;
+        return this;
+    }
+
+
+    public SparseNode pop()
+    {
+	return next;
+    }
+
+    
+    public SparseNode getNext()
+    {
+        return next;
+    }
+
+    
+    public int getColumn()
+    {
+        return column;
+    }
+    
+
+    public float getValue()
+    {
+        return value;
+    }
+}
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/server/CKServer.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/server/CKServer.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/server/CKServer.java	(revision 21054)
@@ -0,0 +1,293 @@
+package vishnu.server;
+
+import java.io.*;
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.Enumeration;
+import vishnu.util.Misc;
+import vishnu.util.Sammon;
+import vishnu.cluster.*;
+import vishnu.datablock.*;
+
+public class CKServer 
+{
+    private String index_directory = null;
+
+    private final static String FILE_DOC_FREQ = "doc_freq";
+    private final static String FILE_TERM_FREQ = "term_freq";
+    private final static String FILE_CK_WORDS = "ck_words";
+    
+    private float[] docFreq = null;
+    private String[] ck_words = null;
+    private Vector termFreq = null;
+
+
+    private int[] x_keywords;
+    private int[] x_hitdocs;
+    private float[][] KDMatrix;
+    private Vector[] clusters;
+    private double[][] centroids;
+    private Point2D[] sammon;
+    private Vector descriptions;
+    
+    public CKServer(String collection_home, String view) {
+	
+	this.index_directory = collection_home;
+	if (!this.index_directory.endsWith(File.separator)) {
+	    this.index_directory += File.separator;
+	}
+	this.index_directory += "index" + File.separator + "ck_index" + File.separator;
+	if (view != null) {
+	    this.index_directory += view + File.separator;
+	}
+	    
+	loadIndices();
+    }
+    
+    private void loadIndices(){
+	FileInputStream fis = null;
+	ObjectInputStream in = null;
+	
+	try {
+	    docFreq = (float[])getObject(this.index_directory  + FILE_DOC_FREQ);
+	    ck_words = (String[])getObject(this.index_directory + FILE_CK_WORDS);
+	    termFreq = (Vector)getObject(this.index_directory + FILE_TERM_FREQ);
+	} catch (Exception e) {
+	    System.err.println("CKServer Error: Couldn't load up ck index data: "+e);
+	}
+    }
+
+    
+    private Object getObject(String str)
+	throws IOException, FileNotFoundException, ClassNotFoundException {
+	Object o = null;
+	FileInputStream fis = null;
+	ObjectInputStream in = null;
+	fis = new FileInputStream(str);
+	in = new ObjectInputStream(fis);
+	o = in.readObject();
+	in.close();
+	return o;
+    }
+
+
+    public void setDescriptions(Vector desc)
+    {
+	descriptions = desc;
+    }
+    
+
+    public String[] computeKeywords(Vector docNum)
+    {
+	int hits = docNum.size();
+	
+	Hashtable keyHash = new Hashtable();
+	
+	int count_ck_all = 0;
+	
+	for( int i = 0; i < hits; i++ ){
+	    
+	    int doc_index = ((Integer)docNum.elementAt(i)).intValue();
+	    Hashtable ht = (Hashtable)termFreq.elementAt(doc_index);
+	    count_ck_all += ht.size();
+	    
+	    Enumeration enumer = ht.keys();
+	    while( enumer.hasMoreElements() ){
+		Integer term_index = (Integer)enumer.nextElement();
+		
+		Integer set_freq;
+		if( (set_freq = (Integer)keyHash.get(term_index)) == null ){
+		    keyHash.put(term_index,new Integer(1));
+		}else{
+		    keyHash.put(term_index,new Integer(set_freq.intValue()+1));
+		}
+	    }
+	}
+	
+
+	/**** compute interesting weight for each keyword ****/
+	
+	Enumeration enumer = keyHash.keys();
+	
+	float[] weight_ck = new float[keyHash.size()];
+	Integer[] weight_index = new Integer[keyHash.size()];
+	int count = 0;
+	
+	while( enumer.hasMoreElements() ){
+	    Integer index_ck = (Integer)enumer.nextElement();
+	    Integer set_freq = (Integer)keyHash.get(index_ck);
+	    
+	    float set_freq_f = (float)set_freq.intValue()/hits;
+	    
+	    float f1 = (float)set_freq_f/docFreq[index_ck.intValue()];
+	    float f2 = -(float)Math.log(set_freq_f);
+	    
+	    weight_index[count] = index_ck;
+	    weight_ck[count] = - f1 * f2;
+	    
+	    count++;
+	}
+	
+	Misc.quickSort(weight_ck,weight_index);
+	
+	
+	/**** store indices of up to 100 top ranked keywords ****/
+	
+	int wordCount = Math.min(100,count);
+	int[] indices_kw = new int[wordCount];
+	
+	for( int i = 0; i < wordCount; i++ ){
+	    indices_kw[i] = weight_index[i].intValue();
+	    
+	    float weight = -weight_ck[i];
+	    String ck = ck_words[indices_kw[i]];
+	}
+	
+	
+	/**** sort the indices_kw array in increasing order of indices ****/
+	
+	Misc.quickSort(indices_kw);
+
+	x_keywords = indices_kw;
+	
+	String[] keywords = new String[x_keywords.length];
+	for( int i = 0; i < x_keywords.length; i++ ){
+	    keywords[i] = ck_words[x_keywords[i]];
+	}
+
+	return keywords;
+    }
+    
+    
+    public SparseMatrix getSparseMatrix(Vector docNums){
+		
+	int hits = docNums.size();
+	int wordCount = x_keywords.length;
+	int scale = 100; // length of document vector
+	
+
+	/**** build full matrix: rows indices_kw (wordCount), col docNum (hits) ****/
+	
+	float[][] matrix_kd = new float[hits][wordCount];
+	float[] sum_rows = new float[hits];
+	int emptyRows = 0;
+	
+	for( int i = 0; i < hits; i++ ){
+	    
+	    int doc_index = ((Integer)docNums.elementAt(i)).intValue();
+	    Hashtable ht = (Hashtable)termFreq.elementAt(doc_index);
+	    
+	    for( int j = 0; j < wordCount; j++ ){
+		
+		Integer term_freq;
+		
+		if( (term_freq = (Integer)ht.get(new Integer(x_keywords[j]))) != null ){
+		    float w = -(float)(Math.log(1+term_freq.intValue()) * Math.log(docFreq[x_keywords[j]]));
+		    w = term_freq.intValue();
+		    matrix_kd[i][j] = w;
+		    sum_rows[i] += w*w;
+		}
+	    }
+	    
+	    
+	    /**** sqrt sum to obtain |z| ****/
+	    
+	    if( sum_rows[i] != 0 ) sum_rows[i] = (float)Math.sqrt(sum_rows[i])/scale; else emptyRows++;
+	}
+	
+	/**** populate sparseMatrix and full matrix dividing each entry by the row_sum ****/
+	
+	SparseMatrix matrix = new SparseMatrix(hits-emptyRows,wordCount);
+	KDMatrix = new float[hits-emptyRows][wordCount];
+		
+	int ci = 0;
+
+	for( int i = hits-1; i >= 0; i-- ){
+	    if( sum_rows[i] == 0 ){
+		docNums.removeElementAt(i);
+		descriptions.removeElementAt(i);
+	    }
+	}
+	
+	for( int i = 0; i < hits; i++ ){
+	    
+	    if( sum_rows[i] == 0 ) continue;
+	    
+	    for( int j = 0; j < wordCount; j++ ){
+		
+		float m = matrix_kd[i][j];
+		
+		if( m != 0 ){
+		    float norm_m = m; //(float)m/sum_rows[i];
+		    matrix.addDataItem(ci, j, norm_m);
+		    KDMatrix[ci][j] = norm_m; //(float)(norm_m);
+		}
+		else KDMatrix[ci][j] = 0;
+	    }
+	    ci++;
+	}
+	
+	x_hitdocs = new int[docNums.size()];
+	
+	for( int i = 0; i < docNums.size(); i++ ){
+	    x_hitdocs[i] = ((Integer)docNums.elementAt(i)).intValue();
+	}
+	
+	return matrix;
+    }
+
+
+    public Vector[] getClusters(){
+
+	int wordCount = KDMatrix[0].length;
+	int docHits = KDMatrix.length;
+	System.err.println("getClusters: word count = "+wordCount+", docHits = "+docHits);
+        int clusterNum =  docHits>20?10:docHits;
+      
+        int limit =  docHits/2;
+	
+        Clustering clustering = new Clustering(Clustering.COMPLETE_LINKAGE,KDMatrix,1,clusterNum,limit);
+	
+        clusters = clustering.getClusters();
+	centroids= clustering.getCentroids();
+	
+	// what are these for??
+	//Sammon map = new Sammon(centroids);
+	//Point2D [] sammon = map.getMapping();
+	return clusters;
+    }
+    
+    
+    public int[] getHitDocuments()
+    {
+	return x_hitdocs;
+    }
+
+
+    public double[][] getCentroids()
+    {
+	return centroids;
+    }
+
+
+    public String[] getHitDescriptions()
+    {
+	String[] desc = new String[descriptions.size()];
+	for( int i = 0; i < desc.length; i++ ){
+	    desc[i] = (String)descriptions.elementAt(i);
+	}
+
+	return desc;
+    }
+    
+    public Point2D[] getSammonMap(double[][] centroids)
+    {
+	if (centroids == null) {
+	    return null;
+	}
+	Sammon sammon = new Sammon(centroids);
+	Point2D[] p = sammon.getMapping();
+	
+	return p;
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/server/CKTester.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/server/CKTester.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/server/CKTester.java	(revision 21054)
@@ -0,0 +1,230 @@
+package vishnu.server;
+
+import java.io.*;
+import java.util.*;
+import vishnu.server.Search.*;
+import vishnu.util.*;
+import vishnu.datablock.*;
+
+public class CKTester
+{
+
+    public static void main(String[] args){
+	CKTester ckt = new CKTester(args);
+    }
+
+    
+    public CKTester(String[] args){
+	
+	Vector docNum = new Vector();
+	Vector descriptions = new Vector();
+	
+	String query = args[0];
+	String coll = args[1];
+	String vis_home = args[2];
+	String view = null;
+	if (args.length >= 4) {
+	    view = args[3];
+	}
+
+	/**** read in ck words, doc freq and term freq ****/
+
+	FileInputStream fis = null;
+	ObjectInputStream in = null;
+	
+	String root = vis_home;
+	if (!root.endsWith(File.separator)) {
+	    root += File.separator;
+	}
+	root += "collections" + File.separator+ coll + File.separator + "index" + File.separator + "ck_index" + File.separator;
+	if (view != null) {
+	    root += view+File.separator;
+	}
+	String fileDocFreq = "doc_freq";
+	String fileTermFreq = "term_freq";
+	String fileCKWords = "ck_words";
+	
+	float[] docFreq = (float[])getObject(root+fileDocFreq);
+	String[] ck_words = (String[])getObject(root + fileCKWords);
+	Vector termFreq = (Vector)getObject(root + fileTermFreq);
+
+
+	/**** get relevant docs ****/
+
+	LUCSearcher lus = new LUCSearcher(coll, view); // coll should be fullpath
+	//lus.setRootDirectory(vis_home);
+	lus.search(query);
+	docNum = lus.getDocIdentifiers();
+	descriptions = lus.getDocDescriptions();
+	
+	int hits = docNum.size();
+	
+
+	/**** pool the candidate keywords and note their frequencies ****/
+	
+	Hashtable keyHash = new Hashtable();
+	
+	int count_ck_all = 0;
+
+	for( int i = 0; i < hits; i++ ){
+	    
+	    int doc_index = ((Integer)docNum.elementAt(i)).intValue();
+	    Hashtable ht = (Hashtable)termFreq.elementAt(doc_index);
+	    count_ck_all += ht.size();
+	    
+	    Enumeration enumer = ht.keys();
+	    while( enumer.hasMoreElements() ){
+		Integer term_index = (Integer)enumer.nextElement();
+		
+		Integer set_freq;
+		if( (set_freq = (Integer)keyHash.get(term_index)) == null ){
+		    keyHash.put(term_index,new Integer(1));
+		}else{
+		    keyHash.put(term_index,new Integer(set_freq.intValue()+1));
+		}
+	    }
+	}
+
+	System.out.println("all keywords: " + count_ck_all);
+	System.out.println("pooled: " + keyHash.size());
+
+
+	/**** compute interesting weight for each keyword ****/
+
+	Enumeration enumer = keyHash.keys();
+
+	float[] weight_ck = new float[keyHash.size()];
+	Integer[] weight_index = new Integer[keyHash.size()];
+	int count = 0;
+
+	while( enumer.hasMoreElements() ){
+	    Integer index_ck = (Integer)enumer.nextElement();
+	    Integer set_freq = (Integer)keyHash.get(index_ck);
+	    
+	    float set_freq_f = (float)set_freq.intValue()/hits;
+
+	    float f1 = (float)set_freq_f/docFreq[index_ck.intValue()];
+	    float f2 = -(float)Math.log(set_freq_f);
+
+	    weight_index[count] = index_ck;
+	    weight_ck[count] = - f1 * f2;
+
+	    //System.out.println(set_freq_f+" "+docFreq[index_ck.intValue()]+" "+weight_ck[count]);
+	    count++;
+	}
+	
+	Misc.quickSort(weight_ck,weight_index);
+
+
+	/**** store indices of up to 100 top ranked keywords ****/
+
+	int wordCount = Math.min(100,count);
+	int[] indices_kw = new int[wordCount];
+	
+	for( int i = 0; i < wordCount; i++ ){
+	    indices_kw[i] = weight_index[i].intValue();
+	    
+	    float weight = -weight_ck[i];
+	    String ck = ck_words[indices_kw[i]];
+	    System.out.println(indices_kw[i] + " " + ck + " " + weight + " ");
+	}
+
+
+	/**** sort the indices_kw array in increasing order of indices ****/
+
+	Misc.quickSort(indices_kw);
+
+
+	/**** build full matrix: rows indices_kw (wordCount), col docNum (hits) ****/
+
+	float[][] matrix_kd = new float[hits][wordCount];
+	float[] sum_rows = new float[hits];
+	int emptyRows = 0;
+
+	for( int i = 0; i < hits; i++ ){
+	    
+	    int doc_index = ((Integer)docNum.elementAt(i)).intValue();
+	    Hashtable ht = (Hashtable)termFreq.elementAt(doc_index);
+	    
+	    for( int j = 0; j < wordCount; j++ ){
+		
+		Integer term_freq;
+		
+		if( (term_freq = (Integer)ht.get(new Integer(indices_kw[j]))) != null ){
+		    float w = -(float)(Math.log(1+term_freq.intValue()) * Math.log(docFreq[indices_kw[j]]));
+		    matrix_kd[i][j] = w;
+		    sum_rows[i] += w*w;
+		}
+		System.out.print(matrix_kd[i][j] + " ");
+	    }
+	    
+	    System.out.println();
+	    
+
+	    /**** sqrt sum to obtain |z| ****/
+
+	    if( sum_rows[i] != 0 ) sum_rows[i] = (float)Math.sqrt(sum_rows[i]); else emptyRows++;
+	}
+	
+	/**** populate sparseMatrix dividing each entry by the row_sum ****/
+	
+	SparseMatrix matrix = new SparseMatrix(hits-emptyRows,wordCount);
+
+	int ci = 0;
+	
+	for( int i = 0; i < hits; i++ ){
+	    
+	    if( sum_rows[i] == 0 )
+		continue;
+	    
+	    for( int j = 0; j < wordCount; j++ ){
+		
+		float m = matrix_kd[i][j];
+		
+		if( m != 0 ){
+		    float norm_m = (float)m/sum_rows[i];
+		    matrix.addDataItem(ci, j, norm_m);
+		    System.out.print(j + " (" + norm_m + ") ");
+		}
+	    }
+
+	    ci++;
+	    System.out.println();
+	}
+    }
+    
+
+
+    private Object getObject(String str){
+	Object o = null;
+	FileInputStream fis = null;
+	ObjectInputStream in = null;
+	try{
+	    fis = new FileInputStream(str);
+	    in = new ObjectInputStream(fis);
+	    o = in.readObject();
+	    in.close();
+	}
+	catch(IOException ex){
+	    ex.printStackTrace();
+	}
+	catch( ClassNotFoundException ex){
+	    ex.printStackTrace();
+	}
+	return o;
+    }
+}
+
+
+class Node{
+    Node next = null;
+    float value = -1;
+    int docIndex = -1;
+    int kwIndex = -1;
+
+    public Node(int d, int k)
+    {
+	docIndex = d;
+	kwIndex = k;
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/server/Packaging.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/server/Packaging.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/server/Packaging.java	(revision 21054)
@@ -0,0 +1,376 @@
+package vishnu.server;
+
+/*********************************************************************
+ Copyright (C) 2003 Imperial College London
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+***********************************************************************/
+
+//import org.apache.xpath.XPathAPI;
+import org.w3c.dom.Node; 
+import org.w3c.dom.NodeList; 
+import org.w3c.dom.Document; 
+import org.w3c.dom.Element; 
+import org.xml.sax.InputSource;
+import javax.xml.parsers.*;
+
+import java.io.*;
+import java.util.*;
+import vishnu.datablock.*;
+import java.awt.*;
+
+public class Packaging
+{
+    static Document doc = null;
+    static boolean out = false;
+
+    static String matrixString = "";
+
+    public Packaging()
+    {
+    }
+
+
+    public static int[] inInts(StringTokenizer st)
+    {
+	String line;
+	int intCount;
+
+	if (!st.hasMoreTokens()) {
+	    return new int[0];
+	}
+
+	intCount = Integer.parseInt(st.nextToken());
+	if(out) System.out.println(intCount);
+
+	int[] intarray = new int[intCount];
+	for (int c = 0; c < intCount;c++)
+	    {
+		line = st.nextToken();
+		int n = Integer.parseInt(line);
+		intarray[c] = n;
+		if(out) System.out.println(n);
+	    }
+	return intarray;
+    }		
+    
+    
+    public static String [] inStrings(StringTokenizer st)
+    {
+	String line;
+	String [] instrings;
+	int strCount;
+
+	if (!st.hasMoreTokens()) {
+	    return new String[0];
+	}
+	
+	strCount = Integer.parseInt(st.nextToken());
+	if(out)	System.out.println(strCount);
+	   
+	instrings = new String[strCount];
+	for (int c = 0; c < strCount;c++)
+	    {
+		instrings[c] = st.nextToken();
+	    }
+
+	return instrings;
+    }
+
+    public static SparseMatrix inMatrix(StringTokenizer st, int wordCount)
+    {
+	String line;
+	int rowCount,column,value;
+
+	if (!st.hasMoreTokens()) {
+	    return new SparseMatrix(0, wordCount);
+	}
+
+	rowCount = Integer.parseInt(st.nextToken());
+	matrixString += String.valueOf(rowCount) + "\n";
+	if(out) System.out.println(rowCount);
+	
+	SparseMatrix matrix = new SparseMatrix(rowCount,wordCount);
+	
+	for (int c = 0; c < rowCount;c++)
+	    {
+		line = st.nextToken();
+		matrixString += line + "\n";
+
+		if(out) System.out.println(line);
+		
+		if(line.length() > 1)
+		    {
+			StringTokenizer tokens = new StringTokenizer(line,",");
+			
+			while(tokens.hasMoreTokens())
+			    {
+				String tokenString = tokens.nextToken();
+				value = Integer.parseInt(tokenString);
+				
+				if(tokens.hasMoreTokens())
+				    { 
+					tokenString = tokens.nextToken();
+					column = Integer.parseInt(tokenString);
+					matrix.addDataItem(c, column, value);
+				    }
+			    }// while				
+		    } //if
+	    } // for 
+	return matrix;
+    } // inMatrix
+
+    
+    public static Vector [] inCluster(StringTokenizer st, int docCount)
+    {
+	String line;
+	int clusterCount,value;
+	
+	if (!st.hasMoreTokens()) {
+	    return new Vector[0];
+	}
+	clusterCount = Integer.parseInt(st.nextToken());
+	if(out)	System.out.println(clusterCount);
+	Vector [] clusters = new Vector[clusterCount];
+	
+	for (int c = 0; c < clusterCount;c++)
+	    {
+		Vector v = new Vector();
+
+		line = st.nextToken();
+		if(out) System.out.println(line);
+		if (line.length()>0)
+		    {
+			StringTokenizer tokens = new StringTokenizer(line,",");
+			
+			while(tokens.hasMoreTokens())
+			    {
+				String tokenString = tokens.nextToken();
+				
+				value = Integer.parseInt(tokenString);
+				if(value >= 0 && value <= docCount)
+				    {
+					v.addElement(new Integer(value));
+				    } // if
+			    } // while
+		    } // if
+		
+		clusters[c]=v;
+	    } // for
+	return clusters;
+    }
+ 
+
+    public static Point2D[] inPoint2D(StringTokenizer st)
+    {
+	String line;
+	int count ;
+	float x = 0,y = 0;
+	
+	if (!st.hasMoreTokens()) {
+	    return new Point2D[0];
+	}
+	
+	count = Integer.parseInt(st.nextToken());
+	if(out) System.out.println(count);
+	
+	Point2D [] array = new Point2D[count];
+	
+	for (int c = 0; c < count;c++)
+	    {
+		line = st.nextToken();
+		if(out)	System.out.println(line);
+		StringTokenizer tokens = new StringTokenizer(line,",");
+
+		if(tokens.hasMoreTokens())
+		    {
+			String tokenString = tokens.nextToken();
+			x = Float.parseFloat(tokenString);
+		    }
+			
+		if(tokens.hasMoreTokens())
+		    {
+			String tokenString = tokens.nextToken();
+			y = Float.parseFloat(tokenString);
+		    } // if
+		    		
+		array[c]= new Point2D(x,y);
+	    } // for
+	return array;
+    }
+
+ 
+ public Element getXMLPackage(String result, Vector desc) 
+    {
+	String [] words;
+	int [] docs;
+	String [] descriptions;
+	SparseMatrix matrix;
+	Vector [] clusters;
+	Point2D [] sammon;
+
+	StringTokenizer st = new StringTokenizer(result);
+
+	docs = inInts(st);
+	words = inStrings(st);
+	matrix = inMatrix(st,words.length);
+	clusters = inCluster(st, docs.length);
+	sammon = inPoint2D(st);	
+	descriptions = new String[docs.length];
+
+	Element response = doc.createElement("response");
+	response.setAttribute("from","Packaging");
+	
+	Element docList = doc.createElement("docNodeList");
+	response.appendChild(docList);
+	for( int i = 0; i < docs.length; i++ )
+	    {
+		Element node = doc.createElement("docNode");
+		node.setAttribute("docNum", String.valueOf(docs[i]));
+		node.setAttribute("title",(String)desc.elementAt(i));
+		docList.appendChild(node);
+	    }
+
+	Element sammonList = doc.createElement("sammonList");
+	response.appendChild(sammonList);
+	for( int i = 0; i < sammon.length; i++ )
+	    {
+		float x = sammon[i].x;
+		float y = sammon[i].y;		
+		Element node = doc.createElement("sammonNode");
+		node.setAttribute("xpos",String.valueOf(x));
+		node.setAttribute("ypos",String.valueOf(y));
+		sammonList.appendChild(node);
+	    }
+
+	Element wordList = doc.createElement("wordList");
+	response.appendChild(wordList);
+	for( int i = 0; i < words.length; i++ )
+	    {
+		Element node = doc.createElement("wordNode");
+		node.setAttribute("term",words[i]);
+		wordList.appendChild(node);
+	    }
+
+	Element mat = doc.createElement("DocTermSparseMatrix");
+	response.appendChild(mat);
+
+	st = new StringTokenizer(matrixString);
+	String line;
+	int rowCount,column,value;
+
+	rowCount = Integer.parseInt(st.nextToken());
+	mat.setAttribute("rows",String.valueOf(rowCount));
+	mat.setAttribute("cols",String.valueOf(words.length));
+
+	Element rowList = doc.createElement("rowList");
+	mat.appendChild(rowList);
+
+	for (int c = 0; c < rowCount;c++)
+	    {
+		line = st.nextToken();
+		if(line.length() > 1)
+		    {
+			Element colList = doc.createElement("colList");
+			rowList.appendChild(colList);
+			colList.setAttribute("row",String.valueOf(c));
+			
+			StringTokenizer tokens = new StringTokenizer(line,",");
+			
+			while(tokens.hasMoreTokens())
+			    {
+				Element entryElem = doc.createElement("entry");
+				colList.appendChild(entryElem);
+
+				String tokenString = tokens.nextToken();
+				value = Integer.parseInt(tokenString);
+
+				entryElem.setAttribute("docFreq",tokenString);
+				
+				if(tokens.hasMoreTokens())
+				    { 
+					tokenString = tokens.nextToken();
+					column = Integer.parseInt(tokenString);
+					entryElem.setAttribute("term",tokenString);
+				    }
+			    }// while				
+		    } //if
+	    } // for 
+
+	Element clusterList = doc.createElement("ClusterList");
+
+	response.appendChild(clusterList);
+
+	for( int i = 0; i < clusters.length; i++ )
+	    {
+		Element cluster = doc.createElement("Cluster");
+		clusterList.appendChild(cluster);
+
+		Vector vector = clusters[i];
+		for( int c = 0; c < vector.size(); c++ )
+		    {
+			Element entry = doc.createElement("leaf");
+			cluster.appendChild(entry);
+			
+			Integer I = (Integer)vector.elementAt(c);
+			entry.setAttribute("docNum",String.valueOf(I.intValue()));
+		    }
+	    }
+
+	for( int i = 0; i < docs.length; i++ ){
+            descriptions[i] = (String)desc.elementAt(i);
+	}
+
+	return response;
+    }
+
+
+   
+    public DataBlock getDataPackage(String result, Vector desc) 
+    {
+	String [] words;
+	int [] docs;
+	String [] descriptions;
+	SparseMatrix matrix;
+	Vector [] clusters;
+	Point2D [] sammon;
+
+	StringTokenizer st = new StringTokenizer(result);
+
+	docs = inInts(st);
+	words = inStrings(st);
+	matrix = inMatrix(st,words.length);
+	clusters = inCluster(st, docs.length);
+	sammon = inPoint2D(st);	
+	descriptions = new String[docs.length];
+
+	for( int i = 0; i < docs.length; i++ )
+	    {
+		descriptions[i] = (String)desc.elementAt(i);
+		//descriptions[i]	= "doc"+Integer.toString(i);
+	    }
+
+	DataBlock data = new DataBlock(docs,descriptions,words,matrix,clusters,sammon); 
+	return data;
+    }
+
+}
+
+
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/server/Search/LUCSearcher.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/server/Search/LUCSearcher.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/server/Search/LUCSearcher.java	(revision 21054)
@@ -0,0 +1,173 @@
+/*********************************************************************
+ Copyright (C) 2003 Imperial College London
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+***********************************************************************/
+
+package vishnu.server.Search;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.search.Searcher;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Hits;
+import org.apache.lucene.queryParser.QueryParser;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+
+
+import java.util.Vector;
+import java.util.Hashtable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+
+public class LUCSearcher implements SearchInterface
+{
+    protected Document doc=null;
+    private String collection_home;
+    private String index_dir;
+    private Vector docIDs;
+    private Vector docDescs;
+    private Hits hits;
+    private Hashtable ht;
+    
+    public LUCSearcher(String coll_home, String view)
+    {
+	collection_home = coll_home;
+	if (!collection_home.endsWith(File.separator)) {
+	    collection_home += File.separator;
+	}
+	index_dir = collection_home + File.separator + "index" + 
+	    File.separator + "luc_index" + File.separator;
+	if (view != null) {
+	    index_dir += view + File.separator;
+	}
+    }
+
+
+    public Vector getDocIdentifiers()
+    {
+	return docIDs;
+    }
+    
+    
+    public Vector getDocDescriptions()
+    {
+	return docDescs;
+    }	
+    
+    
+    public Vector getDocContent(int doc_num)
+    {
+	System.err.println("in doc content, doc num="+doc_num);
+	Integer in = null;
+	if (ht==null) {
+	    System.err.println("ht = null");
+	}
+	try {
+	    in = (Integer)ht.get(new Integer(doc_num));
+	} catch (Exception e) {
+	    System.err.println("exception happened: "+e);
+	}
+	System.err.println("*");
+	int id = 1;
+	System.err.println("*");
+	if( in != null ) id = in.intValue();
+	System.err.println("hit id ="+id);
+	Vector v = new Vector();
+
+	try {
+	    Directory directory = FSDirectory.getDirectory(new File(index_dir), false);
+
+	    IndexReader reader = IndexReader.open(directory);
+	    
+	    Document doc = reader.document(doc_num);
+	    String path = doc.get("path");
+	    // make sure the slashes go the right way
+	    path = path.replaceAll("[\\\\\\/]", File.separator);
+	    String full_path = collection_home + path;
+	    File f = new File(full_path);
+	    BufferedReader dis = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
+	    String line;
+	    
+	    while( (line=dis.readLine()) != null ){
+		v.addElement(line);
+	    }
+	    dis.close();
+	} catch(IOException ex){v.addElement("Content of this document currently unavailable");}    
+	return v;
+    }
+
+    
+    public void search(String q)
+    {
+	docIDs = new Vector();
+	docDescs = new Vector();
+	ht = new Hashtable();
+	
+	try {
+	    Searcher searcher = new IndexSearcher(index_dir);
+	    Analyzer analyzer = new StandardAnalyzer();
+	    
+	    Query query = QueryParser.parse(q, "contents", analyzer);
+	    System.out.println("Searching for: " + query.toString("contents") + " in " + index_dir);
+	    
+	    hits = searcher.search(query);
+	    System.out.println(hits.length() + " total matching documents");
+	    int max_docs = Math.min(MAX_DOCS,hits.length());
+	    for (int i = 0; i < max_docs; i++) {
+
+		int id = hits.id(i);
+		ht.put(new Integer(id),new Integer(i));
+		docIDs.addElement(new Integer(id));
+		
+		Document doc = hits.doc(i);
+		String path = doc.get("path");
+		// make sure the slashes go the right way
+		path = path.replaceAll("[\\\\\\/]", File.separator);
+		String full_path = collection_home + path;
+		File f = new File(full_path);
+		
+		String line = "";
+		
+		try{
+		    BufferedReader dis = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
+		    while( line.length() < MAX_DESCRIPTION_LENGTH ){
+			String tmp = dis.readLine();
+			line += tmp;
+		    }
+		    docDescs.addElement(line.substring(0,MAX_DESCRIPTION_LENGTH));
+		    dis.close();
+		}catch(IOException ex){docDescs.addElement(new String("No description available"));}    
+	    }
+	    searcher.close();
+	    
+	} catch (Exception ex) {ex.printStackTrace();}
+    }
+}
+
+
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/server/Search/MGSearcher.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/server/Search/MGSearcher.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/server/Search/MGSearcher.java	(revision 21054)
@@ -0,0 +1,101 @@
+/*********************************************************************
+ Copyright (C) 2003 Imperial College London
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+***********************************************************************/
+
+package vishnu.server.Search;
+
+import java.util.Vector;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.File;
+
+import org.greenstone.mg.*;
+
+public class MGSearcher implements SearchInterface
+{
+ 
+    private String collection_home;
+    private String collection_name;
+    private Vector docIDs;
+    private Vector docDescs;
+    private String mg_index_path;
+    private MGWrapper mg_src;
+    
+    public MGSearcher(String coll_home, String coll_name, String view)
+    {
+	collection_home = coll_home;
+	if (!collection_home.endsWith(File.separator)) {
+	    collection_home += File.separator;
+	}
+	collection_name = coll_name;
+	mg_src = new MGWrapper();
+	mg_index_path = "index"+File.separator+"mg_index"+ File.separator;
+	if (view != null) {
+	    mg_index_path += view + File.separator;
+	}
+	mg_index_path += collection_name+File.separator+collection_name;
+	mg_src.setIndex(mg_index_path);
+    }
+
+
+    public Vector getDocIdentifiers()
+    {
+	return docIDs;
+    }
+
+
+    public Vector getDocDescriptions()
+    {
+	return docDescs;
+    }	
+
+    // why do we want document in a vector ?? -- kjdon
+    public Vector getDocContent(int doc_num) {
+
+	Vector document = new Vector();
+	String doc_content = this.mg_src.getDocument(collection_home, mg_index_path, doc_num);
+	String [] lines = doc_content.split("\n");
+	for (int i=0; i<lines.length; i++) {
+	    document.addElement(lines[i]);
+	}
+	return document;
+
+    }
+
+    public void search(String query) {
+	this.mg_src.runQuery(collection_home, mg_index_path, query);
+	MGQueryResult mqr = this.mg_src.getQueryResult();
+	docIDs = new Vector();
+	docDescs = new Vector();
+	Vector doc_results =   mqr.getDocs();
+	int max_docs = Math.min(MAX_DOCS,doc_results.size());
+	for (int i=0; i<max_docs; i++) {
+	    int doc_num = (int)((MGDocInfo)doc_results.get(i)).num_;
+	    docIDs.addElement(new Integer(doc_num));
+	    String doc_content = this.mg_src.getDocument(collection_home, mg_index_path, doc_num);
+	    docDescs.addElement(doc_content.substring(0,MAX_DESCRIPTION_LENGTH));
+	}	
+    }
+    
+}
+
+
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/server/Search/SearchInterface.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/server/Search/SearchInterface.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/server/Search/SearchInterface.java	(revision 21054)
@@ -0,0 +1,41 @@
+/*********************************************************************
+ Copyright (C) 2003 Imperial College London
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+***********************************************************************/
+
+package vishnu.server.Search;
+
+import vishnu.datablock.*;
+import java.util.*;
+import org.w3c.dom.Element;
+
+public interface SearchInterface
+{
+
+    public static final int MAX_DOCS = 100;
+    public static final int MAX_DESCRIPTION_LENGTH = 200;
+
+    public void search(String query);
+
+    public Vector getDocIdentifiers();
+
+    public Vector getDocDescriptions();
+
+    public Vector getDocContent(int doc_num);
+
+    //public void setRootDirectory(String root);
+}
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/server/VisServlet.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/server/VisServlet.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/server/VisServlet.java	(revision 21054)
@@ -0,0 +1,328 @@
+/*********************************************************************
+ Copyright (C) 2003 Imperial College London
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+***********************************************************************/
+
+package vishnu.server;
+
+import vishnu.server.Search.*;
+import vishnu.datablock.*;
+import vishnu.util.XMLUtil;
+
+import java.io.*;
+import javax.servlet.*;
+import javax.servlet.http.*;
+import java.util.Vector;
+import java.util.Hashtable;
+
+import org.w3c.dom.Node; 
+import org.w3c.dom.NodeList; 
+import org.w3c.dom.Document; 
+import org.w3c.dom.Element; 
+import org.xml.sax.InputSource;
+import javax.xml.parsers.*;
+
+public class VisServlet extends HttpServlet
+{
+    final String DOCUMENT_FETCH_ACTION = "fetch";
+    final String DOCUMENT_SEARCH_ACTION = "search";
+    final String KEYWORDS_COMPUTE_ACTION = "keywords";
+    final String INITIALIZATION_ACTION = "initialise";
+    
+    protected String COLLS_HOME=null; 
+    
+    Hashtable collH;
+
+    public void init(ServletConfig config) throws ServletException {
+	// always call super.init;
+	super.init(config);
+
+	COLLS_HOME = config.getInitParameter("collectionsHome");
+	if (COLLS_HOME == null || COLLS_HOME.equals("")) {
+	    System.err.println("initialization parameter, collectionsHome, not set");
+	    System.exit(1);
+	}
+	if (!COLLS_HOME.endsWith(File.separator)) {
+	    COLLS_HOME += File.separator;
+	}
+    }
+
+    private Object receiveObject(HttpServletRequest req) throws Exception
+    {
+        ObjectInputStream in = new ObjectInputStream( req.getInputStream() );
+        Object obj = in.readObject();
+        in.close();
+        return obj;
+    }
+    
+    
+    private void sendObject(HttpServletResponse resp, Object obj) throws Exception
+    {
+        ObjectOutputStream out = new ObjectOutputStream( resp.getOutputStream() );
+        out.writeObject(obj);
+        out.close();
+    }
+    
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) 
+	throws IOException {
+
+	resp.setContentType("text/html;charset=UTF-8");
+        PrintWriter out = resp.getWriter();
+	String output = "<html><head><title>Visualiser servlet</title></head>\n<body>The servlet is running, and accepting messages via POST</body></html>";
+	out.println(output);
+
+    }
+    public void doPost(HttpServletRequest req, HttpServletResponse resp)
+    {
+	Object obj;
+	
+        try{
+	    obj = receiveObject(req);
+	    Vector q = (Vector)obj;
+	    Vector response = new Vector();
+	    
+	    String action = (String)q.elementAt(0);
+	    System.out.println("Action.. " + action);
+	    
+ 	    if( action.equals(INITIALIZATION_ACTION) ){
+		Vector colls = activateCollections();
+		sendObject(resp,colls);
+	    }
+	    else if( action.equals(DOCUMENT_SEARCH_ACTION) ){
+		Vector docNums = new Vector();
+		Vector descriptions = new Vector();
+		String engine;
+		
+		String query = (String)q.elementAt(1);
+		String collection = (String)q.elementAt(2);
+		String view = null;
+		if (q.size() == 4) {
+		    view = (String)q.elementAt(3);
+		}
+		if (view != null && view.equals("")) {
+		    view = null;
+		}
+		System.out.println("Requesting..." + collection);
+		
+		try{
+		    engine = (String)collH.get(collection);
+		}catch(Exception ex){engine = "MG";}
+		
+		
+		System.out.println("Retrieving with " + engine);
+		
+		String collection_home = COLLS_HOME+collection+File.separator;
+		SearchInterface search_engine = null;
+		if( engine.equals("MG") ){
+		    search_engine = new MGSearcher(collection_home, collection, view);
+		} else if( engine.equals("LUCENE") ){
+		    search_engine = new LUCSearcher(collection_home, view);
+		} 
+		
+		search_engine.search(query);
+		docNums = search_engine.getDocIdentifiers();
+		descriptions = search_engine.getDocDescriptions();
+		System.out.println(docNums.size() + " documents in VisServlet");
+		DataBlock data = generateDataBlock(collection_home, view, docNums, descriptions);
+		if (data != null) {
+		    response.addElement(data);
+		}
+		sendObject(resp,response);
+		search_engine = null;
+		data = null;
+		docNums = null;
+		descriptions = null;
+		
+	    }
+	    else if( action.equals(DOCUMENT_FETCH_ACTION) ){
+		String docNum = (String)q.elementAt(1);
+		String collection = (String)q.elementAt(2);
+		String view = null;
+		if (q.size() == 4) {
+		    view = (String)q.elementAt(3);
+		}
+		if (view != null && view.equals("")) {
+		    view = null;
+		}
+		String engine;
+		String collection_home = COLLS_HOME+collection+File.separator;
+		
+		try{
+		    engine = (String)collH.get(collection);
+		}catch(Exception ex){engine = "MG";}
+		
+                System.out.println("Retrieving with " + engine);
+		
+		SearchInterface search_engine = null;
+		if( engine.equals("MG") ){
+		    search_engine = new MGSearcher(collection_home, collection, view);
+		} else if( engine.equals("LUCENE") ){
+		    search_engine = new LUCSearcher(collection_home, view);
+		} 
+		System.err.println("Searching for doc "+docNum);
+		Vector doc = search_engine.getDocContent(Integer.parseInt(docNum));
+		response.addElement(doc);			
+		sendObject(resp,response);
+		search_engine = null;
+	    }
+	} catch(Exception e){}
+    }
+    
+
+    public Vector connect(Vector q)
+    {
+	return null;
+    }
+
+
+    private Vector activateCollections()
+    {
+	System.err.println("activating collections");
+	Vector colls = new Vector();
+	collH = new Hashtable();
+	Document doc = null;
+	
+	File dir = new File(COLLS_HOME);
+	if( !dir.exists() ){
+	    System.err.println("collections directory, "+COLLS_HOME+", doesn't exist, can't activate collections");
+	    return null;
+	}
+	
+	File[] list = dir.listFiles();
+	
+	for( int i = 0; i < list.length; i++ ){
+	    File f = list[i];
+	    if( f.isDirectory()){
+		File config_file = new File(f, "config.xml");
+		if (!config_file.exists()) {
+		    System.err.println("Collection "+f.getName()+" has no config file, ignoring");
+		    // ignore this collection
+		    continue;
+		} else {
+		    System.err.println("Trying to init collection "+f.getName());
+		}
+		try {
+		    DocumentBuilderFactory doc_build_fac = DocumentBuilderFactory.newInstance();
+		    DocumentBuilder doc_build = doc_build_fac.newDocumentBuilder();
+		    doc = doc_build.parse(config_file);
+		} catch (Exception e) {
+		    System.err.println("Error parsing config file "+config_file.getPath()+", ignoring this collection");
+		    continue;
+		}
+		Element root = doc.getDocumentElement();
+		String name = root.getAttribute("name");
+		String desc = root.getAttribute("description");
+		String engine = root.getAttribute("engine");
+		int size = Integer.parseInt(root.getAttribute("size"));
+		String coll_id = f.getName();
+		Element view_elem = (Element)XMLUtil.getChildByTagName(root, "viewList");
+		String [] views = null;
+		if (view_elem != null) {
+		    NodeList view_list = view_elem.getElementsByTagName("view");
+		    int num_views = view_list.getLength();
+		    if (num_views > 0) {
+			views = new String[num_views];
+			for (int j=0; j<num_views; j++) {
+			    String v = ((Element)view_list.item(j)).getAttribute("name");
+			    views[j]=v;
+			}
+		    }
+		}
+		Collection c = new Collection(coll_id,name,desc,size,engine,views);
+		System.out.println("Hashing.." + coll_id);
+		colls.addElement(c);
+		collH.put(coll_id,engine);
+	    }			
+	}
+	doc = null;
+	return colls;
+    }
+    
+    private DataBlock generateDataBlock(String collection_home, String view, Vector docNums, Vector descriptions) {
+
+	if (docNums.size()==0) {
+	    return null;
+	}
+	DataBlock data = new DataBlock();
+
+	CKServer ck_server = new CKServer(collection_home, view);
+	
+	/**** set data fields one by one and pass one what ever gets assembled ****/
+		
+	try{
+	    
+	    ck_server.setDescriptions(descriptions);
+	    
+	    /**** get candidate keywords ****/
+	    
+	    String[] keywords = ck_server.computeKeywords(docNums);
+	    
+	    data.words = keywords;
+	    
+	    
+	    /**** get sparse document * keyword matrix ****/
+	    
+	    SparseMatrix matrix = ck_server.getSparseMatrix(docNums);
+	    
+	    data.matrix = matrix;
+	    
+	    
+	    /**** get document indices, this is a subset of the original ****/
+	    /**** those without keywords are excluded                    ****/
+	    
+	    int[] docs = ck_server.getHitDocuments();
+	    
+	    data.docs = docs;
+	    
+	    
+	    String[] desc = ck_server.getHitDescriptions();
+	    
+	    data.descriptions = desc;
+	    
+	    
+	    /**** get 10 or so clusters ****/
+	    
+	    Vector[] clusters = ck_server.getClusters();
+	    
+	    data.clusters = clusters;
+	    
+	    
+	    double[][] centroids = ck_server.getCentroids();
+	    
+	    
+	    /**** send their centroids through sammon mapping ****/
+	    
+	    Point2D[] sammon = ck_server.getSammonMap(centroids);
+	    
+	    System.out.println("Num descriptions: " + descriptions.size());
+	    System.out.println("Num docs: " + docs.length);
+	    System.out.println("Num desc: " + desc.length);
+	    
+	    data.sammon = sammon;
+	    
+	} catch (Exception e) {
+	    System.err.println("VisServlet: computing clustering Error: "+e);
+	    e.printStackTrace();
+	}
+	ck_server = null;
+	return data;
+    }
+}
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/browser/Browser.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/browser/Browser.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/browser/Browser.java	(revision 21054)
@@ -0,0 +1,125 @@
+
+/**
+ * Title:        <p>
+ * Description:  <p>
+ * Copyright:    Copyright (c) <p>
+ * Company:      <p>
+ * @author
+ * @version 1.0
+ */
+package vishnu.testvis.browser;
+
+import javax.swing.JFrame;
+import java.awt.*;
+import javax.swing.*;
+import java.awt.event.*;
+import java.applet.*;
+import java.beans.*;
+import javax.swing.event.*;
+import java.net.URL;
+import java.io.IOException;
+import java.util.*;
+
+public class Browser extends JFrame
+{
+  JScrollPane scrollPane;
+  JEditorPane editorPane;
+  JPanel panel;
+  public Browser(URL url)
+  {
+    jb_init(url);
+  }
+  
+  public Browser(Vector document, long docNumber)
+  {
+    jb_init(document);
+  }
+
+  private void jb_init(Vector document)
+  {
+     WindowListener l = new WindowAdapter()
+    {
+      public void windowClosing(WindowEvent e)
+      {
+        setVisible(false);          // hide the Frame
+        dispose();
+      }
+    };
+    
+    panel = new JPanel();
+    setSize(500,400);
+    Container content = this.getContentPane();
+    panel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+    panel.setLayout(new BorderLayout(10,10));
+    content.setLayout(new BorderLayout(10,10));
+    editorPane = new JEditorPane();
+    editorPane.setEditable(false);               
+    editorPane.setContentType("text/html");   
+    
+    String text = new String("");
+    
+    for( int i = 0; i < document.size(); i++ )
+    {
+        text += (String)document.elementAt(i) + "\n";
+    }
+    
+    editorPane.setText(text);
+    scrollPane = new JScrollPane();
+    scrollPane.getViewport().add(editorPane);
+    panel.add(scrollPane,"Center");
+    content.add(panel,"Center");
+    
+    /*editorPane = createEditorPane(url);
+    scrollPane = new JScrollPane();
+    scrollPane.getViewport().add(editorPane);
+    panel.add(scrollPane,"Center");
+    content.add(panel,"Center");*/
+    
+    setVisible(true);
+  }
+
+  private void jb_init(URL url)
+  {
+    WindowListener l = new WindowAdapter()
+    {
+      public void windowClosing(WindowEvent e)
+      {
+        setVisible(false);          // hide the Frame
+        dispose();
+      }
+    };
+    panel = new JPanel();
+    setSize(500,400);
+    Container content = this.getContentPane();
+    panel.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+    panel.setLayout(new BorderLayout(10,10));
+    content.setLayout(new BorderLayout(10,10));
+    editorPane = createEditorPane(url);
+    scrollPane = new JScrollPane();
+    scrollPane.getViewport().add(editorPane);
+    panel.add(scrollPane,"Center");
+    content.add(panel,"Center");
+    setVisible(true);
+  }
+
+  private JEditorPane createEditorPane(URL url)
+  {
+    JEditorPane edPane = new JEditorPane();
+    edPane.setEditable(false);
+    displayURL(url,edPane);
+    return edPane;
+  }
+
+  private void displayURL(URL url, JEditorPane edPane)
+  {
+    try
+    {
+      edPane.setPage(url);
+    }
+    catch(IOException e)
+    {
+      System.out.println("Bad URL: "+ url.toString());
+    }
+  }
+}
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/AveLink.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/AveLink.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/AveLink.java	(revision 21054)
@@ -0,0 +1,9 @@
+package vishnu.testvis.dendro;
+
+public class AveLink extends LinkFunc
+{
+    double link(double a, double b)
+	{
+  		return (a+b)/2;
+	}
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Cluster.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Cluster.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Cluster.java	(revision 21054)
@@ -0,0 +1,120 @@
+package vishnu.testvis.dendro;
+
+import java.util.*;
+
+public class Cluster
+{
+    public int _id;
+    public Cluster  _parent = null;
+    public Cluster  _child_1 = null;
+    public Cluster  _child_2 = null;
+    public Vector docVect;
+    public Vector docObjVect;
+    public int[] wordFreq;  	
+    
+    public double _children_sim;
+    
+    // probably the size of the cluster
+    public int _items;
+    
+    public int _split;
+    
+    
+    
+    // [] -> centroid for each vector element ?
+    public double[] _centroid=null;
+    
+    public Vector _iter_items = null;
+    public int _iter_items_num = 0;
+    
+    public int _x = 0;
+    public int _y = 0;
+    public double[] getCentroid(){return _centroid;}
+    
+    public Cluster(int id, Cluster parent, Cluster child_1, Cluster child_2, double children_sim, int items)
+    {
+	_id                    = id;
+	_children_sim          = children_sim;
+	_items                 = items;
+	_split                 = 0;
+	_parent                = parent;
+        _iter_items            = new Vector(0);
+        
+        // probably to get a balanced tree
+	if (child_1 != null && child_2 != null)
+	    {
+		if (child_2._items > child_1._items)
+		    {
+		        _child_1 = child_1;
+		        _child_2 = child_2;
+		    }
+    		else
+		    {
+		        _child_1 = child_2;
+		        _child_2 = child_1;
+		    }
+  	    }
+	else // if either or both are null
+	    {
+    		_child_1 = child_1;
+    		_child_2 = child_2;
+	    }
+    }
+    
+    public void setDocVect(Vector docs)
+    {
+	docVect = docs;
+    }
+    
+    public void setDocObjVect(Vector docObj)
+    {
+	docObjVect = docObj;
+    }
+    
+    public Vector getDocObjVect()
+    {
+	return docObjVect;
+    }
+    
+    public Vector getDocVect()
+    {
+	return docVect;
+    }
+    
+    public void setWordFreq(int[] freqs)
+    {
+	wordFreq = freqs;
+    }
+    
+    public int[] getWordFreq()
+    {
+	return wordFreq;
+    }
+    
+    // a nice recursive function to determine the depth of the hierarchy
+    public int depth()
+    {
+	if (_id != -1)
+	    return 0;
+	else
+    		return Math.max(1 + _child_1.depth(), 1 + _child_2.depth());
+	}
+
+    void addChildrenToOutputVector(int []coding, Vector output)
+	{
+  	  if (_id != -1)
+          {
+    	    Integer ii = new Integer(coding[_id]);
+            output.addElement(ii);
+
+          }
+  	  else
+    	  {
+      	    _child_1.addChildrenToOutputVector(coding,output);
+      	    _child_2.addChildrenToOutputVector(coding,output);
+    	  }
+    }
+
+}
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/ClusterArray.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/ClusterArray.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/ClusterArray.java	(revision 21054)
@@ -0,0 +1,56 @@
+package vishnu.testvis.dendro;
+
+import java.util.*;
+import vishnu.testvis.object.*;
+import vishnu.datablock.*;
+
+
+public class ClusterArray
+{
+    Vector contents;
+    
+    ClusterArray(DataManager dm, Vector indices)
+    {
+	int i;
+	
+	contents = new Vector();
+
+	// num_of_clusters = number of docs
+	for (i = 0; i < indices.size(); i++)
+	    {
+		int id = ((Integer)indices.elementAt(i)).intValue();
+		Cluster c = new Cluster(i, null, null, null, 0, 1);
+		Vector docIndexVector = new Vector();
+		docIndexVector.addElement(new Integer(id));
+		c.setDocVect(docIndexVector);
+		
+		Vector docObjVector = new Vector();
+		DocObj docObj = dm.getDocObjAt(id);
+		docObjVector.addElement(docObj);
+		c.setDocObjVect(docObjVector);    
+		
+		DataBlock data = dm.getDataBlock();
+		int[] wordFreqs = new int[data.words.length];
+		for(int d=0; d < data.words.length; d++) wordFreqs[d]=0;
+		
+		SparseNode row = data.matrix.getRow(id);
+		
+		while( row!= null)
+		    {
+			wordFreqs[row.getColumn()]++;
+			row = row.getNext();
+		    }
+		c.setWordFreq(wordFreqs);
+		
+    		contents.addElement(c);
+		
+		// i.e. id = i, similarity between kids = 0, #items = 1
+	    }
+    }
+    
+    
+}
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/ClusterObj.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/ClusterObj.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/ClusterObj.java	(revision 21054)
@@ -0,0 +1,171 @@
+package vishnu.testvis.dendro;
+
+import java.awt.*;
+import vishnu.testvis.object.*;
+import java.util.Vector;
+
+public class ClusterObj
+
+{
+    Cluster cluster;
+    Point position;
+    int radius;
+    boolean inSubCluster;
+    private boolean orderStatus;
+    private boolean keywordStatus;
+    private String[] freqWords;
+    
+    DocObj[] _docs;		// list of DocObj ref
+    String _label;		// (top 3-5? related words)
+    DataManager _data;
+    
+    public ClusterObj(Cluster c, Point P, int r, DataManager data)
+    {
+        cluster = c;
+        position = P;
+        radius = r;
+        inSubCluster = false;
+        _data = data;
+        orderStatus = false;
+        keywordStatus = false;
+    }
+    
+    
+    public void setPosition(Point p)
+    {
+        position = p;
+    }
+    
+    public void setOrderStatus(boolean b)
+    {
+        orderStatus = b;
+    }
+    
+    public boolean getOrderStatus()
+    {
+        return orderStatus;
+    }
+    
+    public void setFreqWords(String[] str)
+    {
+        freqWords = str;
+        keywordStatus = true;
+    }
+    
+    public String[] getFreqWords()
+    {
+        return freqWords;
+    }
+    
+    public boolean getKeywordStatus()
+    {
+        return keywordStatus;
+    }
+    
+    public void setIsSubset(boolean b)
+    {
+        inSubCluster = b;
+    }
+    
+    public boolean getIsSubset()
+    {
+        return inSubCluster;
+    }
+    
+    public boolean inCircle(int x, int y)
+    {
+        double dist_sqr = (x-position.x)*(x-position.x) + (y-position.y)*(y-position.y);
+        return (dist_sqr <= (4*radius*radius)) ? true : false;
+    }
+    
+    
+    public void setDocObjs(DocObj[] docArray)
+    {
+        _docs = docArray;
+    }
+    
+    
+    public int getX()
+    {
+        return position.x;
+    }
+    
+    public int getY()
+    {
+        return position.y;
+    }
+    
+    public int getRad()
+    {
+        return radius;
+    }
+   
+   
+    public ClusterObj(DocObj[] d, String t, DataManager dm)
+    {
+        _docs=d; _label=t;
+        _data = dm;
+    }
+  
+  
+  public int getDocsSize(){return _docs.length;}
+
+  public String getLabel(){return _label;}
+
+  public String getLabel1(){return getDocsSize() + " documents found";}
+  public String getLabel2(){return getLabel();}
+
+
+  public String getInfo(){
+    return getDocsSize() + " documents found\n" + getLabel();
+  }
+  
+
+
+  /**
+   * This returns a list of list of documents. The inner list of documents represents
+   * which documents have the corresponding related word
+   * pre: _docs can't be null
+   * @return a vector (same length as the related words) of
+   *         vectors (arbitrary length)
+   *
+   */
+  public Vector getHitDocFreq()
+  {
+    int s = _data.getWordCount();
+    
+    Vector rwVec = new Vector(s);
+    
+    for(int i=0;i<s;i++)
+    {
+      rwVec.addElement(new Vector(0));
+      for(int j=0;j<_docs.length;j++)
+      {
+        if ( _docs[j].getWeights(i) > 0 )
+          ((Vector) rwVec.elementAt(i)).addElement(_docs[j]);
+      }
+    }
+    return rwVec;
+  }
+  
+  
+  public int getDocSize()
+  {
+        return cluster._items;
+  }
+
+
+  public String formatDocs()
+  {
+    int l = getDocsSize();
+    String s = "<html><font face=Arial size=-1> "+ l +" documents found\n";
+    for(int i=0;i<l;i++)
+    {
+      s += _docs[i].format4Fields();
+    }
+    s += "</font></html>\n";
+    return s;
+  }
+
+        
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Clustering.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Clustering.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Clustering.java	(revision 21054)
@@ -0,0 +1,248 @@
+package vishnu.testvis.dendro;
+
+/* Related word hierarchical clustering using a given triangle similarity matrix */
+
+//import cluster.*;
+import java.util.*;
+import vishnu.testvis.object.*;
+
+public class Clustering
+{
+    public static final int COMPLETE_LINKAGE = 0;
+    public static final int AVERAGE_LINKAGE  = 1;
+    public static final int SINGLE_LINKAGE   = 2;
+
+    private Vector [] theResult;
+    private double [][] theCentroids;
+    private Cluster theRoot;
+    public DataManager dataManager;
+    private int method = 1;
+
+    public Vector [] getTheResults()
+    {
+        return theResult;
+    }
+    public double[][] getTheCentroids()
+    {
+        return theCentroids;
+    }
+    
+    public Cluster getRoot()
+    {
+        return theRoot;
+    }
+
+
+    public Clustering (DataManager dm, int linkage, float [][]m,
+		       int num_of_vectors,
+		       int arraySize, int min_c_items,
+		       int target_clusters, int limit)
+    {
+	
+        SimMatrix sim_matrix;
+	LinkFunc linkage_func=null;
+	dataManager = dm;
+	
+	// number of keywords
+	int VECTOR_SIZE = arraySize;
+        
+	Cluster toplevel_cluster;
+	TheInt clusters = new TheInt(0);
+	
+	Distance sim_func = new EuclideanDistance();
+	
+	double[] heights;
+	double [][] vectors = floatToDoubleMatrix(m);//intToDoubleMatrix(m);
+	//int [] coding = new int[m.length];
+        
+	// populate array with numbers from 0 to #docs-1
+	//for (int c=0;c<m.length;c++) coding[c]=c;
+	
+	int i, j;
+	
+	switch (linkage)
+	    {
+    	    case COMPLETE_LINKAGE:
+      		linkage_func = new MaxLink();
+      		break;
+    	    case AVERAGE_LINKAGE:
+      		linkage_func = new AveLink();
+      		break;
+    	    case SINGLE_LINKAGE:
+      		linkage_func = new MinLink();
+      		break;
+    	    default:
+      		System.out.println("clustering.c: main(): linkage type "+
+	                  linkage +" not defined.");
+      	        System.exit(0);
+    	}
+
+        long startTime = System.currentTimeMillis();
+        long readTime = System.currentTimeMillis();
+        
+        
+        // if fewer documents than upper cluster limit
+        // I think this is an error, should be vectors.length, viz. #documents
+        // which was never spotted as limit is always smaller
+        // than #keywords
+        // if(limit > vectors[0].length) limit = vectors[0].length;
+
+        // contains distance matrix between documents, built using
+        // the double vector[i][] list of keyword frequencies
+
+
+	Vector indices = null;
+	//randomly choose sqrt(k*n) documents
+
+	if( method == 0 )
+	    {
+
+		int sampleSize = 2*(int)Math.sqrt(vectors.length);
+		Vector sampleInt = new Vector();
+		Vector allDocs = new Vector();
+		
+		for( int s=0; s<vectors.length;s++ )
+		    {
+			allDocs.addElement(new Integer(s));
+		    }
+		
+		while( sampleInt.size() < sampleSize )
+		    {
+			int r = (int)(Math.random()*allDocs.size());
+			sampleInt.addElement(new Integer(r));
+			allDocs.removeElementAt(r);
+		    }
+		double[][] sample_matrix = new double[sampleSize][VECTOR_SIZE];
+		for(int s=0; s<sampleSize;s++)
+		    {
+			int docNum = ((Integer)sampleInt.elementAt(s)).intValue();
+			sample_matrix[s] = vectors[docNum];
+		    }
+		
+		indices = sampleInt;
+		sim_matrix = new SimMatrix(sample_matrix,limit,sim_func,VECTOR_SIZE);
+
+		long simMatixTime = System.currentTimeMillis();
+		
+		// an array of heights sorted by descending disim
+		// as many heights as there are docs
+		heights = new double[m.length];//[limit - 1];
+		
+		//int maxDoc = 0;
+		int maxDoc = indices.size();
+		
+		Hierarchical hierarchical = new Hierarchical(maxDoc);
+		theRoot = hierarchical.clustering(dm, sim_matrix, maxDoc, heights,linkage_func,indices);
+		
+		// collect all clusters
+		Vector content=new Vector();
+		getLeaves(theRoot,content);
+		
+		for( int it = 0; it < allDocs.size(); it++ )
+		    {
+			int docNum = ((Integer)allDocs.elementAt(it)).intValue();
+			
+			double min_dist = 10000;
+			int min_index = -1;
+
+			for( int c = 0; c < content.size(); c++ )
+			    {
+				
+				Cluster clust = (Cluster)content.elementAt(c);
+
+				int docNums = ((Integer)clust.getDocVect().elementAt(0)).intValue();
+
+				double sim = sim_func.get(vectors[docNums],vectors[docNum], VECTOR_SIZE);
+
+				if (sim < min_dist)
+				    {
+					min_dist = sim;
+					min_index = c;
+				    }
+			    }
+			
+			Cluster clust = (Cluster)content.elementAt(min_index);
+			clust._iter_items.addElement(new TheInt(docNum));
+			clust._iter_items_num++;
+			clust.docVect.addElement(new Integer(docNum));
+			long iterativeTime = System.currentTimeMillis();
+			
+		    }
+	    }			 
+	else
+	    {
+   		Vector v = new Vector();
+		for( int l = 0; l < vectors.length; l++ )
+		    {
+			v.addElement(new Integer(l));
+		    }
+
+		indices = v;
+		sim_matrix = new SimMatrix(vectors, limit, sim_func, VECTOR_SIZE);
+	        
+		long simMatixTime = System.currentTimeMillis();
+		
+		// an array of heights sorted by descending disim
+		// as many heights as there are docs
+		heights = new double[m.length];//[limit - 1];
+		
+		//int maxDoc = 0;
+		int maxDoc = indices.size();
+		
+		Hierarchical hierarchical = new Hierarchical(maxDoc);
+		theRoot = hierarchical.clustering(dm, sim_matrix, maxDoc, heights,linkage_func,indices);
+		
+		// collect all clusters
+		Vector content=new Vector();
+		getLeaves(theRoot,content);
+	    }
+	
+    }
+
+    static double[][] intToDoubleMatrix(int [][] m)
+    {
+        int h=m.length;
+        double [][] res = null;
+        if (h > 0)
+        {
+          int w = m[0].length;
+          res = new double[h][w];
+          for(int c=0;c<h;c++)
+            for(int d = 0; d<w; d++)
+              res[c][d]=(double)m[c][d];
+        }
+        return res;
+   }
+    static double[][] floatToDoubleMatrix(float [][] m)
+    {
+        int h=m.length;
+        double [][] res = null;
+        if (h > 0)
+        {
+          int w = m[0].length;
+          res = new double[h][w];
+          for(int c=0;c<h;c++)
+            for(int d = 0; d<w; d++)
+              res[c][d]=(double)m[c][d];
+        }
+        return res;
+   }
+
+
+    private void getLeaves(Cluster node, Vector content)
+    {
+	if( node._child_1 != null )
+	    {
+		getLeaves(node._child_1,content);
+		getLeaves(node._child_2,content);
+	    }
+	else
+	    {
+		content.addElement(node);
+		Vector v = node.docVect;
+		int i = ((Integer)v.elementAt(0)).intValue();
+	    }
+    }
+    
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/DendroKeywordPanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/DendroKeywordPanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/DendroKeywordPanel.java	(revision 21054)
@@ -0,0 +1,102 @@
+package vishnu.testvis.dendro;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import vishnu.testvis.visual.*;
+import vishnu.testvis.object.*;
+import java.util.*;
+import vishnu.testvis.treemap.table.*;
+
+class DendroKeywordPanel extends JPanel
+{
+    RW_DataModel _dataModel;
+    JTable _table = new JTable();
+    JScrollPane theScrollPane;
+    JCheckBox checkBoxes[];
+    JLabel labels[];
+    Vishnu currentMain;
+    DendroPanel parentPane;
+
+    public DendroKeywordPanel(Vishnu currentMain, DendroPanel parent)
+    {
+        this.currentMain = currentMain;
+        this.parentPane = parent;
+        
+        theScrollPane = new JScrollPane();
+
+        setPreferredSize(new Dimension(200,900));
+        setBorder(BorderFactory.createLoweredBevelBorder());
+        setLayout(new BorderLayout(10,0));
+        Font theFont = new java.awt.Font("Monospaced", 0, 12);
+
+         _dataModel = new RW_DataModel();
+         _table.setModel(_dataModel);
+
+        _table.setAutoCreateColumnsFromModel(false);
+        _table.getColumnModel().getColumn(0).setPreferredWidth(20);
+        _table.getColumnModel().getColumn(1).setPreferredWidth(100);
+        _table.getColumnModel().getColumn(2).setPreferredWidth(50);
+        _table.setRowSelectionAllowed(false);
+        setupRenderer();
+
+        theScrollPane.getViewport().add(_table, null);
+        add(theScrollPane,"Center");
+
+        setVisible(true);
+
+        JButton refineButton = new JButton("Filter Documents");
+        refineButton.setBorder(BorderFactory.createRaisedBevelBorder());
+        refineButton.setMaximumSize(new Dimension(30,70));
+
+        MouseListener ml = new MouseAdapter()
+        {
+            public void mouseClicked(MouseEvent e)
+            {
+                 refineEngine();
+            }
+        };
+
+        refineButton.addMouseListener(ml);
+        refineButton.setFont(theFont);
+        add(refineButton,"South");
+    }
+
+    public void refineEngine()
+    {
+        // the filter words are available from within _dataModel, so nothing
+       // is passed on
+       parentPane.hLinkPanel.setText(_dataModel.getRefinedDocs());
+       String trec_id[] = _dataModel.getRefinedTrecIDs();
+       String filter_Words[] = _dataModel.getFilterWords();
+       String s = new String("");
+       for( int i = 0; i < filter_Words.length; i++ )
+       {
+            s += filter_Words[i] + " ";
+       }
+    }
+        
+
+    public void update(vishnu.testvis.dendro.ClusterObj c)
+    {
+        _dataModel.setData(currentMain.dataManager.getWords(), c);
+    }
+
+    public void clear()
+    {
+        _dataModel.clear();
+    }
+    private void setupRenderer()
+    {
+        BarRenderer bar = new BarRenderer();
+        //-- Have a number painted in the middle of the bar
+        bar.setStringPainted(true);
+        //-- setup the background color of the bar
+        bar.setBackground(_table.getBackground());
+
+        //-- render the last column as a bar
+        _table.getColumnModel().getColumn(2).setCellRenderer(bar);
+    }
+
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/DendroPanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/DendroPanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/DendroPanel.java	(revision 21054)
@@ -0,0 +1,61 @@
+package vishnu.testvis.dendro;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import vishnu.testvis.visual.*;
+import vishnu.testvis.object.*;
+import vishnu.testvis.sammon.*;
+import java.util.*;
+
+/** Panel containing the dendro map interface */
+public class DendroPanel extends JPanel
+{
+
+  public final static int LIST_ON_LEFT = 0;
+  public final static int LIST_ON_RIGHT = 1;
+  
+  public JPanel picturePanel;
+  public TreePanel treePanel;
+  public DendroKeywordPanel keywordPanel;
+  public HLinkPanel hLinkPanel;
+  public Stack dataStack;
+  public DataManager dataMan=null;
+  public Vishnu currentMain;
+  public JPanel topPanel = new JPanel();
+
+  public DendroPanel(Vishnu parent)
+  {
+    currentMain = parent;
+    setUpGui();
+  }
+  
+  public void setUpGui()
+  {
+        setLayout(new BorderLayout(10,10));
+        setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+        treePanel = new TreePanel(this,currentMain);
+        keywordPanel = new DendroKeywordPanel(currentMain,this);	
+	Dimension d = currentMain.getSize();
+        hLinkPanel = new HLinkPanel((int)(d.width)/4,d.height,currentMain);
+        picturePanel = new JPanel();
+        picturePanel.setLayout(new BorderLayout(0,3));
+              
+        picturePanel.add(treePanel,"Center");
+        add("East",  hLinkPanel.linkPane);
+        add("Center",   picturePanel);
+        add("West",   keywordPanel);
+        setVisible(true);
+  }
+  
+  public void update()
+  {
+    dataMan = currentMain.dataManager;
+    dataStack = new Stack(); 
+    treePanel.clear();
+    treePanel.update();
+    keywordPanel.clear();
+    hLinkPanel.clear();
+  }
+}
+  
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Distance.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Distance.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Distance.java	(revision 21054)
@@ -0,0 +1,10 @@
+package vishnu.testvis.dendro;
+
+// Abstract class
+
+public abstract class Distance
+{
+	public abstract double get(double[] v1, double[] v2, int size);
+}
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/EuclideanDistance.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/EuclideanDistance.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/EuclideanDistance.java	(revision 21054)
@@ -0,0 +1,16 @@
+package vishnu.testvis.dendro;
+
+public class EuclideanDistance extends Distance
+{
+    public double get(double[] v1, double[] v2, int size)
+    {
+  	double sum = 0, x;
+   //System.out.println("Array length "+ v1.length);
+	for(int c=0;c<v1.length;c++)
+	{
+      	    x = v1[c] - v2[c];
+	    sum += x*x;
+	}
+  	return Math.sqrt(sum);
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/HLinkPanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/HLinkPanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/HLinkPanel.java	(revision 21054)
@@ -0,0 +1,80 @@
+package vishnu.testvis.dendro;
+
+import javax.swing.*;
+import javax.swing.text.html.*;
+import javax.swing.event.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+import vishnu.testvis.visual.*;
+
+class HLinkPanel extends JEditorPane {
+
+    public JScrollPane linkPane;
+    public Vishnu currentMain;
+    public int index = 0;
+
+    public int numDisplayedDocs = 15;
+
+    public HLinkPanel(int width,int length, Vishnu currentMain) {
+
+        linkPane = new JScrollPane(this);
+        this.currentMain=currentMain;
+        linkPane.setBorder(BorderFactory.createLoweredBevelBorder());
+        linkPane.setPreferredSize(new Dimension(width,length));
+        //linkPane.setMinimumSize(new Dimension(width,length));
+        linkPane.setBorder(new javax.swing.border.EtchedBorder());
+        linkPane.getVerticalScrollBar().setUnitIncrement(10);
+
+        setLayout(new GridLayout(17,1));
+
+
+        setEditorKitForContentType("text/html",new HTMLEditorKit());
+        setEditorKit(new HTMLEditorKit());
+        setEditable(false);
+
+        addHyperlinkListener(new MyLinkListener());
+
+        setVisible(true);
+
+    }
+
+    public class MyLinkListener implements HyperlinkListener{
+        public void hyperlinkUpdate(HyperlinkEvent he){
+            //System.out.println("Inside hyperlinkevent ... ");
+            HyperlinkEvent.EventType type = he.getEventType();
+            //System.out.println("Inside hyperlinkevent ... ");
+            if (type==HyperlinkEvent.EventType.ACTIVATED)
+            {
+
+                String hpresult = he.getDescription();
+                int index = Integer.parseInt(hpresult);
+                System.out.println("hyperlink result: " + index);
+                if (index > 0)
+                    currentMain.dataManager.fetchDocNo(index);
+            }
+        }
+    }
+
+    /*public void update()
+    {
+        int count = currentMain.dataManager.getDocCount();
+        Vector subSet = new Vector(count);
+
+        for (int i = index ; i < index + numDisplayedDocs  ; i++)
+        {
+            subSet.addElement(new Integer(i));
+        }
+
+        String htmlDescription = currentMain.dataManager.getDocumentDescription(subSet);
+
+        setText(htmlDescription);
+
+    }*/
+
+    public void clear()
+    {
+        setText("");
+    }
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Hierarchical.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Hierarchical.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Hierarchical.java	(revision 21054)
@@ -0,0 +1,356 @@
+package vishnu.testvis.dendro;
+
+import vishnu.cluster.*;
+import java.util.*;
+import vishnu.testvis.object.*;
+
+public class Hierarchical
+{
+    public double[] minrow;
+    public int[]    colind;
+    //public SimMatrix smatrix;
+    public TriangleIndex triangle;
+    public final double weight = 3.2;
+    public DataManager dataManager;
+
+    public Hierarchical(int matrix_rows)
+    {
+	// create Triangle with "limit" arg = maximum #clusters
+	// an object to help with indexing the cells 
+	// in a triangular matrix
+	triangle = new TriangleIndex(matrix_rows);
+	
+    }
+    
+    // set each minrow[i] to the value of the minimum element of that row
+    void set_minrow (SimMatrix m, int row)
+    {
+	// triangular matrix without diagonal, hence zeroth row is all zero
+        if(row == 0) return;
+	
+	int i;
+	int index_start, index_end;
+        
+        // first column
+	index_start = triangle.index (row, 0);
+	// last column (row(!)-1 because as many rows as columns)
+	index_end   = triangle.index (row, row - 1); 
+	
+	minrow[row] = m.matrix[index_start];
+	colind[row] = index_start;
+	
+    	for (i = index_start + 1; i <= index_end; i++)
+	    {
+      		if (m.matrix[i] < minrow[row])
+		    {
+			minrow[row] = m.matrix[i];
+			colind[row] = i;
+		    }
+	    }
+    }
+    
+    // return the ROW in which the minimum disimilarity resides
+    int get_minrow (int last_row)
+    {
+	int i;
+	int row;
+	double value;
+	
+	row   = 1;
+	value = minrow[1];
+	
+	for (i = 2; i <= last_row; i++)
+	    {
+      		if (minrow[i] < value)
+		    {
+			row = i;
+			value = minrow[i];
+		    }
+	    }
+	
+	return row;
+    }
+    
+    // num_of_rows is number of documents, or subsample for Buckshot
+    // indices is a vector of the doc ids (made necessary by Buckshot, which uses a subsample of all docs)
+    Cluster clustering (DataManager dm, SimMatrix sim_matrix, int num_of_rows,
+			double[] heights, LinkFunc linkage_func, Vector indices)
+    {
+	Cluster child_1;
+	Cluster child_2;
+	Cluster parent=null;
+	ClusterArray cluster_ary;
+	
+	
+        int min_row, index, row, col, i;
+	double sim;
+
+        int last_row_index, sim_matrix_size;
+
+        sim_matrix_size = num_of_rows *(num_of_rows - 1) / 2;
+  		
+	// Place each of the documents in a cluster of each own 
+	// there are vector.length docs but only num_of_rows = limit places in the array
+	// may be two-stage clustering process, i.e. cluster subset of docs and then add the rest
+	//	cluster_ary = new ClusterArray (dm, num_of_rows);
+
+	cluster_ary = new ClusterArray(dm,indices);
+	last_row_index = num_of_rows - 1;
+	
+	// a variable that is a double and exists for each row - what can this be?
+	// the lowest dissimilarity score?
+	minrow = new double[num_of_rows];
+	
+	// a variable that is an int and exists for each row - what can that be?
+	// the document index of the document with that lowest score?
+	colind = new int[num_of_rows];
+	
+  	    for (i = 1; i < num_of_rows; i++)
+        {
+            set_minrow(sim_matrix, i);
+        }
+        
+        // do all the hierarchical clustering by running over all rows, updating the matrix 
+        // as a new cluster is formed each time
+  	    for (i = 0; i < num_of_rows - 1; i++)
+        {
+                // find the row with the lowest disimilarity
+      	        min_row = get_minrow(last_row_index);
+      	        sim = minrow[min_row];
+      	        
+      	        // the column with the lowest dis... (sim) in that row
+      	        index  = colind[min_row];
+
+                // array of heights, DESCENDING disim, i.e. sim goes last
+      	        heights[num_of_rows - 2 - i] = sim; 
+
+      	        row = triangle.i_ind[index];
+      	        col = triangle.j_ind[index];
+      	        
+      	        newMerge(row,col,sim_matrix,last_row_index,linkage_func,cluster_ary);
+      	            
+      	        sim_matrix_size -= last_row_index;
+      	        
+      	        child_1 = (Cluster)cluster_ary.contents.elementAt(row);
+      	        child_2 = (Cluster)cluster_ary.contents.elementAt(col);
+
+      	        parent = new Cluster (-1, null, child_1, child_2, sim, child_1._items + child_2._items);
+      	        Vector vDoc = new Vector();
+      	        vDoc.addAll(child_1.getDocVect());
+      	        vDoc.addAll(child_2.getDocVect());
+      	        parent.setDocVect(vDoc);
+      	        
+      	        Vector vDocObj = new Vector();
+      	        vDocObj.addAll(child_1.getDocObjVect());
+      	        vDocObj.addAll(child_2.getDocObjVect());
+      	        parent.setDocObjVect(vDocObj);
+      	        
+      	        int[] wordFreq1 = child_1.getWordFreq();
+      	        int[] wordFreq2 = child_2.getWordFreq();
+      	        for( int z = 0; z < wordFreq1.length; z++ )
+      	        {
+      	            wordFreq1[z] += wordFreq2[z];
+      	        }
+      	        parent.setWordFreq(wordFreq1);
+      	        
+      	        child_1._parent = parent;
+      	        child_2._parent = parent;
+      	        cluster_ary.contents.setElementAt(cluster_ary.contents.elementAt(last_row_index),row);
+      	        cluster_ary.contents.setElementAt(parent,col);
+      	        last_row_index--;
+         	        
+    	    }
+
+  	    return parent;
+
+        }
+        
+    void newMerge(int r, int c, SimMatrix m, int lastRowIndex, LinkFunc linkage_func, ClusterArray clusterArr)
+    {
+        int n = lastRowIndex+1;
+        double newMatrix[][] = new double[n][n];
+        for( int i = 0; i < n; i++ )
+        {
+            for( int j = 0; j < n; j++ )
+            {
+                if( i != j ) newMatrix[i][j] = (double)((int)(m.matrix[triangle.index(i,j)]*100))/100;
+                    else newMatrix[i][j] = 0;
+            }
+        }
+        
+        for( int i = 0; i < n; i++ )
+        {
+            int sizeA =((Cluster)clusterArr.contents.elementAt(c))._items + ((Cluster)clusterArr.contents.elementAt(r))._items;
+            int sizeB = ((Cluster)clusterArr.contents.elementAt(i))._items;
+            // large if cluster differ in size
+            double scale = weight * (double)((sizeA+sizeB)/2*(sizeA+sizeB)/2)/(sizeA*sizeA+sizeB*sizeB);
+            
+            newMatrix[c][i] = scale*linkage_func.link(newMatrix[c][i],newMatrix[r][i]);            
+            
+        }
+        
+        newMatrix[c][c] = 0;
+        newMatrix[c][r] = newMatrix[c][n-1];
+        for( int i = 0; i < n; i++ )
+        {
+            newMatrix[i][c] = newMatrix[c][i];
+        }
+        
+        
+        for( int i = 0; i < n; i++ )
+        {
+            if( i != c ) newMatrix[r][i] = newMatrix[n-1][i];
+        }
+        newMatrix[r][r] = 0;
+        for( int i = 0; i < n; i++ )
+        {
+            newMatrix[i][r] = newMatrix[r][i];
+        }
+        
+        int index = 0;
+        for( int i = 0; i < n; i++ )
+        {
+            for( int j = 0; j < n; j++ )
+            {
+                if( j < i )
+                {
+                    m.matrix[index] = newMatrix[i][j];
+                    index++;
+                }
+          
+            }
+          
+       }
+       
+       for (int i = 1; i < n; i++)
+       {
+            set_minrow(m, i);
+       }
+        
+     
+    }
+
+    // form a new cluster from the two most similar documents
+    void merge (int i, int j, SimMatrix m, int n, LinkFunc linkage_func)
+	{
+  /* i and j are the matrix elements with maximum similarity         */
+  /* matrix is the triangle similarity matrix with last row index n  */
+  /* linkage_func is a pointer to the linkage function               */
+
+  /* i > j */
+
+  	    int c, k, index1, index2;
+
+        // go over all rows
+  	    for (k = 0; k <= n; k++)
+    	{
+    	    // if k < j the affected elements are on row j
+      		if (k < j)
+		    {
+		        // index1 is the smaller of the two, as j < i
+	     	    index1 = triangle.index (k, j);
+	  	        index2 = triangle.index (k, i);
+	  	        m.matrix[index1] = linkage_func.link(m.matrix[index1], m.matrix[index2]);
+        	}
+      		else if (k > j && k < i)
+		    {
+	            // if j < k < i then only one element is updated per row
+	  	        index1 = triangle.index (k, j);
+	  	        index2 = triangle.index (k, i);
+	  	        m.matrix[index1] = linkage_func.link(m.matrix[index1], m.matrix[index2]);
+    	  	    if (index1 == colind[k])
+	    	    {
+	            // the previous minimum of the row has been changed so recaculate minimum of whole row
+	      		    set_minrow(m, k);
+	    	    }
+	  	        else
+	    	    {
+	                // if the changed element is smaller than the current minimum update minrow and colind
+	      		    if (m.matrix[index1] < minrow[k])
+        			{
+		          	    minrow[k] = m.matrix[index1];
+		  	            colind[k] = index1;
+			        }
+	    	    }
+		    }
+      		else if (k > i && k < n)
+		    {
+	  	        index1 = triangle.index (k, j);
+	  	        index2 = triangle.index (k, i);
+	  	        m.matrix[index1] = linkage_func.link(m.matrix[index1], m.matrix[index2]);
+	  
+          	    if (index1 == colind[k])
+	    	    {
+	                // the previous minimum of the row has been changed so recaculate minimum of whole row */
+	      		    set_minrow(m, k);
+	    	    }
+	  	        else
+	    	    {
+	                // if the changed element is smaller than the current minimum update minrow and colind */
+	      		    if (m.matrix[index1] < minrow[k])
+			        {
+	  		            minrow[k] = m.matrix[index1];
+                        colind[k] = index1;
+			        }
+	    	    }
+
+	  	        index1 = triangle.index (k, i);
+	  	        index2 = triangle.index (k, n);
+	  	        m.matrix[index1] = m.matrix[index2];
+	  	  	    if (index1 == colind[k])
+	    	    {
+	            // the previous minimum of the row has been changed so recaculate minimum of whole row 
+	      		    set_minrow(m, k);
+	    	    }
+	  	        else
+	    	    {
+	                // if the changed element is smaller than the current minimum update minrow and colind */
+	      		    if (m.matrix[index1] < minrow[k])
+			        {
+		  	            minrow[k] = m.matrix[index1];
+		  	            colind[k] = index1;
+			        }
+	    	    }
+		    }
+      	    else if (k == n  && i != n)
+		    {
+	  	        index1 = triangle.index (k, j);
+	  			index2 = triangle.index (k, i);
+	  			m.matrix[index1] = linkage_func.link(m.matrix[index1], m.matrix[index2]);
+	  			if (index1 == colind[k])
+	    		{
+	                // the previous minimum of the row has been changed so recaculate minimum of whole row */
+	      			set_minrow(m, k);
+	    		}
+	  			else
+	    		{
+	                // if the changed element is smaller than the current minimum update minrow and colind */
+	      			if (m.matrix[index1] < minrow[k])
+					{
+		  				minrow[k] = m.matrix[index1];
+		  				colind[k] = index1;
+					}
+	    		}
+
+	   			for (c = 0; c < i; c++)
+	    		{
+	      			index1 = triangle.index (c, i);
+	      			index2 = triangle.index (c, n);
+	      			m.matrix[index1] = m.matrix[index2];
+	      		}
+			}
+						
+    	}
+  		set_minrow(m, i);
+  		set_minrow(m, j);
+	}
+
+}
+
+
+
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Iterative.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Iterative.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/Iterative.java	(revision 21054)
@@ -0,0 +1,111 @@
+package vishnu.testvis.dendro;
+
+import java.util.*;
+
+class Iterative
+{
+    Vector vector;
+    int VECTOR_SIZE;
+    
+    Iterative(int vsize)
+    {
+	VECTOR_SIZE = vsize;
+    }
+    
+    public void printVector(double[] a)
+    {
+        int i;
+
+	for (i = 0; i < VECTOR_SIZE; i++)
+    	    System.out.println("v[" + i + "] = " + a[i]);
+    }
+    
+    
+    void cluster_center (Cluster cluster, double [][] vectors, double[] centroid)
+    {
+        int i;
+	
+      	if (cluster._id == -1)
+	    {
+		cluster_center (cluster._child_1, vectors, centroid);
+		cluster_center (cluster._child_2, vectors, centroid);
+	    }
+        else
+	    {
+		double[] doubleArray = vectors[cluster._id];
+		for (i = 0; i < doubleArray.length; i++)
+	            centroid[i] += doubleArray[i];
+	    }
+    }
+    
+    void compute_cluster_centers (ClusterArray clusters, int num_of_clusters, double [][] vectors)
+    {
+	int i, j; double ssum;
+	Cluster clust;
+	
+	for (i = 0; i < num_of_clusters; i++)
+	    {
+		clust = (Cluster)clusters.contents.elementAt(i);
+	        clust._centroid = new double[VECTOR_SIZE];
+		cluster_center (clust, vectors, clust._centroid);
+		for (ssum = j = 0; j < VECTOR_SIZE; j++)
+		    ssum += clust._centroid[j] * clust._centroid[j];
+		if(ssum>0)                // normalise
+		    for(ssum = 1.0/Math.sqrt(ssum), j = 0; j < VECTOR_SIZE; j++)
+			clust._centroid[j] *= ssum;
+	    }
+    }
+    
+
+    void place_item (ClusterArray clusters,
+		     int num_of_clusters,
+		     double [][] vectors,
+		     int item_id,  Distance sim_func)
+    {
+	double max_sim, sim;
+	int max_sim_index;
+	
+	int i;
+	
+	max_sim_index = 0;
+        Cluster clust = (Cluster)clusters.contents.elementAt(0);
+	max_sim = sim_func.get(clust._centroid,
+			       vectors[item_id], VECTOR_SIZE);
+	
+  	    for (i = 1; i < num_of_clusters; i++)
+    	{
+            clust = (Cluster)clusters.contents.elementAt(i);
+      	    sim = sim_func.get(clust._centroid,
+			vectors[item_id], VECTOR_SIZE);
+
+            if (sim < max_sim)
+	        {
+	  	        max_sim_index = i;
+	  	        max_sim = sim;
+	        }
+    	}
+  	    
+  	    clust = (Cluster)clusters.contents.elementAt(max_sim_index);
+        clust._iter_items.addElement(new TheInt(item_id));
+	    clust._iter_items_num++;
+    }
+
+    // add all documents not yet added to the existing cluster hierarchy (which is based on the 
+    // limit first documents. Hence there are vector.length - limit docs to add
+    // consequently index runs from limit (start_index) to vector.length (end_index)
+    void clustering (ClusterArray clusters, TheInt num_of_clusters,
+                      double[][] vectors, int start_index, int end_index,
+                      Distance sim_func)
+    {
+        int i;
+    
+  	    if (start_index >= end_index)
+    	    	return;
+
+  	    compute_cluster_centers(clusters, num_of_clusters.val, vectors);
+
+  	    for (i = start_index; i < end_index; i++)
+      	    	place_item(clusters, num_of_clusters.val, vectors, i, sim_func);
+    }
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/LinkFunc.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/LinkFunc.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/LinkFunc.java	(revision 21054)
@@ -0,0 +1,8 @@
+package vishnu.testvis.dendro;
+
+//Abstract linkfunctions class
+
+public abstract class LinkFunc
+{
+	abstract double link(double a, double b);
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/MaxLink.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/MaxLink.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/MaxLink.java	(revision 21054)
@@ -0,0 +1,9 @@
+package vishnu.testvis.dendro;
+
+public class MaxLink extends LinkFunc
+{
+	public double link(double a, double b)
+	{
+  		return (a > b) ? a : b;      //*99+b: b*99+a)/100;
+	}
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/MinLink.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/MinLink.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/MinLink.java	(revision 21054)
@@ -0,0 +1,9 @@
+package vishnu.testvis.dendro;
+
+public class MinLink extends LinkFunc
+{
+	public double link(double a, double b)
+	{
+  		return (a < b? a*999+b: b*999+a)/1000;
+	}
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/QSortForDocObj.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/QSortForDocObj.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/QSortForDocObj.java	(revision 21054)
@@ -0,0 +1,76 @@
+package vishnu.testvis.dendro;
+
+import vishnu.testvis.object.*;
+
+public class QSortForDocObj
+{
+
+    private boolean pauseTrue(int lo, int hi) throws Exception {
+        return true;
+    }
+  
+   void QuickSort(DocObj a[], int lo0, int hi0) throws Exception
+   {
+      int lo = lo0;
+      int hi = hi0;
+      int mid;
+
+      if ( hi0 > lo0)
+      {
+
+           mid = a[ ( lo0 + hi0 ) / 2 ].getNumber();
+
+         // loop through the array until indices cross
+         while( lo <= hi )
+         {
+            /* find the first element that is greater than or equal to
+             * the partition element starting from the left Index.
+             */
+             while( ( lo < hi0 ) && pauseTrue(lo0, hi0) && ( a[lo].getNumber() < mid ))
+                 ++lo;
+
+            /* find an element that is smaller than or equal to
+
+             * the partition element starting from the right Index.
+             */
+             while( ( hi > lo0 ) && pauseTrue(lo0, hi0) && ( a[hi].getNumber() > mid ))
+                 --hi;
+
+            // if the indexes have not crossed, swap
+            if( lo <= hi )
+            {
+               swap(a, lo, hi);
+               ++lo;
+               --hi;
+            }
+         }
+
+         /* If the right index has not reached the left side of array
+          * must now sort the left partition.
+
+          */
+         if( lo0 < hi )
+            QuickSort( a, lo0, hi );
+
+         /* If the left index has not reached the right side of array
+          * must now sort the right partition.
+          */
+         if( lo < hi0 )
+            QuickSort( a, lo, hi0 );
+
+      }
+   }
+
+   private void swap(DocObj a[], int i, int j)
+   {
+      DocObj D;
+      D = a[i];
+      a[i] = a[j];
+      a[j] = D;
+   }
+
+   public void sort(DocObj a[]) throws Exception
+   {
+      QuickSort(a, 0, a.length - 1);
+   }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/SimMatrix.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/SimMatrix.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/SimMatrix.java	(revision 21054)
@@ -0,0 +1,46 @@
+package vishnu.testvis.dendro;
+
+import java.util.*;
+
+// kainof DistanceMatrix
+public class SimMatrix
+{
+  public double[] matrix;
+
+  // "vectors" is the expanded SparseMatrix, i.e. doc x keywords
+  // VECTOR_SIZE = #keywords
+  public SimMatrix(double[][] vectors,
+      int limit, Distance simfunc, int VECTOR_SIZE)
+  {
+    int i,j;
+    double iarray[];
+    
+    // size = #docs
+    int size=vectors.length, index=0;
+    
+    // to exclude diagonal and conjugates
+    // - - - - -
+    //         |
+    // *       |
+    // * *     |
+    // * * *   |    
+    int realSize = (size * (size - 1)/2);
+    matrix=new double[realSize];
+    System.out.println("Similarity matrix size: " + size);
+    
+    for(i = 1; i < size; i++)
+    {
+        iarray=vectors[i];
+        for(j = 0; j < i; j++)
+        {
+            matrix[index] = simfunc.get(iarray,
+                            vectors[j],
+                            VECTOR_SIZE);
+	        index++;
+        }
+    }
+  }
+
+}
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/TheInt.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/TheInt.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/TheInt.java	(revision 21054)
@@ -0,0 +1,17 @@
+package vishnu.testvis.dendro;
+
+public class TheInt
+{
+	public int val;
+
+	TheInt(int x)
+	{
+		val = x;
+	}
+	
+	public void setInt(int i)
+	{
+	    val = i;
+	}
+	
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/TheVectors.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/TheVectors.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/TheVectors.java	(revision 21054)
@@ -0,0 +1,79 @@
+package vishnu.testvis.dendro;
+
+import java.io.*;
+import java.util.*;
+
+public class TheVectors
+{
+    public Vector theData;
+    public int size;
+    public Vector coding;
+    public int zeros;
+
+
+    TheVectors(String file, int VECTOR_SIZE)
+    {
+        theData= new Vector(VECTOR_SIZE);
+	coding= new Vector(VECTOR_SIZE);
+        int index,code =0, zerolist=0;
+	double ssum,value;
+    	try
+	{
+      	    FileReader inFile = new FileReader(file);
+      	    BufferedReader inLines = new BufferedReader(inFile);
+	    size=0;
+            String line = null, tokenString;
+	    do
+	    {
+
+		line = inLines.readLine();
+		if (line!= null)
+		{
+
+		  double[] row= new double[VECTOR_SIZE];
+ 					StringTokenizer tokens = new StringTokenizer(line," ");
+                  ssum=0.0;
+   		  for (index=0; index<VECTOR_SIZE; index++)
+		  {
+                      if(tokens.hasMoreTokens())
+                      {
+                          tokenString = tokens.nextToken();
+           	          value = Double.parseDouble(tokenString);
+		          row[index]= value;
+		          ssum+= value*value;
+                      }
+                      else
+                      {
+                          row[index]=0.0;
+                      }
+	          }
+	          if(index==0) continue;
+                  ssum=Math.sqrt(ssum);
+	          if(ssum!=0.0)
+	          {
+	              for(int i=0;i<index;i++)
+		      row[i]/=ssum;
+		      theData.addElement(row);
+		      coding.addElement(new TheInt(code));
+                      size++;
+		  }
+		  else
+		  {
+		      if(zerolist==0)
+		      {
+			//System.out.print("0 ");
+	                  zerolist=1;
+		      }
+		      System.out.print(code + " ");
+		  }
+	      }
+	      code++;
+	  }while(line != null);
+	}
+	catch(IOException e)
+	{
+	    System.err.println("IO Exception in read_vectors");
+	    e.printStackTrace();
+        }
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/TreePanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/TreePanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/TreePanel.java	(revision 21054)
@@ -0,0 +1,822 @@
+package vishnu.testvis.dendro;
+
+import java.awt.image.*;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+import java.util.Enumeration;
+import vishnu.testvis.visual.*;
+import vishnu.testvis.object.*;
+import vishnu.testvis.sammon.*;
+import vishnu.datablock.*;
+
+
+public class TreePanel extends JPanel
+{
+    
+    DendroPanel parent;
+    Vishnu currentMain;
+    DataManager dataManager;
+    DataBlock data;
+    Image offScreen=null;
+    Dimension offScreenSize;
+    Graphics offGraphics;
+    Graphics2D offGraphics2D;
+    Vector clusterVect = null;
+    Cluster root = null;
+    Cluster selectedRoot = null;
+    ClusterObj selectedCluster = null;
+    ClusterObj oldCluster = null;
+    
+    int selectedClusterID = -1; 
+    int oldClusterID = -1;
+    JPopupMenu popup;
+    boolean constructed = false;
+    int selectedDepth = -1;
+    int initBranchLength = 85;
+
+    boolean firstTime = true;
+    Graphics2D big;
+    BufferedImage bi;
+    Rectangle area;
+    Dimension d = null;
+    
+    public TreePanel(DendroPanel parent, Vishnu frame)
+    {
+        currentMain = frame;
+        this.parent = parent;
+        dataManager = frame.dataManager;
+             
+        popup = new JPopupMenu();
+        popup.setVisible(false);
+        add(popup);
+        
+        this.addComponentListener( new ComponentAdapter()
+        {
+            public void componentResized(ComponentEvent evt)
+	    {
+		repaint();
+            }
+        }
+        );
+        
+        this.addMouseMotionListener( new MouseMotionAdapter()
+        {
+            public void mouseMoved(MouseEvent mouseEvent)
+            {
+                int x = mouseEvent.getX();
+                int y = mouseEvent.getY();
+                
+                if( clusterVect != null )
+                {
+                    
+                    oldClusterID = selectedClusterID;
+                    oldCluster = selectedCluster;
+                    selectedClusterID = -1;
+                    selectedCluster = null;
+                    
+                    boolean notFound = true;
+                    int i = 0;
+                    
+                    while( notFound && (i < clusterVect.size()) )
+                    {
+                       ClusterObj co = (ClusterObj)clusterVect.elementAt(i);
+                       
+                       if( co.inCircle(x,y) )
+                       {
+                            selectedCluster = co;
+                            selectedClusterID = i;
+                                        
+                            if( selectedClusterID != oldClusterID )
+                            {
+                                 repaint();
+                            }
+                            notFound = false;
+                       }
+                       i++;
+                    }
+                    
+                    if( (oldClusterID != selectedClusterID) && ( oldClusterID != -1 ) )
+                    {
+                        popup.setVisible(false);
+                        repaint();
+			//update();
+                    }
+                    
+                    if( selectedClusterID != -1 )
+                    {
+                        if( selectedClusterID != oldClusterID )
+                        {
+                            popup.removeAll();
+                            int size = selectedCluster.cluster._items;
+                            String str =  size + ((size > 1) ? " documents" : " document");
+                            JMenuItem menuItem = new JMenuItem(str);
+                            menuItem.addActionListener(new ActionListener()
+                            {
+                                public void actionPerformed(ActionEvent actionEvent)
+                                {
+                                    getDocObjs();
+                                    String[] drillWords = selectedCluster.getFreqWords();
+                                    String keyWordStr = new String(drillWords[0]+" "+drillWords[1]+" "+drillWords[2]);
+
+                                    updateHLinkPanel();                                
+                                    updateKeyWordTable();
+                                }
+                                
+                            });
+                            
+                            popup.add(menuItem);
+                            
+                            popup.add(new JPopupMenu.Separator());
+                            
+                            int mode0 = -1;
+                            
+                            // if the currentRoot is selected, dont show "drilldown"
+                            if( selectedClusterID == 0 )
+                            {
+                                // if we are at the top, dont show "undrill"
+                                if( ((Object)selectedCluster.cluster).equals((Object)root) )
+                                {
+                                    str = "Top of hierarchy";
+                                    mode0 = 2;
+                                }
+                                else
+                                {
+                                    str = "Sequential undrill";
+                                    mode0 = 1;
+                                }
+                            }
+                            // if somewhere else in the tree
+                            else
+                            {
+                                if( selectedCluster.cluster._items > 1 )
+                                {
+                                    str = "Drill down";
+                                    mode0 = 0;
+                                }
+                                else
+                                {
+                                    str = "Outermost twigs";
+                                    mode0 = 3;
+                                }
+                            }
+                            
+                            final int mode = mode0;
+                            
+                            menuItem = new JMenuItem(str);
+                            if( (mode != 0 ) && (mode != 1) )
+                                menuItem.setEnabled(false);
+                                
+                            menuItem.addActionListener(new ActionListener()
+                            {
+                                public void actionPerformed(ActionEvent evt)
+                                {
+                                    if( mode == 0 )
+                                    {
+                                        selectedRoot = selectedCluster.cluster;
+                                        String[] drillWords = selectedCluster.getFreqWords();
+                                        String keyWordStr = new String(drillWords[0]+" "+drillWords[1]+" "+drillWords[2]);
+                                        
+                                        // to establish new clusterobj vector
+                                        constructed = false;   
+                                        repaint();
+					//update();                                  
+                                    }
+                                    if( mode == 1 )
+                                    {
+                                        selectedRoot = selectedRoot._parent;
+                                        constructed = false;
+                                        repaint();
+					//update();
+                                    }
+                                }
+                            });
+                            popup.add(menuItem);
+                            
+                            if( mode == 1 )
+                            {
+                                menuItem = new JMenuItem("Total undrill");
+                                menuItem.addActionListener(new ActionListener()
+                                {
+                                    public void actionPerformed(ActionEvent evt)
+                                    {
+                                        selectedRoot = root;
+                                        constructed = false;
+                                        //update();
+					repaint();
+                                    }
+                                });
+                                popup.add(menuItem);
+                            }
+                            
+                            popup.add(new JPopupMenu.Separator());
+                            int[] wordFreq = selectedCluster.cluster.getWordFreq();
+                            String[] words = getMostFrequentWords(wordFreq);
+                            
+                            selectedCluster.setFreqWords(words);
+                            
+                            menuItem = new JMenuItem(words[0]);
+                            menuItem.setEnabled(false);
+                            popup.add(menuItem);
+                            
+                            menuItem = new JMenuItem(words[1]);
+                            menuItem.setEnabled(false);
+                            popup.add(menuItem);
+                            
+                            menuItem = new JMenuItem(words[2]);
+                            menuItem.setEnabled(false);
+                            popup.add(menuItem);
+                            
+                            popup.validate();
+                            popup.setVisible(true);
+                        }
+                        
+                        popup.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
+                        mouseEvent.consume();
+                    }     
+                }  // of clustervect != null
+             } // of mouse moved
+        });
+        
+        setVisible(true);
+    }
+    
+    public void clear()
+    {
+        constructed = false;
+    }
+
+        
+    public String[] getMostFrequentWords(int[] wordFreq)
+    {
+        
+        
+        int max1Freq = 0;
+        int max1Index = 0;
+        int max2Freq = 0;
+        int max2Index = 0;
+        int max3Freq = 0;
+        int max3Index = 0;
+        
+        for( int i = 0; i < wordFreq.length; i++ )
+        {
+            if( wordFreq[i] > max3Freq )
+            {
+                if( wordFreq[i] > max2Freq )
+                {
+                    if( wordFreq[i] > max1Freq )
+                    {
+                        max3Freq = max2Freq;
+                        max3Index = max2Index;
+                        max2Freq = max1Freq;
+                        max2Index = max1Index;
+                        max1Freq = wordFreq[i];
+                        max1Index = i;
+                    }
+                    else
+                    {
+                        max3Freq = max2Freq;
+                        max3Index = max2Index;
+                        max2Freq = wordFreq[i];
+                        max2Index = i;
+                    }
+                }
+                else
+                {
+                    max3Freq = wordFreq[i];
+                    max3Index = i;
+                }
+            }
+                
+        }
+        
+        data = dataManager.getDataBlock();
+        String[] words = new String[3];
+        words[0] = data.words[max1Index];
+        words[1] = data.words[max2Index];
+        words[2] = data.words[max3Index];
+        return words;
+    }
+    
+    public void getDocObjs()
+    {
+       
+       Vector docVect = selectedCluster.cluster.getDocVect();
+       DocObj[] docs = new DocObj[docVect.size()];
+       
+       for( int i = 0; i < docVect.size(); i++ )
+       {
+            int index = ((Integer)docVect.elementAt(i)).intValue();
+            docs[i] = dataManager.getDocObjAt(index);
+       }    
+       
+       
+       if( !selectedCluster.getOrderStatus() )
+       {
+            QSortForDocObj qSort = new QSortForDocObj();
+            try
+            {   
+                 qSort.sort(docs);
+            }
+            catch (Exception e)
+            {}
+            
+            selectedCluster.setDocObjs(docs);
+            selectedCluster.setOrderStatus(true);
+       }
+       
+        
+    }
+    
+    public void updateHLinkPanel()
+    {
+       
+        String htmlDescription = selectedCluster.formatDocs();
+        parent.hLinkPanel.setText(htmlDescription);
+    }
+    
+    public void updateKeyWordTable()
+    {
+        parent.keywordPanel.update(selectedCluster);
+    }
+    
+    
+    public void setConstructed(boolean b)
+    {
+        constructed = b;
+    }
+    
+    
+    public void getData()
+    {
+        dataManager = currentMain.dataManager;
+        data = dataManager.getDataBlock();
+        SparseMatrix m = data.matrix;
+        
+        Vector idVect = new Vector();
+        Vector sammCluster =  dataManager.getSammonClusters();
+        
+        for( int i = 0; i < sammCluster.size(); i++ )
+        {
+            SammCluster cluster = (SammCluster)sammCluster.elementAt(i);
+            idVect.addAll(cluster.getIDs());
+        }
+        
+        for( int c = 0; c < idVect.size(); c++ )
+        {
+            Integer i = (Integer)idVect.elementAt(c);
+        }
+        
+        for (int c = 0; c< idVect.size();c++)
+        {
+          // the document id of the cth document
+          Integer i = (Integer)idVect.elementAt(c);
+        }
+        
+        //int [][] full = m.getExpanded(); 
+        float [][] full = m.getExpanded(); 
+        // the number of new clusters generated is either 20
+        // or if there are not many docs half the #docs
+               
+        int limit =  m.getRowCount();
+        
+        int clusterNum = 10;
+                      
+        Clustering clustering = new Clustering(dataManager,Clustering.COMPLETE_LINKAGE,
+					       full,m.getRowCount(),
+					       m.getColumnCount(),1,
+					       clusterNum,limit);
+          
+        root = clustering.getRoot();
+        selectedRoot = root;
+    }
+    
+    
+    public void update(Graphics g)
+    {
+	Graphics2D g2 = (Graphics2D)g;
+	
+	d = getSize();
+	int w = d.width;
+	int h = d.height;
+	
+	area = new Rectangle(d);
+	bi = (BufferedImage)createImage(w, h);
+	offGraphics2D = bi.createGraphics();
+	area.setLocation(w/2-50, h/2-25);
+	BasicStroke myStroke = new BasicStroke(2,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER);
+	
+	offGraphics2D.setColor(Color.white);
+	offGraphics2D.fillRect(0,0,d.width,d.height);
+		
+	if( !constructed )
+	    {
+		clusterVect = new Vector();
+		selectedCluster = null;
+		selectedClusterID = -1;
+	    }
+	
+	int initRad = 0;  
+	initBranchLength = (int)(Math.min(d.width,d.height)*0.12);
+	
+	Cluster cc = selectedRoot;
+	Point center = new Point(d.width/2,d.height/2);
+	int radius = (int)(Math.log(cc._items))* 3;
+	if( radius < 2 ) radius = 2;
+	
+	// all the leaf cluster ids underneath selected node
+	// to be painted bright red
+	Vector allSubNodeIDs = new Vector();
+	
+	if( !constructed ){
+	    ClusterObj rootObj = new ClusterObj(cc,center,radius,dataManager);
+	    clusterVect.addElement(rootObj);            
+	}
+	else{
+	    ClusterObj c = (ClusterObj)clusterVect.elementAt(0);
+	    c.setPosition(center);
+	    clusterVect.setElementAt(c,0);
+	    c = (ClusterObj)clusterVect.elementAt(0);
+	}
+	
+	int depth = 1;
+	TheInt ii = new TheInt(1);
+	
+	boolean selected = false;
+	
+	// used to paint the tree different colour under selected node
+	selectedDepth = -1;
+
+	if( cc._items > 1 ){
+	    double alpha = (double)Math.PI/Math.pow(2,depth);
+	    double theta = 0;
+	    
+	    if( (selectedClusterID == 0) )
+                {
+                    selected = true;                    
+                    selectedDepth = 0;
+                    offGraphics2D.setColor(Color.blue);
+                }
+
+	    paintPoke(offGraphics2D,cc._child_1,center.x,center.y,center,depth,theta,-alpha,initRad, selected, allSubNodeIDs, ii);
+	    paintPoke(offGraphics2D,cc._child_2,center.x,center.y,center,depth,theta,alpha,initRad, selected, allSubNodeIDs, ii);
+	}
+	
+	for( int i = 0; i < clusterVect.size(); i++ )
+            {
+                ClusterObj co = (ClusterObj)clusterVect.elementAt(i);
+                
+                // if leaf draw reddish
+                if( co.cluster._items == 1 )
+                {
+		    int r = co.getRad();
+                    offGraphics2D.setColor(new Color((float)1, (float)0.7, (float)0.7));
+                    offGraphics2D.fillArc(co.getX()-5,co.getY()-5,2*r,2*r,0,360);    
+                    offGraphics2D.setColor(Color.black);
+                    offGraphics2D.drawArc(co.getX()-5,co.getY()-5,2*r,2*r,0,360);    
+                }
+                else  // draw blueish
+                {
+                    offGraphics2D.setColor(Color.white);
+                    int r = co.getRad();
+                    offGraphics2D.fillArc(co.getX()-r,co.getY()-r,2*r,2*r,0,360);
+                    offGraphics2D.setColor(new Color((float)0.7, (float)0.7, (float) 1));
+                    offGraphics2D.drawArc(co.getX()-r,co.getY()-r,2*r,2*r,0,360);
+                }
+                               
+            }
+            
+            for( int i = 0; i < allSubNodeIDs.size(); i++ )
+            {
+                int value = ((Integer)allSubNodeIDs.elementAt(i)).intValue();
+                ClusterObj co = (ClusterObj)clusterVect.elementAt(value);    
+                // if leaf
+                if( co.cluster._items == 1 )
+                {
+		    int r = co.getRad();
+                    offGraphics2D.setColor(Color.red);
+                    offGraphics2D.fillArc(co.getX()-5,co.getY()-5,2*r,2*r,0,360);    
+                    offGraphics2D.setColor(Color.black);
+                    offGraphics2D.drawArc(co.getX()-5,co.getY()-5,2*r,2*r,0,360);    
+                }
+                else
+                {
+                    offGraphics2D.setColor(Color.white);
+                    int r = co.getRad();
+                    offGraphics2D.fillArc(co.getX()-r,co.getY()-r,2*r,2*r,0,360);
+                    offGraphics2D.setColor(Color.blue);
+                    offGraphics2D.drawArc(co.getX()-r,co.getY()-r,2*r,2*r,0,360);
+                }
+                
+                
+            }
+            
+            if( (selectedCluster != null)  && (selectedCluster.cluster._items > 1) )
+            {
+                offGraphics2D.setColor(Color.blue);
+                offGraphics2D.fillArc(selectedCluster.getX()-selectedCluster.getRad(),selectedCluster.getY()-selectedCluster.getRad(),2*selectedCluster.getRad(),2*selectedCluster.getRad(),0,360);
+		}
+	    
+	    g2.drawImage(bi, 0, 0, this);
+	constructed = true;
+    }
+
+           
+    public void update()
+    {
+	
+	if( !constructed )
+            {
+                clusterVect = new Vector();
+                selectedCluster = null;
+                selectedClusterID = -1;
+            }
+	
+	Dimension d = getSize();
+	offScreenSize = d;
+	if( d.width <= 0 ) return;
+
+	offScreen = createImage(d.width,d.height);
+	offGraphics = offScreen.getGraphics();
+            
+	Graphics2D offGraphics2D = (Graphics2D)offGraphics;
+	
+	offGraphics2D.setColor(Color.white);
+	offGraphics2D.fillRect(0,0,d.width,d.height);
+	offGraphics2D.setColor(new Color((float)0.7, (float)0.7, (float)1));
+	BasicStroke myStroke = new BasicStroke(2,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER);
+	offGraphics2D.setStroke(myStroke);	        
+	
+	int initRad = 0;  
+
+	Cluster cc = selectedRoot;
+	Point center = new Point(d.width/2,d.height/2);
+	int radius = (int)(Math.log(cc._items))* 3;
+	if( radius < 2 ) radius = 2;
+	
+	// all the leaf cluster ids underneath selected node
+	// to be painted bright red
+	Vector allSubNodeIDs = new Vector();
+	
+	if( !constructed )
+            {
+                ClusterObj rootObj = new ClusterObj(cc,center,radius,dataManager);
+                clusterVect.addElement(rootObj);            
+            }
+	else
+            {
+                ClusterObj c = (ClusterObj)clusterVect.elementAt(0);
+                c.setPosition(center);
+                clusterVect.setElementAt(c,0);
+                c = (ClusterObj)clusterVect.elementAt(0);
+            }
+	
+	int depth = 1;
+	TheInt ii = new TheInt(1);
+	
+	boolean selected = false;
+	
+	// used to paint the tree different colour under selected node
+	selectedDepth = -1;
+	
+	if( cc._items > 1 ){
+	    double alpha = (double)Math.PI/Math.pow(2,depth);
+	    double theta = 0;
+	    
+	    if( (selectedClusterID == 0) ){
+		selected = true;                    
+		selectedDepth = 0;
+		offGraphics2D.setColor(Color.blue);
+	    }
+	    
+	    paintPoke(offGraphics2D,cc._child_1,center.x,center.y,center,depth,theta,-alpha,initRad, selected, allSubNodeIDs, ii);
+	    paintPoke(offGraphics2D,cc._child_2,center.x,center.y,center,depth,theta,alpha,initRad, selected, allSubNodeIDs, ii);
+	}
+	
+	for( int i = 0; i < clusterVect.size(); i++ )
+            {
+                
+                ClusterObj co = (ClusterObj)clusterVect.elementAt(i);
+                
+                // if leaf draw reddish
+                if( co.cluster._items == 1 )
+		    {
+			int r = co.getRad();
+                    offGraphics2D.setColor(new Color((float)1, (float)0.7, (float)0.7));
+                    offGraphics2D.fillArc(co.getX()-5,co.getY()-5,2*r,2*r,0,360);    
+                    offGraphics2D.setColor(Color.black);
+                    offGraphics2D.drawArc(co.getX()-5,co.getY()-5,2*r,2*r,0,360);    
+                }
+                else  // draw blueish
+                {
+                    offGraphics2D.setColor(Color.white);
+                    int r = co.getRad();
+                    offGraphics2D.fillArc(co.getX()-r,co.getY()-r,2*r,2*r,0,360);
+                    offGraphics2D.setColor(new Color((float)0.7, (float)0.7, (float) 1));
+                    offGraphics2D.drawArc(co.getX()-r,co.getY()-r,2*r,2*r,0,360);
+                }
+                               
+            }
+            
+            for( int i = 0; i < allSubNodeIDs.size(); i++ )
+            {
+                int value = ((Integer)allSubNodeIDs.elementAt(i)).intValue();
+                ClusterObj co = (ClusterObj)clusterVect.elementAt(value);    
+                // if leaf
+                if( co.cluster._items == 1 )
+                {
+		    int r = co.getRad();
+                    offGraphics2D.setColor(Color.red);
+                    offGraphics2D.fillArc(co.getX()-5,co.getY()-5,2*r,2*r,0,360);    
+                    offGraphics2D.setColor(Color.black);
+                    offGraphics2D.drawArc(co.getX()-5,co.getY()-5,2*r,2*r,0,360);    
+                }
+                else
+                {
+                    offGraphics2D.setColor(Color.white);
+                    int r = co.getRad();
+                    offGraphics2D.fillArc(co.getX()-r,co.getY()-r,2*r,2*r,0,360);
+                    offGraphics2D.setColor(Color.blue);
+                    offGraphics2D.drawArc(co.getX()-r,co.getY()-r,2*r,2*r,0,360);
+                }
+                
+                
+            }
+            
+            if( (selectedCluster != null)  && (selectedCluster.cluster._items > 1) )
+            {
+                offGraphics2D.setColor(Color.blue);
+                offGraphics2D.fillArc(selectedCluster.getX()-selectedCluster.getRad(),selectedCluster.getY()-selectedCluster.getRad(),2*selectedCluster.getRad(),2*selectedCluster.getRad(),0,360);
+            }
+
+           repaint();
+           constructed = true;
+    }
+    
+    // theta is the angle in the polar representation
+    public void paintPoke(Graphics2D g, Cluster child, int x, int y, Point center, int d, double theta, double alpha,int r, boolean selected, Vector allSubNodeIDs, TheInt ii)
+    {
+	// translate parent cluster position to center
+	int x1 = x - center.x;
+	int y1 = y - center.y;
+	
+	// rotate cluster about origin by alpha to obtain intermediate tree point
+	int x_inter = (int)(x1 * Math.cos(alpha) + y1 * Math.sin(alpha)) + center.x;
+	int y_inter = (int)(x1 * - Math.sin(alpha) + y1 * Math.cos(alpha)) + center.y;
+
+	// rotate cluster about origin by theta
+	int x2 = (int)(x1 * Math.cos(-theta) + y1 * Math.sin(-theta));
+	int y2 = (int)(x1 * - Math.sin(-theta) + y1 * Math.cos(-theta));
+	
+	// extend radius
+	int dRad = (initBranchLength-d*10);
+	if( dRad <= 0 ) dRad = 10;
+	int x3 = x2 + dRad;
+	int y3 = y2;
+
+	// rotate back by (theta +/- alpha)
+	int x4 = (int)(x3 * Math.cos(theta+alpha) + y3 * Math.sin(theta+alpha));
+	int y4 = (int)(x3 * -Math.sin(theta+alpha) + y3 * Math.sin(theta+alpha));
+
+	// finally, add the center back again
+	int x_new = x4 + center.x;
+	int y_new = y4 + center.y;
+		
+	int startAngle =  (int)(180*theta/Math.PI);
+	int rangeAngle = (int)(180*alpha/Math.PI);
+			
+	boolean hitSelectedNode = false;
+	if( selectedClusterID != -1 )
+	{
+	    Object obj1 = (Object)child;
+	    ClusterObj selObj = (ClusterObj)clusterVect.elementAt(selectedClusterID);
+	    Object obj2 = (Object)selObj.cluster;
+	    hitSelectedNode = obj1.equals(obj2);
+	}
+	
+	int currentClusterID = -1;
+    
+	if( selectedClusterID != -1 )
+	    {
+		int w = 0;
+		boolean found = false;
+		while( w < clusterVect.size() && !found )
+		    {
+			Object obj1 = (Object)child;
+			ClusterObj obj = (ClusterObj)clusterVect.elementAt(w);
+			Object obj2 = (Object)obj.cluster;
+			if( obj1.equals(obj2) )
+			    {
+				currentClusterID = w;
+				found = true;
+			    }
+			w++;
+		    }
+	    }
+	
+	if( (d <= selectedDepth) && (!hitSelectedNode) )
+	    {	        
+		g.setColor(new Color( (float) 0.7, (float) 0.7, (float) 1)); 
+		selected = false;
+		
+	    }
+	
+	g.drawArc(center.x-r,center.y-r,2*r,2*r,startAngle,rangeAngle);
+	g.drawLine(x_inter,y_inter, x_new, y_new);
+	
+	if( hitSelectedNode )
+	    {	 
+		selectedDepth = d;
+		selected = true;
+		g.setColor(Color.blue);
+		Vector docVector = child.getDocVect();
+	    }
+	
+	g.setColor(selected ? Color.blue : new Color((float) 0.7, (float) 0.7, (float) 1) );
+	
+	int radius = (int)(Math.log(child._items))* 3;
+	if( radius < 2 ) radius = 2;
+
+	if( child._items == 1 )
+	    {
+		Vector docVector = child.getDocVect();
+		radius = Math.max(5,(int)Math.sqrt(docVector.size())*2);
+	    }
+	
+  	d++;
+	r = (int)(Math.sqrt((x_new-center.x)*(x_new-center.x) + (y_new-center.y)*(y_new-center.y)));
+	
+	Point P = new Point(x_new,y_new);
+	if( !constructed )
+	    {	    
+		ClusterObj cObj = new ClusterObj(child,P,radius,dataManager);
+		clusterVect.addElement(cObj);            
+	    }
+	else
+	    {
+		ClusterObj c = (ClusterObj)clusterVect.elementAt(ii.val);
+		c.setPosition(P);
+		clusterVect.setElementAt(c,ii.val);
+	    }
+	ii.val++;
+	
+   	double new_alpha = (double)alpha/2;
+   	
+   	if( selected )
+   	    allSubNodeIDs.addElement(new Integer(currentClusterID) );
+	
+	if( child._items > 1 && d < 6 ){
+	    paintPoke(g,child._child_1,x_new,y_new,center,d,theta+alpha,new_alpha,r,selected,allSubNodeIDs,ii);
+	    paintPoke(g,child._child_2,x_new,y_new,center,d,theta+alpha,-new_alpha,r,selected, allSubNodeIDs,ii);
+	}
+
+	// we have reached the outermost twigs
+	else{
+	    int[] wordFreq = child.getWordFreq();
+	    String[] words = getMostFrequentWords(wordFreq);
+	    
+	    Font f = new Font("Courier",Font.PLAIN,16);
+	    FontMetrics fm = g.getFontMetrics(f);
+	    int ascent = fm.getAscent();
+	    int height = fm.getHeight();
+	    int wordLength = fm.stringWidth(words[0]);
+	    g.setFont(f);
+	    
+	    if( x_new > center.x ){
+		g.drawString(words[0],x_new + Math.max(radius,5) + 10,y_new + Math.max(radius,5));
+	    }
+	    else{
+		g.drawString(words[0],x_new - Math.max(radius,5) - 10 - wordLength,y_new + Math.max(radius,5));
+	    }
+	}
+    }
+    
+    
+    public void paintTree(Cluster cc, Graphics g, int x, int y, int dist)
+    {
+        int dy = 10;		
+        int rad = 2;
+        int dmt = 4;
+	g.setColor(Color.white);
+	g.fillArc(x-rad,y-rad,dmt,dmt,0,360);
+	g.setColor(Color.blue);
+	g.drawOval(x-rad,y-rad,dmt,dmt);
+	String desc = String.valueOf(cc._items);
+	g.drawString(desc,x-(int)rad/2 - (desc.length()-2)*3,y + (int)rad/2 - 2);
+	
+	if( (cc._items > 1) )
+	    {
+		g.drawLine(x,y+rad,x-dist,y-rad+dy);
+		g.drawLine(x,y+rad,x+dist,y-rad+dy);
+		if( cc._child_1 != null ) paintTree(cc._child_1, g, x - dist, y + dy, (int)dist/2);
+		if( cc._child_2 != null ) paintTree(cc._child_2, g, x +  dist, y + dy, (int)dist/2);
+	    }
+    }
+    
+    public void paint(Graphics g)
+    {
+       update(g);
+    }
+    
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/TriangleIndex.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/TriangleIndex.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/dendro/TriangleIndex.java	(revision 21054)
@@ -0,0 +1,85 @@
+package vishnu.testvis.dendro;
+
+public class TriangleIndex
+{
+	short [] i_ind;    /* from index to i */
+	short [] j_ind;    /* from index to j */
+
+
+
+	TriangleIndex(int matrix_rows)
+	{
+
+  		short i, j;
+        int theIndex;
+
+  		int num_of_elements = matrix_rows * (matrix_rows - 1) / 2;
+
+  		/* Allocate space for lookup tables */
+  		i_ind = new short[num_of_elements];
+  		j_ind = new short [num_of_elements];
+
+	  	for (i = 1; i < matrix_rows; i++)
+    	{
+      		for (j = 0; j < i; j++)
+			{
+	  			theIndex = TriangleIndex.index (i, j);
+				i_ind[theIndex] = i;
+	  			j_ind[theIndex] = j;
+			}
+    	}
+	}
+
+	int row_size (int size)
+	{
+  /* Don't forget that we actually start counting in the second row, since the first one is assumed to be all zeroes */
+  		int i = 1;
+  		int sum = 0;
+
+  		while (sum != size)
+    		sum += i++;
+  		return i;
+	}
+
+	static int index (int i, int j)
+	{
+  /* Assumes lower triangular matrix, i.e. i>j */
+
+  		int temp;
+
+  /* The matrix in this case is symmetric so (i, j) = (j, i) */
+  		if (i < j)
+    	{
+      		temp = i; i = j; j = temp;
+    	}
+
+  		if (i != j)
+    		return (i*i-i)/2 + j;
+    		//(i - 1) * (i - 2) / 2 + i - 1 + j;
+  		else         /* The elements on the diagonal are all 0 */
+    	{
+      		return -1; /* so just return the dummy value -1      */
+    	}
+	}
+	static void print (double[] matrix, int row_size)
+	{
+  		int i, j, theIndex;
+
+  		for (i = 0; i < row_size; i++)
+    	{
+      		for (j = 0; j < row_size; j++)
+			{
+	  			if (i <= j)
+	    			System.out.print(" ");
+	  			else
+	    		{
+	      			theIndex = index (i, j);
+	      			System.out.print(matrix[theIndex] + " ");
+	    		}
+			}
+      		System.out.println();
+    	}
+	}
+
+}
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/evaluate/Logging.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/evaluate/Logging.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/evaluate/Logging.java	(revision 21054)
@@ -0,0 +1,107 @@
+
+/**
+ * Title:        Visualisation<p>
+ * Description:  An implementation of some search result visualisations
+ * <p>
+ * Copyright:    Copyright (c) Matthew Carey<p>
+ * Company:      <p>
+ * @author Matthew Carey
+ * @version 1.0
+ */
+package vishnu.testvis.evaluate;
+import vishnu.testvis.object.*;
+import vishnu.testvis.visual.*;
+import java.io.*;
+import java.util.*;
+import java.text.*;
+
+public class Logging
+{
+    private Vishnu displayFrame;
+    private String _id;
+    String filename;
+    PrintWriter outlog;
+    long startmilli;
+
+    public Logging(String id, String path, Vishnu d)
+    {
+        displayFrame = d;
+        DateFormat longTime = DateFormat.getDateTimeInstance(DateFormat.FULL,DateFormat.FULL);
+        DateFormat shortTime = DateFormat.getDateTimeInstance(DateFormat.SHORT,DateFormat.SHORT);
+        Date todayDate = new Date();
+        String node = replace_chars(
+            replace_chars(id + "_" + shortTime.format(new Date()) + ".xml"," ","_"),
+            "/:","-");
+        if (null==path)filename = node;
+        else filename = path + node;
+        System.out.println(filename);
+        //Properties p = d.getProperties();
+        _id = id;
+        long Time;
+        try
+        {
+            /*outlog =
+                new PrintWriter(
+                    new BufferedWriter(
+                        new FileWriter(filename)));*/
+            File target = new File(filename);
+		    FileWriter fw = new FileWriter(target);
+		    outlog = new PrintWriter(fw,true);
+		
+		    outlog.println("<LOG>");
+            startmilli=System.currentTimeMillis();
+            log("ID",_id);
+            log("DATE_AND_TIME",longTime.format(todayDate));
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    private String replace_chars(String target,String oldch, String newch)
+    {
+        StringTokenizer tokenizer = new StringTokenizer(target,oldch);
+        StringBuffer buff = new StringBuffer();
+
+        while(tokenizer.hasMoreElements())
+        {
+            if(buff.length()>0)
+                buff.append(newch);
+            buff.append(tokenizer.nextToken());
+        }
+        return buff.toString();
+    }
+    
+    public void log(String tag, String value)
+    {
+        String out ="<" + tag + " MILLISECS = \"" +
+            Long.toString(System.currentTimeMillis()-startmilli) + "\">" +  value + "</" + tag + ">";
+        outlog.println(out);
+    }
+    
+
+    public void log(String tag)
+    {
+        String out ="<" + tag + " MILLISECS = \"" +
+            Long.toString(System.currentTimeMillis()-startmilli) + "\"/>";
+        outlog.println(out);
+    }
+
+    public void endsection(String tag)
+    {
+        outlog.println("</"+tag+">");
+    }
+
+    public void startsection(String tag)
+    {
+        outlog.println("<"+tag+">");
+    }
+
+    public void logout()
+    {
+        log("LOGOUT",_id);
+        outlog.println("</LOG>");
+        outlog.close();
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/evaluate/Queries.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/evaluate/Queries.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/evaluate/Queries.java	(revision 21054)
@@ -0,0 +1,338 @@
+package vishnu.testvis.evaluate;
+import java.io.*; 
+import java.util.*;
+
+public class Queries
+{
+    Vector queryID;
+    Vector queryString;
+    Vector relevance[];
+
+    public Queries()
+    {
+        getQueries();
+        
+        // only of the queries just read
+        getRelevanceData();
+        //printRelevance();
+    }
+    
+    private void getRelevanceData()
+    {
+        
+        relevance = new Vector[queryID.size()];
+        for( int i = 0; i < queryID.size(); i++ )
+        {
+            relevance[i] = new Vector();            
+        }
+        
+        try
+        {
+		    FileReader file = new FileReader("qrels.dat");
+		    BufferedReader buff = new BufferedReader(file);
+		    
+		    String line = buff.readLine();
+		    //System.out.println(line);
+		    		    
+		    StringTokenizer st = new StringTokenizer(line);
+		    String id = st.nextToken();
+		    //System.out.println(id);
+		    int index = 0;
+		    if( queryID.contains(id) )
+		    {
+		        index = queryID.indexOf(id);
+		        //System.out.println("Contained at position " + index);
+		    }
+		    
+		    String docStr = st.nextToken();
+		    //System.out.println(docStr);
+		    relevance[index].addElement(docStr);
+		    boolean eof = false;
+		    			
+		    while( !eof ) 
+		    {
+		        line = new String();
+			    line = buff.readLine();	
+			    
+			    if( line == null || line == "" )
+			        eof = true;
+			    else
+			    {
+			        st = new StringTokenizer(line);
+	    	    	id = st.nextToken();
+		            if( queryID.contains(id) )
+		            index = queryID.indexOf(id);
+		            docStr = st.nextToken();
+		            relevance[index].addElement(docStr);
+		            st = new StringTokenizer(line);
+		        }
+	        }
+	        buff.close();	
+		}
+		catch( IOException e)
+		{
+			System.out.println("Error -- " + e.toString());
+		}    
+    }
+    
+    
+    private void getQueries()
+    {
+        queryID = new Vector();
+        queryString = new Vector();
+        
+        try
+		{
+		    FileReader file = new FileReader("queries.dat");
+		    BufferedReader buff = new BufferedReader(file);
+		    
+		    String line = buff.readLine();
+		    //System.out.println(line);
+		    		    
+		    StringTokenizer st = new StringTokenizer(line);
+		    
+		    String keywords = new String();
+		    
+		    while( st.hasMoreTokens() )
+		    {
+		        String strTemp = st.nextToken();
+		        
+		        // check whether token is last token
+		        // assume last token is the query number
+		        if( !st.hasMoreTokens() )
+		        {
+		            queryID.addElement(strTemp);
+		            keywords = keywords.trim();
+		            queryString.addElement(keywords);
+		        }
+		        else
+		        {
+		            keywords += " " + strTemp;
+		        }
+		    }
+		   	    
+            boolean eof = false;
+			
+		    while( !eof )
+		    {
+		        line = new String();
+			    line = buff.readLine();	
+			    //System.out.println(line);
+			    if( line == null || line == "" )
+			        eof = true;
+			    else
+			    {   
+	    		    keywords = new String();
+		    
+		            st = new StringTokenizer(line);
+		            while( st.hasMoreTokens() )
+		            {
+		                String strTemp = st.nextToken();
+		                
+		                // check whether token is last token
+		                // assume last token is the query number
+		                if( !st.hasMoreTokens() )
+		                {
+		                    queryID.addElement(strTemp);
+		                    queryString.addElement(keywords);
+		                    //System.out.println(keywords);
+		                }
+		                else
+		                {
+		                    keywords += " " + strTemp;
+		                }
+		            }
+        		}
+	        }
+	        buff.close();	
+		}
+		catch( IOException e)
+		{
+			System.out.println("Error -- " + e.toString());
+		}
+	}
+        
+    public Object[] getQueryObjectArray()
+    {
+        Object[] objArray = new Object[queryString.size()];
+	    for( int i = 0; i < queryString.size(); i++ )
+		{
+		    objArray[i] = queryString.elementAt(i);
+		}
+		return objArray;
+    }
+        
+    public Vector getQueryIDs()
+    {
+        return queryID;
+    }
+    
+    
+    public void printRelevance()
+    {
+        for( int i = 0; i < relevance.length; i++ )
+        {
+            for( int j = 0; j < relevance[i].size(); j++ )
+            {
+                System.out.print((String)relevance[i].elementAt(j));
+            }
+            System.out.println();
+            System.out.println();
+        }
+    }
+    
+    // returns vector with first element = total of relevant followed
+    // by precision at 10, 20...
+    public Vector getPerformance(String query, String rankedTrecIDs[])
+    {
+        int num = 0;
+        Vector precisionAt = new Vector();
+        int index = queryString.indexOf(query);
+        //System.out.println("Query number " + index + " selected");
+        if( index == -1 )
+            return precisionAt;  // return empty vector
+                
+        boolean relevant[] = new boolean[rankedTrecIDs.length];
+        //System.out.println("Size of ranked list: " + rankedTrecIDs.length);
+        
+        for( int i = 0; i < rankedTrecIDs.length; i++ )
+        {
+            // determine for each doc in the list whether it is relevant
+            if( relevance[index].contains(rankedTrecIDs[i]) )
+            {
+                relevant[i] = true;
+                num++;
+            }
+            else 
+                relevant[i] = false;
+                
+            // precision at 10,20,30,40,50    
+            if( (i+1) % 10 == 0 && (i+1)/10 < 6)
+            {
+                Integer prec = new Integer(Math.round(((float)num/(i+1)*100)));
+                
+                //System.out.println((double)num/(i+1));
+                precisionAt.addElement(prec);
+                
+            }    
+        }
+        
+        precisionAt.addElement(new Integer(relevance[index].size()));
+        precisionAt.addElement(new Integer(num));
+        //System.out.println(num + " relevant docs from a total of " + relevance[index].size() + " have been found");
+        
+        return precisionAt;
+  }
+    
+}
+
+/*		int totalRel = no_in_cat[categories.indexOf(queryId)]-1; // number of relevant
+		int relSoFar = 0;
+		
+		int pre_vs_rec[] = new int[11];
+			
+				
+		// trec method to compute average precision
+		// compute P for relevant images only
+		// P(r0) = max(P(r >= r0))
+
+		
+		// the following array stores pre/rec for every relevant, the first 
+		// entry is precision for the 0th item = 0.
+		// maximum size of array = items.length
+		// actual size of array = totalRel
+		
+		int recall = 0;
+		int precision = 0;
+		
+		
+		// begin of Rueger method
+		
+		relSoFar = totalRel;
+            
+	   	    // i  = 1 because target img is at zero!!!
+	   	    int maxPrecision = 0;
+	   	    int cutL = 10;
+		    for( int i = no_of_items -1; i > 0; i-- )
+			{
+				String listId = new
+			    String((String)cat_of_item.elementAt((int)list[i]));
+				if( listId.equals(queryId) )
+			    {
+			        recall = (int)(100*relSoFar)/totalRel;
+			        
+			        precision = (int)(100*relSoFar)/i;
+			        
+
+			        double cut = (double)(recall)/10;
+			        
+			        //if( r == 6 ) System.out.print(recall + " " + precision + ":");
+			        			        
+                    // if hit the next cut-off cutL, then set cut-off value to whatever 
+                    // is maximum
+			        if( cut == cutL )
+			        {
+			            //System.out.print("eq ");
+			            if( precision > maxPrecision )
+			            {
+			              //  System.out.print("eq-cm(" + precision + ") ");
+			                maxPrecision = precision;
+			            }
+                        
+                        //System.out.print("eq-ap(" + maxPrecision + ") ");
+			            pre_vs_rec[cutL] = maxPrecision;
+			            cutL--;
+			        }
+			        
+			        // if moved to the left of cut-off, the cut-off value (and those
+			        // lying between it and the last one set) is given the 
+			        // current maxPrecision
+			        else if( cut < cutL )   
+			        {
+			            for( int c = (int)cut+1; c < cutL+1; c++ )
+			            {
+			                pre_vs_rec[c] = maxPrecision;
+			            }
+			            
+			            
+			            if( precision > maxPrecision )
+			            {
+			                maxPrecision = precision;
+			            }
+			            
+			            cutL = (int)cut;
+    
+			        }
+		
+		            
+			        
+			        // just in case cut > cutL, we need still to update maxPrecision
+			        if( precision > maxPrecision )
+			        {
+			            maxPrecision = precision;
+			        }
+			        relSoFar--;
+			    
+			    }
+			    // if not relevant, nothing happens
+		    }
+		    
+		    for( int z = 0; z < cutL+1; z++ )
+		    {
+		        pre_vs_rec[z] = maxPrecision;
+		    }
+		    	    
+            // end of Rueger method
+		
+		// average precision for this query                                                     
+		double area = 0;
+		for(int j = 0; j < 10; j++ )
+		{
+			area += (pre_vs_rec[j]+pre_vs_rec[j+1])/2* 0.1;
+		}
+			
+		Performance p = new Performance(area, pre_vs_rec, queryId);
+		return p;
+  
+	}
+*/
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/CacheManager.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/CacheManager.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/CacheManager.java	(revision 21054)
@@ -0,0 +1,169 @@
+package vishnu.testvis.object;
+import java.io.*;
+import java.util.*;
+import vishnu.datablock.*;
+
+
+public class CacheManager
+{
+    
+    int cacheSize;
+    Vector file_names_dat;
+    Vector file_names_doc;
+        
+    public CacheManager()
+    {
+        //getCache();
+    }
+    
+    private void getCache()
+    {
+        Properties p = System.getProperties();
+        String currentDir = p.getProperty("user.dir");
+        File cache_dir = new File(currentDir);
+        file_names_dat = new Vector();
+        file_names_doc = new Vector();
+        
+        String fileArr[] = null;
+        if( cache_dir.exists() )
+        {
+        
+            fileArr = cache_dir.list(new FilenameFilter()
+            {
+                public boolean accept(File dir, String name)
+		        {
+		            return ( name.endsWith(".dat") || name.endsWith(".doc") );
+		        }
+	        });
+	    }
+	    else 
+	    {
+	        System.out.println("Directory does not exist");
+	        System.exit(0);
+	    }
+	    
+	    for( int i = 0; i < fileArr.length; i++ )
+	    {   
+	        if( fileArr[i].endsWith(".dat") )
+	            file_names_dat.addElement(fileArr[i]);
+	        if( fileArr[i].endsWith(".doc") )
+	            file_names_doc.addElement(fileArr[i]);
+	    }	    
+    }
+           
+    public void addDataItem(DataBlock data, String key)
+    {   
+        String fileName = new String(key +".dat");
+        file_names_dat.addElement(fileName);
+        
+        try
+        {
+            FileOutputStream ostream = new FileOutputStream(fileName);
+            ObjectOutputStream out = new ObjectOutputStream(ostream); 
+            out.writeObject(data);
+            ostream.close();                
+        }
+        catch (FileNotFoundException ex) {System.out.println("File not found exception");}
+	    catch (IOException ex) {System.out.println("Error when writing to raw_imagebase");}
+	    catch (Exception ex) {System.out.println(ex);}
+	    
+    }
+    
+     public void addDocItem(Vector doc, String key)
+    {   
+        String fileName = new String(key +".doc");
+        file_names_doc.addElement(fileName);
+        
+        try
+        {
+            FileOutputStream ostream = new FileOutputStream(fileName);
+            ObjectOutputStream out = new ObjectOutputStream(ostream); 
+            out.writeObject(doc);
+            ostream.close();                
+        }
+        catch (FileNotFoundException ex) {System.out.println("File not found exception");}
+	    catch (IOException ex) {System.out.println("Error when writing to raw_imagebase");}
+	    catch (Exception ex) {System.out.println(ex);}
+	    
+    }
+   
+        
+    public boolean containsData(String query)
+    {
+        String path = new String(query+".dat");
+        
+        if( file_names_dat.contains(path) )
+            return true;
+            else
+            return false;
+    }
+    
+    public boolean containsDoc(String docName)
+    {
+        String path = new String(docName+".doc");
+        
+        if( file_names_doc.contains(path) )
+            return true;
+            else
+            return false;
+    }
+  
+    
+    public Vector getDoc(String docName)
+    {
+        String filename = new String(docName + ".doc");
+        Vector doc = null;
+        
+        try
+        {
+            FileInputStream istream = new FileInputStream(filename);
+            ObjectInputStream in = new ObjectInputStream(istream);
+            doc = (Vector)in.readObject();        
+        }
+    	catch (FileNotFoundException ex) {System.out.println("File not found exception");}
+	    catch (IOException ex) {System.out.println("Error when writing to raw_imagebase");}
+	    catch (Exception ex) {System.out.println(ex);}
+	    
+        return doc;   
+    }
+    
+    
+ 
+    public DataBlock getData(String query)
+    {
+        String filename = new String(query + ".dat");
+        DataBlock data = null;
+        
+        try
+        {
+            FileInputStream istream = new FileInputStream(filename);
+            ObjectInputStream in = new ObjectInputStream(istream);
+            data = (DataBlock)in.readObject();        
+        }
+    	catch (FileNotFoundException ex) {System.out.println("File not found exception");}
+	    catch (IOException ex) {System.out.println("Error when writing to raw_imagebase");}
+	    catch (Exception ex) {System.out.println(ex);}
+	    
+        return data;   
+    }
+    
+    public int getSize()
+    {
+        return file_names_dat.size();
+    }
+    
+    public Vector listCache()
+    {
+        return file_names_dat;
+    }
+    
+    public void printMe()
+    {
+        for( int i = 0; i < file_names_dat.size(); i++ )
+        {
+            String object = (String)file_names_dat.elementAt(i);
+            System.out.println(object);
+        }
+    }
+  
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/ClusterGroupObj.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/ClusterGroupObj.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/ClusterGroupObj.java	(revision 21054)
@@ -0,0 +1,69 @@
+/**
+ * @(#)ClusterGroupObj.java  12/12/99
+ * @author Peter Au
+ * All rights reserved.
+ */
+
+package vishnu.testvis.object;
+
+public class ClusterGroupObj
+{
+    ClusterObj[] clusters;  //-- list of ClusterObj ref
+    String label;  //-- a general description for the group (take the highest related word)
+    private int groupSize;  //-- total number of docs in the group
+    public ClusterGroupObj(ClusterObj[] c, String ls)
+    {
+        //System.out.println("ClusterGroupObject constructor called label " + ls);
+        //System.out.flush();
+        clusters=c; label=ls;
+        groupSize=0;
+        for (int i=0;i<clusters.length;i++)
+      		groupSize+=clusters[i].getDocsSize();
+	}
+
+
+ public String getLabels()
+ {
+   return label;
+ }
+
+
+  public String getLabels_toString()
+  {
+    return label;
+  }
+/*
+  	String tmp="";
+  	for (int i=0;i<label.length;i++) tmp = tmp + " " + label[i];
+  	return tmp;
+  }
+*/
+  /**
+   * @return the total number of documents in the group
+   */
+  public int getGroupSize(){return groupSize;}
+
+  /**
+   * @return the number of clusters in the group
+   */
+  public int getClustersSize(){return clusters.length;}
+
+
+  /**
+   * @return the clusters list
+   */
+  public ClusterObj[] getClusters(){return clusters;}
+
+
+  public String getInfo(){
+  	return "No of clusters: "+ String.valueOf(clusters.length)+ " totally contains: "+
+  		String.valueOf(getGroupSize())+" docs\nGroup Label: "+getLabels_toString()+"\n";
+  }
+
+  public void showInfo(){
+    System.out.print(getInfo());
+    for (int i = 0; i<getClustersSize(); i++)
+      System.out.print( i+")  "+clusters[i].getInfo());
+  }
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/ClusterObj.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/ClusterObj.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/ClusterObj.java	(revision 21054)
@@ -0,0 +1,77 @@
+/**
+ * @(#)ClusterObj.java  12/12/99
+ * @author Peter Au
+ * All rights reserved.
+ */
+
+package vishnu.testvis.object;
+import vishnu.datablock.*;
+import java.util.Vector;
+
+public class ClusterObj
+{
+    DocObj[] _docs;		//-- list of DocObj ref
+    String _label;		//-- (top 3-5? related words)
+    Point2D [] _sammon;			//-- 2D mapping for the centroids
+    DataManager _data;
+    public ClusterObj(DocObj[] d, String t, Point2D [] sammon, DataManager dm)
+    {
+        _docs=d; _label=t;
+        _sammon = sammon;
+        _data = dm;
+    }
+  /**
+   * @return the number of documents in the cluster
+   */
+  public int getDocsSize(){return _docs.length;}
+
+  public String getLabel(){return _label;}
+
+  public String getLabel1(){return getDocsSize() + " documents found";}
+  public String getLabel2(){return getLabel();}
+
+
+  public String getInfo(){
+    return getDocsSize() + " documents found\n" + getLabel();
+  }
+
+
+  /**
+   * This returns a list of list of documents. The inner list of documents represents
+   * which documents have the corresponding related word
+   * pre: _docs can't be null
+   * @return a vector (same length as the related words) of
+   *         vectors (arbitrary length)
+   *
+   */
+  public Vector getHitDocFreq()
+  {
+    int s = _data.getWordCount();
+    Vector rwVec = new Vector(s);
+    for(int i=0;i<s;i++)
+    {
+      rwVec.addElement(new Vector(0));
+      for(int j=0;j<_docs.length;j++)
+      {
+        if ( _docs[j].getWeights(i) > 0 )
+          ((Vector) rwVec.elementAt(i)).addElement(_docs[j]);
+      }
+    }
+    return rwVec;
+  }
+
+
+  public String formatDocs()
+  {
+    int l = getDocsSize();
+    String s = "<html><font face=Arial size=-1> "+ l +" documents found\n";
+    for(int i=0;i<l;i++)
+    {
+      s += _docs[i].format4Fields();
+    }
+    s += "</font></html>\n";
+    return s;
+  }
+
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/DataManager.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/DataManager.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/DataManager.java	(revision 21054)
@@ -0,0 +1,627 @@
+package vishnu.testvis.object;
+
+import vishnu.datablock.*;
+import vishnu.testvis.visual.*;
+import vishnu.testvis.dendro.*;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.io.*;
+import java.net.*;
+
+import vishnu.testvis.sammon.*;
+import vishnu.util.Sammon;
+import vishnu.cluster.Clustering;
+import vishnu.testvis.browser.*;
+
+public class DataManager
+{
+    private Vishnu displayFrame;
+    private DataBlock data = null;
+    private CacheManager cache = null;
+    public Vector collections;
+    public Collection currentCollection = null;
+    public String currentView = null;
+    /** Labels for the clusters needed by tree map,
+     *  generated in this class */
+    private String [] clusterLabels = null;
+    /** vertical array of vectors representing
+     *  the documents that word x appears in */
+    private int [] clustersBigWord;
+    //private Vector [] wordDocuments;
+    /** ClusterObjs as used in Tree map */
+    private DocObj [] docObjs;
+    private ClusterObj [] clusterObjs = null;
+    private ClusterGroupObj [] clusterGroups = null;
+    private int[][] sommClusterWords;
+    private Vector sammonClusters;
+
+    private String engine_type = "Engine";
+    
+    /** Set gsdl state */
+    public void setEngineType(String engine) {
+	engine_type = engine;
+    }
+    /** Generate labels for the clusters needed by tree map,
+     *  pre: data is initialised
+     */
+
+    private void setUpClusterLabels()
+    {
+        int [] wordFreqs = new int [data.words.length];
+        StringBuffer sbuff= new StringBuffer(20);
+        clustersBigWord = new int[data.clusters.length];
+        clusterLabels= new String[data.clusters.length];
+        sommClusterWords= new int [data.clusters.length][4];
+
+        for(int c=0;c<data.clusters.length;c++)
+        {
+            for(int d=0; d < data.words.length; d++) wordFreqs[d]=0;
+
+            for(int d=0; d < data.clusters[c].size(); d++)
+            {
+                Integer n = (Integer) data.clusters[c].elementAt(d);
+                int docNo = n.intValue();
+                SparseNode row = data.matrix.getRow(docNo);
+                while( row!= null)
+                {
+                    wordFreqs[row.getColumn()]++;
+                    row = row.getNext();
+                }
+            }
+
+            int col1 = -1, c1val = 0;
+            int col2 = -1, c2val = 0;
+            int col3 = -1, c3val = 0;
+            int col4 = -1, c4val = 0;
+            for(int d=0; d < data.words.length; d++)
+            {
+                if(c1val < wordFreqs[d])
+                {
+                    col4=col3;
+                    col3=col2;
+                    c3val=c2val;
+                    col2=col1;
+                    c2val=c1val;
+                    col1=d;
+                    c1val=wordFreqs[d];
+                }
+                else if(c2val < wordFreqs[d])
+                {
+                    col4=col3;
+                    col3=col2;
+                    c3val=c2val;
+                    col2=d;
+                    c2val=wordFreqs[d];
+                }
+                else if(c3val < wordFreqs[d])
+                {
+                    col4=col3;
+                    col3=d;
+                    c3val=wordFreqs[d];
+                }
+                else if(c4val < wordFreqs[d])
+                {
+                    col4=d;
+                    c4val=wordFreqs[d];
+                }
+
+            } // for (each document in the cluster)
+           if(col1 > -1)
+           {
+                sbuff.append(data.words[col1]);
+                if(col2 > -1)
+                {
+                    sbuff.append(" ");
+                    sbuff.append(data.words[col2]);
+                    if(col3 > -1)
+                    {
+                        sbuff.append(" ");
+                        sbuff.append(data.words[col3]);
+                    }  // if
+                } // if
+                clusterLabels[c] = new String(sbuff);
+                sbuff.setLength(0);
+            } //if
+            else
+                clusterLabels[c] = new String("<empty>");
+
+            clustersBigWord[c] = col1;
+            sommClusterWords[c][0] = col1;
+            sommClusterWords[c][1] = col2;
+            sommClusterWords[c][2] = col3;
+            sommClusterWords[c][3] = col4;
+        } // for (each cluster)
+    } // end of setUpClusterLabels()
+    
+
+    private void setUpDocObjs()
+    {
+        docObjs = new DocObj[data.docs.length];
+        for(int c=0;c<data.docs.length;c++)
+            docObjs[c] = new DocObj(c, data.descriptions[c], data.matrix.getRow(c), data.docs[c]);
+    }
+    
+    public DocObj getDocObjAt(int index)
+    {
+        return docObjs[index];
+    }
+
+    public int getClusterGroupsSize()
+    {
+        return clusterGroups.length;
+    }
+
+    public ClusterGroupObj getClusterGroup(int i)
+    {
+        return clusterGroups[i];
+    }
+    private void setUpClusterObjs()
+    {
+        clusterObjs = new ClusterObj[data.clusters.length];
+        for(int c=0;c<data.clusters.length;c++)
+        {
+            DocObj [] dObjArray = new DocObj[data.clusters[c].size()];
+            for(int d=0; d<data.clusters[c].size();d++)
+            {
+                Integer n = (Integer)data.clusters[c].elementAt(d);
+                dObjArray[d] = docObjs[n.intValue()];
+            }
+            clusterObjs[c]= new ClusterObj(dObjArray,clusterLabels[c],data.sammon,this);
+        }
+        //ClusterObj(DocObj[] d, String t, Point2D sammon, DataManager dm)
+        // !!!!remember to comment out this loop when it works
+        //for(int c=0;c<data.clusters.length;c++)
+        //    System.out.println("In cluster " + c  + clusterObjs[c].getDocsSize() + " documents ");
+    }
+    /** This for tree map as it needs clusters of clusters
+     * this is a cheap and probably not very satisfactory solution
+     * as all I am doing is taking clusters with the common most significant
+     * word and putting them in groups.
+     */
+    private void setUpClusterGroupObjs()
+    {
+        int count = data.clusters.length;
+        int num=0, i,j;
+        int [] sizes = new int[count];
+        for(i=0;i < count;i++)
+            sizes[i]=1;
+
+        // contains #supercluster clusterList objects, each a list of clusters
+        Vector groupList = new Vector(count);
+        
+        // contains #supercluster BigWords for each supercluster
+        Vector wordList = new Vector(count);
+        for(i=0;i < count;i++)
+        {
+            if(sizes[i]>0)
+            {
+                Vector clusterList = new Vector(count);
+                clusterList.addElement(clusterObjs[i]);
+                int bigWord = clustersBigWord[i];
+                for (j = i+1; j < count; j++)
+                    if (clustersBigWord[j] == bigWord)
+                    {
+                        sizes[i]+=sizes[j];
+                        if(sizes[j]>0)
+                            clusterList.addElement(clusterObjs[j]);
+                        sizes[j]=0;
+                    }
+                groupList.addElement(clusterList);
+                wordList.addElement(new Integer(bigWord));
+            }
+        }
+
+        clusterGroups = new ClusterGroupObj[groupList.size()];
+
+        for(i=0;i < groupList.size();i++)
+        {
+            Vector clusterList = (Vector)groupList.elementAt(i);
+            if(clusterList == null)
+                System.out.println("Cluster list "+ i + " is null");
+            else
+            {
+                //ClusterObj [] clusterObjsArray = clusterList.toArray();
+                int groupSize = clusterList.size();
+                ClusterObj [] clusterObjsArray = new ClusterObj [groupSize];
+                for (j=0; j < groupSize;j++)
+                    clusterObjsArray[j] = (ClusterObj) clusterList.elementAt(j);
+                //System.out.println("Group " + i + " is " + clusterObjsArray.length + " long");
+                Integer bigWord = (Integer)wordList.elementAt(i);
+                int bw = bigWord.intValue();
+                String word;
+                if (bw <0)
+                {
+                    System.out.println("Group " + i + " is " + clusterObjsArray.length + " long");
+                    System.out.println("bigword is empty");
+                    word = "< Empty >";
+                }
+                else
+                    word = data.words[bigWord.intValue()];
+                clusterGroups[i] = new ClusterGroupObj(clusterObjsArray,word);
+            }
+        }
+    }
+    
+    
+    private void printData(String[] str, String filename)
+    {
+        try
+	    {
+	        filename += ".txt";
+            File target = new File(filename);
+		    FileWriter fw = new FileWriter(target);
+		    PrintWriter out = new PrintWriter(fw,true);
+		    String line = new String();
+		   			
+			    for( int j = 0; j < str.length; j++ )
+			    {
+			        line = str[j] + " ";
+			        out.println(j + " " + line);
+			        System.out.println(j + " " + line);
+		        }
+		    
+		    out.close();
+	    } catch (IOException e){ System.out.println("Error " + e.toString());
+	    } catch (SecurityException se) { System.out.println("Error " + 	se.toString());}
+	}
+	
+	public void setUpDendroClusters()
+	{
+	    displayFrame.dendroPanel.treePanel.getData();
+	}
+    
+       
+    
+    public int query(String queryStr)
+    {
+        if( !displayFrame.refine )
+        {
+            if( displayFrame.firstSearch )
+            {
+                displayFrame.firstSearch = false;
+            }
+        }
+                
+	Engine theEngine;
+	try {
+	    theEngine = (Engine)Class.forName("vishnu.testvis.object."+engine_type).newInstance();
+	} catch (Exception e) {
+	    System.err.println("Couldn't create an engine "+engine_type+ ", aborting query");
+	    System.err.println(e);
+	    return 0;
+	}
+	theEngine.setDisplay(displayFrame);
+	//GSDLEngine theEngine = new GSDLEngine(displayFrame);
+	System.out.println("Searching for " + queryStr);
+	DataBlock db =  theEngine.getData(queryStr, currentCollection.dir, currentView);
+
+        if( db == null ) return 0;
+
+	data = db;
+	
+        int spaceIndex=0;
+        int count = 0;
+        int wordNum = 0;
+        int clusterNum=0;
+        StringBuffer qbuff = new StringBuffer();
+            
+        setUpClusterLabels();
+        setUpDocObjs();
+        setUpClusterObjs();
+        setUpClusterGroupObjs();
+        setUpSammClusters();
+        if( !displayFrame.refine ) setUpDendroClusters();
+        
+	count = this.data.docs.length;
+	wordNum = this.data.words.length;
+	clusterNum = this.data.clusters.length;
+	
+        if( !displayFrame.refine )
+	    {
+		displayFrame.searchStarted = true;
+	    }
+        
+	displayFrame.update();
+        return count;
+    }
+
+
+    public void fetch(int index)
+    {
+        if(data==null) return ;
+        if(index < 0 || index >= data.docs.length) return;
+        int docNum = data.docs[index];
+        fetchDocNo(docNum);
+    }
+
+
+    public void fetchDocNo(long docNum)
+    {
+        System.out.println("Fetch document: " + Long.toString(docNum));
+
+	Engine theEngine;
+	try {
+	    theEngine = (Engine)Class.forName("vishnu.testvis.object."+engine_type).newInstance();
+	} catch (Exception e) {
+	    System.err.println("Couldn't create an engine "+engine_type+ ", aborting query");
+	    System.err.println(e);
+	    return;
+	}
+	theEngine.setDisplay(displayFrame);
+	//GSDLEngine engine = new GSDLEngine(displayFrame);
+	Vector document = theEngine.getDocument(Long.toString(docNum),currentCollection.dir, currentView);
+	
+	// Fetch theFetch = new Fetch(displayFrame);
+        // document = theFetch.getDocument(docNum);
+        
+	Browser browser;
+	if (document.size() > 1) {
+	    String url = (String)document.elementAt(1);
+	    
+	    if( url.indexOf("http") != -1 )
+		{
+		    try{
+			displayFrame.getAppletContext().showDocument(new URL(url),"_blank");
+			// http://icarus.umkc.edu/cgi-bin/ptext?doc=perseus:text:1999.01.0154"),"_blank");
+		    }catch(IOException ex){ex.printStackTrace();};
+		}     
+	    else browser = new Browser(document,docNum);
+	} else { 
+	    browser = new Browser(document,docNum);
+	}
+    }
+    
+
+    public String [] getClusterLabels()
+    {
+        return clusterLabels;
+    }
+
+
+    public String [] getDescriptions()
+    {
+        if(data != null)
+            return data.descriptions;
+        else return new String[0];
+    }
+    
+    
+    public String [] getTrecIDs()
+    {
+        if(data != null)
+        {
+            String trecID[] = new String[data.descriptions.length];
+            for( int i = 0; i < data.descriptions.length; i++ )
+            {
+                StringTokenizer st = new StringTokenizer(data.descriptions[i]);
+                trecID[i] = st.nextToken();             
+            }
+            return trecID;
+        }
+        else return new String[0];
+    }
+    
+       
+    public String getDescriptionStr(Vector subset)
+    {
+        String returnStr = new String("");
+        for(int c = 0; c < subset.size();c++)
+        {
+            Integer n = (Integer)(subset.elementAt(c));
+            returnStr = returnStr + data.descriptions[n.intValue()] + "\n";
+        }
+        return returnStr;
+    }
+    public int [] getDocs()
+    {
+        if(data != null)
+            return data.docs;
+        else return new int[0];
+    }
+
+    public String [] getWords()
+    {
+        if(data != null)
+        {
+            if(data.words==null)
+            {
+                System.out.println("No words in data!");
+                return new String[0];
+            }
+            return data.words;
+        }
+        else return new String[0];
+    }
+
+    public SparseMatrix getMatrix()
+    {
+        if(data != null)
+            return data.matrix;
+        else return null;
+    }
+
+    public Vector [] getClusters()
+    {
+        if(data != null)
+            return data.clusters;
+        else return null;
+    }
+    public Point2D [] getSammon()
+    {
+        if(data != null)
+            return data.sammon;
+        else return new Point2D[0];
+    }
+
+    public DataManager(Vishnu d) {
+	this(d, null);
+    }
+    public DataManager(Vishnu d, String engine)
+    {
+        displayFrame = d;
+	if (engine != null && !engine.equals("")) {
+	    engine_type = engine;
+	}
+	Engine theEngine;
+	try {
+	    theEngine = (Engine)Class.forName("vishnu.testvis.object."+engine_type).newInstance();
+	} catch (Exception e) {
+	    System.err.println("Couldn't create an engine "+engine_type+ ", aborting query");
+	    System.err.println(e);
+	    return;
+	}
+
+	theEngine.setDisplay(displayFrame);
+	Vector q = new Vector();
+	q.addElement("initialise");
+	collections = theEngine.getCollections(q); 
+    }
+
+
+    // This constructor creates a smaller data manager from
+    // several clusters from a larger one
+    // it is intended to be used with sammon mapping drilling down
+    // The clustering code is lifted from the C version and could be
+    // improved
+    
+    public DataManager(DataBlock oldData, Vector idVect, Vishnu d)
+    {
+        displayFrame = d;
+        
+        // create new document/keyword matrix with only the 
+        // documents from the selected cluster, but keeping
+        // the same 100 keywords as columns
+        SparseMatrix m = new SparseMatrix(idVect.size(),
+                                          oldData.matrix.getColumnCount());
+
+        for (int c = 0; c< idVect.size();c++)
+        {
+          // the document id of the cth document
+          Integer i = (Integer)idVect.elementAt(c);
+          
+          // ptr to the first document of ith row (linked list)
+          SparseNode row = oldData.matrix.getRow(i.intValue());
+          // add the row to new lean matrix
+          while(row != null)
+          {
+              m.addDataItem(c,row.getColumn(),row.getValue());
+              row=row.getNext();
+          }
+        }
+        
+        float [][] full = m.getExpanded(); 
+        
+        // the number of new clusters generated is either 20
+        // or if there are not many docs half the #docs
+        int clusterNum =  m.getRowCount()>20?10:m.getRowCount()/2;
+        
+        // limit set to 20 if > 20 docs otherwise to 3/4 #docs
+        int limit =  m.getRowCount()>20?20:(m.getRowCount()/2)+
+                      (m.getRowCount()/4);
+                      
+        Clustering clustering = new Clustering(Clustering.COMPLETE_LINKAGE,
+					       full,/*m.getRowCount(),
+						      m.getColumnCount(),*/1,
+					       clusterNum,limit);
+                                                    
+        Vector [] clusters = clustering.getClusters();
+        double [][] centroids= clustering.getCentroids();
+
+        Sammon map = new Sammon(centroids);
+        Point2D [] sammon = map.getMapping();
+        String [] words = oldData.words;
+        int [] newDocs = new int[idVect.size()];
+        String [] newDesc = new String[idVect.size()];
+        for (int c=0;c<idVect.size();c++)
+        {
+          Integer i = (Integer) idVect.elementAt(c);
+          int index = i.intValue();
+          newDocs[c] = oldData.docs[index];
+          newDesc[c] = oldData.descriptions[index];
+        }
+        data = new DataBlock(newDocs,newDesc,words,m,clusters,sammon);
+        
+        setUpClusterLabels();
+        setUpDocObjs();
+        setUpClusterObjs();
+        setUpClusterGroupObjs();
+        setUpSammClusters();
+    }
+
+    public DataBlock getDataBlock()
+    {
+        return data;
+    }
+    
+    public int getWordCount()
+    {
+        if (data!=null)
+            return data.words.length;
+        else return 0;
+    }
+    public int getDocCount()
+    {
+        if (data!=null)
+            return data.docs.length;
+        return 0;
+    }
+
+    public Vector getSammonClusters()
+    {
+        if(data != null)
+            return sammonClusters;
+        else return null;
+    }
+
+    private void setUpSammClusters()
+    {
+        LengthTable [] table = new LengthTable [data.clusters.length];
+        
+        // create as many sammon clusters as there are clusters in the data object
+        // and store it in sammmonClusters
+        sammonClusters = new Vector(data.clusters.length);
+        
+        for (int c = 0; c < data.clusters.length; c++){
+	    table[c]= new LengthTable(c,data.clusters[c].size());
+        }
+        
+        
+        // sort by cluster size, I guess
+        QSortAlgorithmTableLength qSort = new QSortAlgorithmTableLength();
+        try
+        {
+            qSort.sort(table);
+        }
+        catch (Exception e)
+        {}
+        
+        
+        for (int c = 0; c < data.clusters.length; c++)
+        {
+            int i = table[c].index;
+             SammCluster cluster = new SammCluster(data.clusters[i],
+                        data.sammon[i], sommClusterWords[i],
+                        clusterObjs[i]);
+            sammonClusters.addElement(cluster);
+        }
+    }
+
+    public void requestCollection()
+    {
+	// should reset the data
+	data = null;
+    }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/DocObj.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/DocObj.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/DocObj.java	(revision 21054)
@@ -0,0 +1,89 @@
+/**
+ * @(#)DocObj.java  12/12/99
+ * @author Peter Au
+ * All rights reserved.
+ * Adapted to use sparce matrix Matthew Carey 7/7/2000
+ */
+
+package vishnu.testvis.object;
+import vishnu.datablock.*;
+import java.util.*;
+public class DocObj
+{
+    private int _number;
+    private int _mgNumber;
+    private String _description;
+//--!!! we don't need to store the whole number, as the actual value is not very important
+//--!!! but we need to keep track the doc contains particular related words or not
+//--!!! therefore, one bit will do
+    private SparseNode _weights, _sparceNode;
+
+
+    public DocObj(int num, String s,SparseNode w, int mgn)
+    {
+        _number = num;
+        _description=s;
+        //_fields=f;
+        _weights=w;
+        _mgNumber = mgn;
+    }
+
+  //public void setWeights(int[] w){_weights=w;}
+    public int getNumber()
+    {
+        return _number;
+    }
+    public String getDescription()
+    {
+        return _description;
+    }
+
+    public SparseNode getWeights()
+    {
+        return _weights;
+    }
+    public float getWeights(int i)
+    {
+        SparseNode test = _weights;
+        while(test != null)
+        {
+            if(test.getColumn() == i) return test.getValue();
+            test = test.getNext();
+        }
+        return 0;
+    }
+    static public String getANumberOfWords(String in, int num)
+    {
+        StringTokenizer st = new StringTokenizer(in);
+        StringBuffer buff = new StringBuffer();
+        for(int c=0;c<num && st.hasMoreTokens() ;c++)
+        {
+            if (c>0) buff.append(" ");
+            buff.append(st.nextToken());
+        }
+        String retString = new String(buff);
+        return retString;
+    }
+    
+    public String getTrecID()
+    {
+        StringTokenizer st = new StringTokenizer(_description);
+        return st.nextToken();             
+    }
+
+    public String format4Fields()
+    {
+        String s = "";
+        s += "<dl>\n";
+        s += "  <dt> <a HREF=\"" + _mgNumber +"\">"+ "Document: " + _mgNumber + "</a>\n";
+        //s += "  <dd> <b>"+ getANumberOfWords(_description,5) +"</b><br> \n";
+	String modDesc = _description.replaceAll("<"," ");
+	modDesc = modDesc.replaceAll(">"," ");
+        s += "    <dd> <font color=gray> " + modDesc + " </font>\n";
+        s += "</dl>\n";
+	System.out.println(s);
+        return s;
+  }
+
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/Engine.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/Engine.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/Engine.java	(revision 21054)
@@ -0,0 +1,118 @@
+package vishnu.testvis.object;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import vishnu.datablock.*;
+import vishnu.testvis.visual.*;
+
+public class Engine
+{
+    Vishnu display;
+    String servlet;
+
+    public Engine() {
+    }
+
+    public Engine(Vishnu display)
+    {
+	this.setDisplay(display);
+	
+    }
+    
+    public void setDisplay(Vishnu display) {
+	this.display = display;
+	servlet = display.server_address;
+	System.out.println("servlet= "+servlet);
+
+    }
+    
+    public Vector getCollections(Vector q)
+    {
+	Vector c = null;// = new Vector();
+	try{
+	    URLConnection con = getConnection();
+            System.out.println("Sending request for collections");
+	    sendObject(con,q);
+	    c = (Vector)receiveObject(con);
+	} catch( Exception ex) {System.out.println(ex);}
+	return c;
+    }
+    
+    public DataBlock getData(String query, String collection, String view)
+    {
+        DataBlock data = null;
+        Vector q = new Vector();
+	q.addElement("search");
+        q.addElement(query);
+	q.addElement(collection);
+	q.addElement(view);
+	
+
+        try{
+            URLConnection con = getConnection();
+            sendObject(con,q);
+	    Vector v = (Vector)receiveObject(con);
+	    if( v == null || v.size()==0 ) {
+		System.out.println("Engine.getData: Null or empty result");
+	    } else {
+		data = (DataBlock)v.elementAt(0);
+	    }
+        } catch( Exception ex)  {
+	    System.out.println("Engine.getData: "+ex);
+	}
+	
+	return data;
+    }
+
+    
+    public Vector getDocument(String id, String collection, String view)
+    {
+	Vector q = new Vector();
+	Vector resp = new Vector();
+	q.addElement("fetch");
+	q.addElement(id);
+	q.addElement(collection);
+	q.addElement(view);
+
+	try{
+	    URLConnection con = getConnection();
+	    sendObject(con,q);
+	    resp = (Vector)receiveObject(con);
+	} catch(Exception ex) {ex.printStackTrace();}
+	return (Vector)resp.elementAt(0);
+    }
+
+
+    private URLConnection getConnection() throws IOException
+    {
+        URL u = new URL(servlet);
+        URLConnection con = u.openConnection();
+        con.setDoInput(true);
+        con.setDoOutput(true);
+        con.setUseCaches(false);
+	con.setRequestProperty("Content-type","application/octet-stream");
+        con.setAllowUserInteraction(false);
+        return con;
+    }
+    
+    
+    private void sendObject(URLConnection con, Object obj) throws IOException {
+        ObjectOutputStream out = new  ObjectOutputStream(con.getOutputStream());
+        if( obj != null )
+	    {
+                out.writeObject(obj);
+	    }
+        out.close();
+    }
+    
+    
+    private Object receiveObject(URLConnection con) throws Exception
+    {
+	Object obj;
+	InputStream is = con.getInputStream();
+	ObjectInputStream in = new ObjectInputStream(con.getInputStream() );
+	obj = in.readObject();
+	in.close();
+	return obj;
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/GSDLEngine.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/GSDLEngine.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/GSDLEngine.java	(revision 21054)
@@ -0,0 +1,107 @@
+package vishnu.testvis.object;
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import vishnu.datablock.*;
+import vishnu.testvis.visual.*;
+import vishnu.server.Packaging;
+import vishnu.util.XMLUtil;
+import vishnu.util.Base64;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.apache.xerces.parsers.DOMParser;
+
+public class GSDLEngine extends Engine
+{
+    public GSDLEngine() {
+    }
+    
+    public GSDLEngine(Vishnu display)
+    {
+	this.setDisplay(display);
+    }
+        
+    public Vector getCollections(Vector q) {
+	return null;
+    }
+    
+    public DataBlock getData(String query_term, String collection, String view) {
+
+	
+	String query = servlet+"&type=search&q="+query_term;
+	if (view != null) {
+	    query += "&view="+view;
+	}
+	System.out.println("sending query: " + query_term);
+	DataBlock db = null;
+	try {
+	    URL cgi = new URL(query);
+	    DataInputStream in = new DataInputStream(cgi.openStream());
+	    DOMParser parser = new DOMParser();
+	    parser.parse(new InputSource(in));
+	    Document data_doc = parser.getDocument();
+	    Element data_elem = data_doc.getDocumentElement();
+	    String results = XMLUtil.getNodeText(data_elem);
+	    
+	    // unserialize the data block
+	    db = (DataBlock) Base64.decodeToObject(results);
+	    //byte [] bytes = results.getBytes("UTF-8");
+// 	    byte [] bytes = Base64.decode(results);
+// 	    ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+// 	    ObjectInputStream ois = new ObjectInputStream(bais);
+// 	    db = (DataBlock)ois.readObject();
+//	    ois.close();
+// 	    Element cluster_results = (Element)XMLUtil.getChildByTagName(data_elem, "cluster");
+// 	    String results = XMLUtil.getNodeText(cluster_results);
+// 	    //System.out.println("recived results:" + results);
+// 	    Element description_results = (Element)XMLUtil.getChildByTagName(data_elem, "descriptions");
+// 	    NodeList descs = description_results.getElementsByTagName("desc");
+// 	    Vector desc_vector = new Vector();
+// 	    for (int i=0; i<descs.getLength(); i++) {
+// 		String d = XMLUtil.getNodeText((Element)descs.item(i));
+// 		desc_vector.add(d);
+// 	    }
+// 	    Packaging p = new Packaging();
+// 	    db = p.getDataPackage(results, desc_vector);
+	    in.close();
+	} catch (Exception e) {
+	    System.err.println( "Error sending query to cgi: " + e);
+	    e.printStackTrace();
+	    return null;
+	}
+	return db;
+
+    }
+
+    public Vector getDocument(String id, String collection, String view) {
+	String query = servlet+"&type=fetch&d="+id;
+	if (view != null) {
+	    query += "&view="+view;
+	}	
+	System.out.println("sending fetch: " + id);
+	Vector doc;
+	try {
+	    URL cgi = new URL(query);
+	    DataInputStream in = new DataInputStream(cgi.openStream());
+	    DOMParser parser = new DOMParser();
+	    parser.parse(new InputSource(in));
+	    Document data_doc = parser.getDocument();
+	    Element data_elem = data_doc.getDocumentElement();
+	    String results = XMLUtil.getNodeText(data_elem);
+	    System.out.println("recived results:" + results);
+	    // ugly hack. make this nicer!!
+	    doc = new Vector(Arrays.asList(results.split("\n")));
+	    in.close();
+	} catch (Exception e) {
+	    System.err.println( "Error sending query to cgi: " + e);
+	    e.printStackTrace();
+	    return null;
+	}
+	return doc;
+		
+    }
+    
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/LengthTable.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/LengthTable.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/object/LengthTable.java	(revision 21054)
@@ -0,0 +1,21 @@
+
+/**
+ * Title:        <p>
+ * Description:  <p>
+ * Copyright:    Copyright (c) <p>
+ * Company:      <p>
+ * @author
+ * @version 1.0
+ */
+package vishnu.testvis.object;
+
+public class LengthTable
+{
+    public int index;
+    public int length;
+    public LengthTable(int ind, int len)
+    {
+        index= ind;
+        length = len;
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/ClusterPanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/ClusterPanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/ClusterPanel.java	(revision 21054)
@@ -0,0 +1,450 @@
+package vishnu.testvis.sammon;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+import java.util.Enumeration;
+import vishnu.testvis.visual.*;
+import vishnu.testvis.object.*;
+import java.awt.image.*;
+
+class ClusterPanel extends JPanel
+{
+    Image offScreen=null;
+    Dimension offScreenSize;
+    Graphics2D offGraphics;
+    private SammCluster currentCluster;
+    private final int RADIUS = 30;
+    private SammCluster drawnCluster;
+    private GraphicalPane parentSammon;
+
+    boolean firstTime = true;
+    Graphics2D big;
+    BufferedImage bi;
+    Rectangle area;
+    Dimension d = null;
+    
+    int locationID = -1;
+    JPopupMenu[] popups;
+    JPopupMenu popup;
+    Vishnu _currentMain;
+    DataManager dm; 
+
+    public ClusterPanel(Vishnu currentMain, GraphicalPane parent)
+    {
+        this.parentSammon = parent;
+        _currentMain=currentMain;
+	dm = _currentMain.dataManager;
+        popup = new JPopupMenu();
+        popup.setVisible(false);
+        add(popup);
+
+        setOpaque(true);
+        setPreferredSize(new Dimension(600,600));
+        setBorder(BorderFactory.createCompoundBorder(
+            BorderFactory.createEmptyBorder(20,20,20,20),
+            BorderFactory.createLoweredBevelBorder()));
+            
+        addComponentListener(
+            new ComponentAdapter()
+        {
+            public void componentResized(ComponentEvent evt)
+            {
+                if( parentSammon.dataMan != null && parentSammon.dataMan.getDocCount()>0){
+		    repaint();
+                }
+
+            }
+        }
+        );
+
+        addMouseMotionListener( new MouseMotionAdapter()
+        {
+            public void mouseMoved(MouseEvent mouseEvent)
+            {
+                int docsInSelectedClusters=0;
+                
+                if( parentSammon.dataMan == null) return;
+                int oldLocationID = locationID;
+                
+                Vector clusters = parentSammon.dataMan.getSammonClusters();
+                if (clusters == null) return;
+                locationID = -1;
+                for (int i = clusters.size()-1 ; i >= 0 ; i--)
+                {
+
+                    SammCluster cluster = (SammCluster) clusters.elementAt(i);
+                    int x = (int) (cluster.getX()*(offScreenSize.width-2*RADIUS)) + RADIUS;
+                    int y = (int) (cluster.getY()*(offScreenSize.height-2*RADIUS)) +RADIUS;
+
+                    if (inCircle(mouseEvent,x,y,cluster.getDisplayedRadius()))
+                    {
+                        locationID = i;
+                    }
+                    if(cluster.getSelected())
+                      docsInSelectedClusters+=cluster.documentIDs.size();
+                }
+
+                if ((oldLocationID != locationID) && (oldLocationID != -1))
+                    popup.setVisible(false);
+
+
+                if (locationID != -1)
+                {
+		    if (locationID != oldLocationID)
+                    {
+               
+                        popup.removeAll();
+                             
+                        String[] allWords = parentSammon.dataMan.getWords();
+                        
+                        currentCluster = (SammCluster) clusters.elementAt(locationID);
+                        String str =  currentCluster.getSize() + " documents";
+                        JMenuItem menuItem = new JMenuItem(str);
+                        popup.add(menuItem);
+
+                        menuItem.addActionListener(new ActionListener()
+                        {
+                            public void actionPerformed(ActionEvent actionEvent)
+                            {
+                                drawnCluster = currentCluster;
+                                updateHLinkPanel();
+                                repaint();
+				//update
+                            }
+                        });
+
+                        popup.add(new JPopupMenu.Separator());
+                        menuItem = new JMenuItem("Drill down");
+
+                        if(!currentCluster.getSelected())
+                          docsInSelectedClusters+=currentCluster.documentIDs.size();
+                                                   
+                        if(docsInSelectedClusters<7)menuItem.setEnabled(false);
+                            else menuItem.setEnabled(true);
+                        
+                        // for drilldown
+                        menuItem.addActionListener(new ActionListener()
+                        {
+                            public void actionPerformed(ActionEvent ActionEvent)
+                            {
+                                Vector idVector;
+                                SammCluster thisCluster = getCurrentCluster();
+                                                                
+                                int theIndex = thisCluster.getWords()[0];
+                                String drillWords = new String(parentSammon.dataMan.getWords()[theIndex]);
+                                for( int i = 1; i < thisCluster.getWords().length; i++ )
+                                {
+                                    theIndex = thisCluster.getWords()[i];
+                                    drillWords += " " + parentSammon.dataMan.getWords()[theIndex];
+                                }
+				
+				Vector clusterPl = parentSammon.dataMan.getSammonClusters();
+
+                                Vector idVect = (Vector)thisCluster.getIDs().clone();
+                                
+                                thisCluster.setSelected(true);
+                                for (int i = clusterPl.size()-1; i >= 0; i--)
+                                {
+
+                                  SammCluster cluster = (SammCluster) clusterPl.elementAt(i);
+                                  if (!cluster.equals(thisCluster) && cluster.getSelected())
+                                    idVect.addAll(cluster.getIDs());
+                                }
+                                if(idVect.size()>6)
+                                {
+                                
+                                  parentSammon.drillDown(idVect);
+                                  
+                                  currentCluster=null;
+                                  drawnCluster=null;
+				  //update();
+				  repaint();
+                                }
+                                
+                            }
+                        });
+                        popup.add(menuItem);
+                        if(!currentCluster.getSelected())
+                          menuItem = new JMenuItem("Select");
+                        else
+                          menuItem = new JMenuItem("Deselect");
+                        menuItem.addActionListener(new ActionListener()
+                        {
+                            public void actionPerformed(ActionEvent ActionEvent)
+                            {
+                                SammCluster cluster = getCurrentCluster();
+                                cluster.setSelected(!cluster.getSelected());
+                                //update();
+				repaint();
+                                
+                            }
+                        });
+
+                        popup.add(menuItem);
+                        int[] words = currentCluster.getWords();
+                        popup.add(new JPopupMenu.Separator());
+                        for(int c=0;c<4;c++)
+                        {
+                          int index = words[c];
+                          if (index>= 0)menuItem = new JMenuItem(allWords[index]);
+                          popup.add(menuItem);
+                          menuItem.setEnabled(false);
+                        }
+
+                        popup.validate();
+                        popup.setVisible(true);
+			
+                    }
+                    popup.show(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY());
+                    mouseEvent.consume();
+                }
+		
+            }
+	    });
+	
+        setVisible(true);
+    }
+
+    
+    public SammCluster getCurrentCluster() {return currentCluster;}
+    
+    
+    public void paint(Graphics g)
+    {
+	update(g);
+    }
+    
+
+    public boolean inCircle(MouseEvent mouseEvent, int x, int y, int radius)
+    {
+        int dx2 = (mouseEvent.getX()-x)*(mouseEvent.getX()-x);
+        int dy2 = (mouseEvent.getY()-y)*(mouseEvent.getY()-y);
+
+        return (dx2 + dy2 < radius*radius);
+    }
+    
+    
+    private void drawFlag(Graphics g,int x, int y)
+    {
+	g.drawLine(x,y,x,y-12);
+	int [] xs = new int[3];
+	int [] ys = new int[3];
+	xs[0]=x;xs[1]= x+10; xs[2]=x;
+	ys [0]=y-12; ys[1]=y-7;ys[2]=y-2;
+	g.fillPolygon(xs,ys,3);
+    }
+
+    
+    public void update(Graphics g)
+    {
+	Graphics2D g2 = (Graphics2D)g;
+	Dimension d = getSize();
+	offScreenSize = d;
+	
+	int w = d.width;
+	int h = d.height;
+	
+	area = new Rectangle(d);
+	bi = (BufferedImage)createImage(w, h);
+	offGraphics = bi.createGraphics();
+	area.setLocation(w/2-50, h/2-25);
+	BasicStroke myStroke = new BasicStroke(2,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER);
+	
+	offGraphics.setColor(Color.white);
+	/*offGraphics.clearRect(0, 0, area.width, area.height);
+	offGraphics.draw(area);
+	offGraphics.fill(area);*/
+	offGraphics.fillRect(0,0,d.width,d.height);
+	
+        if( parentSammon.dataMan.getDocCount()<1) return;
+	
+        Vector clusters = parentSammon.dataMan.getSammonClusters();
+	if (clusters.size()==0) return;
+	double minNumDocs = ((SammCluster) clusters.elementAt(0)).getSize();
+        double maxNumDocs = ((SammCluster) clusters.elementAt(clusters.size()-1)).getSize();
+        double deltaNumDocs = maxNumDocs - minNumDocs;
+	
+        for (int i = clusters.size()-1; i >= 0; i--) {
+	    
+            SammCluster cluster = (SammCluster) clusters.elementAt(i);
+            int x = (int) (cluster.getX()*(offScreenSize.width-2*RADIUS)) + RADIUS;
+            int y = (int) (cluster.getY()*(offScreenSize.height-2*RADIUS)) + RADIUS;
+	    
+            // Value between 0 and 1 representing the cluster (0 is the smaller cluster and 1 is the bigger one
+            double clusterWeight = (cluster.getSize() - minNumDocs)/ deltaNumDocs;
+
+            offGraphics.setColor(new Color((float)0.7, (float)0.7, (float)1));
+	    
+            cluster.setDisplayedRadius(RADIUS-(int)(RADIUS*(1-clusterWeight)/2));
+	    myStroke = new BasicStroke(3,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER);
+            offGraphics.setStroke(myStroke);	        
+            offGraphics.fillOval(x - cluster.getDisplayedRadius(),
+				 y - cluster.getDisplayedRadius(),
+				 2*cluster.getDisplayedRadius(),
+				 2*cluster.getDisplayedRadius());
+	    
+            offGraphics.setColor(new Color((float)0,(float)0, (float)0.7));
+            offGraphics.drawOval(x - cluster.getDisplayedRadius(),
+				 y - cluster.getDisplayedRadius(),
+				 2*cluster.getDisplayedRadius(),
+				 2*cluster.getDisplayedRadius());
+	    
+            if (cluster.getSelected())
+		drawFlag(offGraphics,x,y-cluster.getDisplayedRadius());
+	    
+            if (cluster.equals(this.drawnCluster))
+		{
+		    
+		    offGraphics.setColor(new Color((float)0.3, (float)0.3, (float)1));
+		    offGraphics.fillOval(x - cluster.getDisplayedRadius(),
+					 y - cluster.getDisplayedRadius(),
+					 2*cluster.getDisplayedRadius(),
+					 2*cluster.getDisplayedRadius());
+		    
+		    offGraphics.setColor(new Color((float)0, (float)0, (float)0.7));  
+		    offGraphics.drawOval(x - cluster.getDisplayedRadius(),
+					 y - cluster.getDisplayedRadius(),
+					 2*cluster.getDisplayedRadius(),
+					 2*cluster.getDisplayedRadius());
+		}
+	    
+        }
+	
+        int i = clusters.size();
+        Font normal = new Font("Courier",Font.PLAIN,16);
+        Font bold = new Font("Courier",Font.BOLD,16);
+        for (Enumeration enumer = clusters.elements() ; enumer.hasMoreElements();)
+	    {
+		
+		SammCluster cluster = (SammCluster) enumer.nextElement();
+		int x = (int) (cluster.getX()*(offScreenSize.width-2*RADIUS)) + RADIUS;
+		int y = (int) (cluster.getY()*(offScreenSize.height-2*RADIUS)) + RADIUS;
+		offGraphics.setFont(bold);
+		offGraphics.setColor(new Color((float)0, (float)0,(float)0.4));
+		
+		FontMetrics fm = offGraphics.getFontMetrics();
+		int theIndex = cluster.getWords()[0];
+		
+		String theWord = parentSammon.dataMan.getWords()[theIndex];
+		
+		int width = fm.stringWidth(theWord);
+		offGraphics.drawString(theWord,x-(width/2),y+5);
+		i--;
+	    }
+	
+	g2.drawImage(bi,0,0,this);
+    }
+    
+    
+    public void update()
+    {
+	/*        Dimension d = getSize();
+        offScreenSize = d;
+        offScreen = createImage(d.width,d.height);
+        offGraphics = (Graphics2D)offScreen.getGraphics();
+	
+        offGraphics.setColor(Color.white);
+        offGraphics.fillRect(0,0,d.width,d.height);
+	
+        if( parentSammon.dataMan.getDocCount()<1) return;
+	
+        Vector clusters = parentSammon.dataMan.getSammonClusters();
+	
+	double minNumDocs = ((SammCluster) clusters.elementAt(0)).getSize();
+        double maxNumDocs = ((SammCluster) clusters.elementAt(clusters.size()-1)).getSize();
+        double deltaNumDocs = maxNumDocs - minNumDocs;
+	
+        for (int i = clusters.size()-1; i >= 0; i--) {
+	    
+            SammCluster cluster = (SammCluster) clusters.elementAt(i);
+            int x = (int) (cluster.getX()*(offScreenSize.width-2*RADIUS)) + RADIUS;
+            int y = (int) (cluster.getY()*(offScreenSize.height-2*RADIUS)) + RADIUS;
+	    
+            // Value between 0 and 1 representing the cluster (0 is the smaller cluster and 1 is the bigger one
+            double clusterWeight = (cluster.getSize() - minNumDocs)/ deltaNumDocs;
+
+            //offGraphics.setColor(new Color((float) (1 - 0.7*clusterWeight),(float) 0, (float) 0));
+            offGraphics.setColor(new Color((float)0.7, (float)0.7, (float)1));
+	    
+            cluster.setDisplayedRadius(RADIUS-(int)(RADIUS*(1-clusterWeight)/2));
+            BasicStroke myStroke = new BasicStroke(3,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER);
+            offGraphics.setStroke(myStroke);	        
+            offGraphics.fillOval(x - cluster.getDisplayedRadius(),
+				 y - cluster.getDisplayedRadius(),
+				 2*cluster.getDisplayedRadius(),
+				 2*cluster.getDisplayedRadius());
+	    
+            offGraphics.setColor(new Color((float)0,(float)0, (float)0.7));
+            offGraphics.drawOval(x - cluster.getDisplayedRadius(),
+				 y - cluster.getDisplayedRadius(),
+				 2*cluster.getDisplayedRadius(),
+				 2*cluster.getDisplayedRadius());
+	    
+            if (cluster.getSelected())
+		drawFlag(offGraphics,x,y-cluster.getDisplayedRadius());
+	    
+            if (cluster.equals(this.drawnCluster))
+		{
+		    
+		    offGraphics.setColor(new Color((float)0.3, (float)0.3, (float)1));
+		    offGraphics.fillOval(x - cluster.getDisplayedRadius(),
+					 y - cluster.getDisplayedRadius(),
+					 2*cluster.getDisplayedRadius(),
+					 2*cluster.getDisplayedRadius());
+		    
+		    offGraphics.setColor(new Color((float)0, (float)0, (float)0.7));  
+		    offGraphics.drawOval(x - cluster.getDisplayedRadius(),
+					 y - cluster.getDisplayedRadius(),
+					 2*cluster.getDisplayedRadius(),
+					 2*cluster.getDisplayedRadius());
+		}
+	    
+        }
+	
+        int i = clusters.size();
+        Font normal = new Font("Courier",Font.PLAIN,16);
+        Font bold = new Font("Courier",Font.BOLD,16);
+        for (Enumeration enumer = clusters.elements() ; enumer.hasMoreElements();)
+	    {
+		
+		SammCluster cluster = (SammCluster) enumer.nextElement();
+		int x = (int) (cluster.getX()*(offScreenSize.width-2*RADIUS)) + RADIUS;
+		int y = (int) (cluster.getY()*(offScreenSize.height-2*RADIUS)) + RADIUS;
+		offGraphics.setFont(bold);
+		offGraphics.setColor(new Color((float)0, (float)0,(float)0.4));
+		
+		FontMetrics fm = offGraphics.getFontMetrics();
+		int theIndex = cluster.getWords()[0];
+		
+		String theWord = parentSammon.dataMan.getWords()[theIndex];
+		
+		int width = fm.stringWidth(theWord);
+		offGraphics.drawString(theWord,x-(width/2),y+5);
+		i--;
+		}*/
+        repaint();
+    }
+
+
+    private void updateHLinkPanel()
+    {
+        if( parentSammon.dataMan==null) return;
+        
+        if (locationID != -1)
+	    {
+		
+		Vector clusters = parentSammon.dataMan.getSammonClusters();
+		
+		SammCluster selectedCluster = (SammCluster) clusters.elementAt(locationID);
+		ClusterObj cObj = selectedCluster.getClusterObj();
+		String htmlDescription = cObj.formatDocs();
+		
+		parentSammon.hLinkPanel.setText(htmlDescription);
+		parentSammon.keywordPanel.update(cObj);
+	    }
+    }
+}
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/GraphicalPane.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/GraphicalPane.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/GraphicalPane.java	(revision 21054)
@@ -0,0 +1,148 @@
+package vishnu.testvis.sammon;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import vishnu.testvis.visual.*;
+import vishnu.testvis.object.*;
+import java.util.*;
+
+/** Panel containing the sammon map interface */
+public class GraphicalPane extends JPanel
+{
+    public JPanel picturePanel;
+    public ClusterPanel clusterPanel;
+    public SammKeywordPanel keywordPanel;
+    public HLinkPanel hLinkPanel;
+    public Stack dataStack;
+    
+    public DataManager dataMan=null;
+    public Vishnu currentMain;
+    public JLabel pictureLabel = new JLabel("Level 0");
+    public JButton pictureButton = new JButton("Back");
+    public JPanel topPanel = new JPanel();
+    private int largeFont = 16;
+    private int smallFont = 14;
+    private int level = 0;
+    
+    public GraphicalPane(Vishnu currentMain)
+    {
+	this.currentMain = currentMain;
+	dataMan = currentMain.dataManager;
+	
+	dataStack=new Stack();
+	level=0;
+	setName("Sammon Map");
+	setLayout(new BorderLayout(10,10));
+	setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
+	clusterPanel = new ClusterPanel(currentMain,this);
+	keywordPanel = new SammKeywordPanel(currentMain,this);
+	Dimension d = currentMain.getSize();
+	hLinkPanel = new HLinkPanel((int)(d.width)/4,d.height,currentMain);
+	picturePanel = new JPanel();
+	picturePanel.setLayout(new BorderLayout(0,3));
+	topPanel.setLayout(new FlowLayout(FlowLayout.LEFT,10,0));
+	topPanel.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
+	pictureLabel.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+	pictureButton.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+	topPanel.add(pictureLabel);
+	topPanel.add(pictureButton);
+	pictureButton.addMouseListener(new java.awt.event.MouseAdapter()
+	    {
+		public void mouseClicked(MouseEvent e)
+		{
+		    unDrill();
+		}
+	    });
+	
+	pictureButton.setEnabled(false);
+	picturePanel.add(topPanel,"North");
+
+	picturePanel.add(clusterPanel,"Center");
+	add("East", hLinkPanel.linkPane);
+	add("Center", picturePanel);
+	add("West", keywordPanel);
+	setVisible(true);
+    }
+    
+    public void drillDown(Vector idVect)
+    {
+	System.out.println("Drilling");
+
+	// save old data manager and create new one with old data
+	// and vector of document ids of the selected cluster
+	dataStack.push(dataMan);
+	dataMan = new DataManager(dataMan.getDataBlock(), idVect, currentMain);
+	level++;
+	pictureButton.setEnabled(true);
+	keywordPanel.clear();
+	hLinkPanel.clear();
+	pictureLabel.setText("Level "+ level);
+    }
+    
+    public void unDrill()
+    {
+	if(!dataStack.empty()){
+		dataMan = (DataManager)dataStack.pop();
+		hLinkPanel.clear();
+		keywordPanel.clear();
+		level--;
+		clusterPanel.repaint();
+		pictureLabel.setText("Level "+ level);
+	    }
+	else level = 0;
+	if(level==0)
+	    pictureButton.setEnabled(false);
+    }
+  
+    public void update()
+    {
+	dataMan = currentMain.dataManager;
+	dataStack=new Stack(); // dispose of old stack - create new empty one
+	level=0;
+	clusterPanel.repaint();
+	keywordPanel.clear();
+	hLinkPanel.clear();
+    }
+  
+    
+    public void addNotify()
+    {
+        Dimension d = getSize();
+	//Record the size of the window prior to calling parents addNotify.
+        super.addNotify();
+	
+        if (fComponentsAdjusted)
+	    return;
+	
+	// Adjust components according to the insets
+        setSize(getInsets().left + getInsets().right + d.width,
+		getInsets().top + getInsets().bottom + d.height);
+        Component components[] = getComponents();
+        for (int i = 0; i < components.length; i++)
+	    {
+		Point p = components[i].getLocation();
+		p.translate(getInsets().left, getInsets().top);
+		components[i].setLocation(p);
+	    }
+        fComponentsAdjusted = true;
+    }
+    
+    /** Used for addNotify check. */
+    protected boolean fComponentsAdjusted = false;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/HLinkPanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/HLinkPanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/HLinkPanel.java	(revision 21054)
@@ -0,0 +1,80 @@
+package vishnu.testvis.sammon;
+
+import javax.swing.*;
+import javax.swing.text.html.*;
+import javax.swing.event.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+import vishnu.testvis.visual.*;
+
+class HLinkPanel extends JEditorPane {
+
+    public JScrollPane linkPane;
+    public Vishnu currentMain;
+    public int index = 0;
+
+    public int numDisplayedDocs = 15;
+
+    public HLinkPanel(int width,int length, Vishnu currentMain) {
+
+        linkPane = new JScrollPane(this);
+        this.currentMain=currentMain;
+        linkPane.setBorder(BorderFactory.createLoweredBevelBorder());
+        linkPane.setPreferredSize(new Dimension(width,length));
+        linkPane.setMinimumSize(new Dimension(width,length));
+        linkPane.setBorder(new javax.swing.border.EtchedBorder());
+        linkPane.getVerticalScrollBar().setUnitIncrement(10);
+
+        setLayout(new GridLayout(17,1));
+
+
+        setEditorKitForContentType("text/html",new HTMLEditorKit());
+        setEditorKit(new HTMLEditorKit());
+        setEditable(false);
+
+        addHyperlinkListener(new MyLinkListener());
+
+        setVisible(true);
+
+    }
+
+    public class MyLinkListener implements HyperlinkListener{
+        public void hyperlinkUpdate(HyperlinkEvent he){
+            //System.out.println("Inside hyperlinkevent ... ");
+            HyperlinkEvent.EventType type = he.getEventType();
+            //System.out.println("Inside hyperlinkevent ... ");
+            if (type==HyperlinkEvent.EventType.ACTIVATED)
+            {
+
+                String hpresult = he.getDescription();
+                int index = Integer.parseInt(hpresult);
+                System.out.println("hyperlink result: " + index);
+                if (index > 0)
+                    currentMain.dataManager.fetchDocNo(index);
+            }
+        }
+    }
+
+    /*public void update()
+    {
+        int count = currentMain.dataManager.getDocCount();
+        Vector subSet = new Vector(count);
+
+        for (int i = index ; i < index + numDisplayedDocs  ; i++)
+        {
+            subSet.addElement(new Integer(i));
+        }
+
+        String htmlDescription = currentMain.dataManager.getDocumentDescription(subSet);
+
+        setText(htmlDescription);
+
+    }*/
+
+    public void clear()
+    {
+        setText("");
+    }
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/QSortAlgorithmTableLength.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/QSortAlgorithmTableLength.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/QSortAlgorithmTableLength.java	(revision 21054)
@@ -0,0 +1,142 @@
+/*
+ * @(#)QSortAlgorithm.java        1.6 96/12/06
+ *
+ * Copyright (c) 1994-1996 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
+ * modify and redistribute this software in source and binary code form,
+ * provided that i) this copyright notice and license appear on all copies of
+ * the software; and ii) Licensee does not utilize the software in a manner
+ * which is disparaging to Sun.
+ *
+
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
+ * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
+ * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
+ * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
+ * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
+ * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
+
+ * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
+ * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
+ * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ *
+ * This software is not designed or intended for use in on-line control of
+ * aircraft, air traffic, aircraft navigation or aircraft communications; or in
+ * the design, construction, operation or maintenance of any nuclear
+
+ * facility. Licensee represents and warrants that it will not use or
+ * redistribute the Software for such purposes.
+ */
+
+/**
+ * A quick sort demonstration algorithm
+ * SortAlgorithm.java
+ *
+ * @author James Gosling
+ * @author Kevin A. Smith
+ * @version         @(#)QSortAlgorithm.java        1.3, 29 Feb 1996
+ */
+
+package vishnu.testvis.sammon;
+
+import vishnu.testvis.object.*;
+
+public class QSortAlgorithmTableLength
+{
+
+    /**
+     * A version of pause() that makes it easier to ensure that we pause
+     * exactly the right number of times.
+
+     */
+    private boolean pauseTrue(int lo, int hi) throws Exception {
+        return true;
+    }
+
+   /** This is a generic version of C.A.R Hoare's Quick Sort
+    * algorithm.  This will handle arrays that are already
+    * sorted, and arrays with duplicate keys.<BR>
+    *
+    * If you think of a one dimensional array as going from
+    * the lowest index on the left to the highest index on the right
+    * then the parameters to this function are lowest index or
+
+    * left and highest index or right.  The first time you call
+    * this function it will be with the parameters 0, a.length - 1.
+    *
+    * @param a       an integer array
+    * @param lo0     left boundary of array partition
+    * @param hi0     right boundary of array partition
+    */
+   void QuickSort(LengthTable a[], int lo0, int hi0) throws Exception
+   {
+      int lo = lo0;
+      int hi = hi0;
+      int mid;
+
+      if ( hi0 > lo0)
+      {
+
+         /* Arbitrarily establishing partition element as the midpoint of
+
+          * the array.
+          */
+         mid = a[ ( lo0 + hi0 ) / 2 ].length;
+
+         // loop through the array until indices cross
+         while( lo <= hi )
+         {
+            /* find the first element that is greater than or equal to
+             * the partition element starting from the left Index.
+             */
+             while( ( lo < hi0 ) && pauseTrue(lo0, hi0) && ( a[lo].length < mid ))
+                 ++lo;
+
+            /* find an element that is smaller than or equal to
+
+             * the partition element starting from the right Index.
+             */
+             while( ( hi > lo0 ) && pauseTrue(lo0, hi0) && ( a[hi].length > mid ))
+                 --hi;
+
+            // if the indexes have not crossed, swap
+            if( lo <= hi )
+            {
+               swap(a, lo, hi);
+               ++lo;
+               --hi;
+            }
+         }
+
+         /* If the right index has not reached the left side of array
+          * must now sort the left partition.
+
+          */
+         if( lo0 < hi )
+            QuickSort( a, lo0, hi );
+
+         /* If the left index has not reached the right side of array
+          * must now sort the right partition.
+          */
+         if( lo < hi0 )
+            QuickSort( a, lo, hi0 );
+
+      }
+   }
+
+   private void swap(LengthTable a[], int i, int j)
+   {
+      LengthTable T;
+      T = a[i];
+      a[i] = a[j];
+      a[j] = T;
+   }
+
+   public void sort(LengthTable a[]) throws Exception
+   {
+      QuickSort(a, 0, a.length - 1);
+   }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/SammCluster.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/SammCluster.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/SammCluster.java	(revision 21054)
@@ -0,0 +1,86 @@
+package vishnu.testvis.sammon;
+
+import java.util.*;
+import vishnu.datablock.*;
+import vishnu.testvis.object.ClusterObj;
+
+public class SammCluster {
+
+    // The co-ordinates of the centroid in the keyword space
+    //public Vector centroid  = null;
+
+    Point2D sammon;
+
+    // The documents ID contained in this cluster
+    public Vector documentIDs = new Vector();
+    public int[] sigWords;
+    private boolean selected =false;
+    private ClusterObj cObj;
+    public int getSize() { return documentIDs.size();}
+
+    // When looking for a particular cluster in a vector
+    /*public boolean equals(Object obj) {
+
+      System.out.println("Equals " + obj.getClass());
+
+        if (obj instanceof MyInteger)
+        return (documentIDs.size() == ((MyInteger) obj).value);
+
+          return super.equals(obj);
+
+            }
+    */
+    // Co-ordinates of the cluster centroid in the 2 dimension space
+    private double x;
+
+    private double y;
+
+    // Displayed size of the cluster
+    private int displayedRadius;
+
+    public void setDisplayedRadius(int value) {displayedRadius = value;}
+
+    public int getDisplayedRadius() {return displayedRadius;}
+
+
+    //private int numDocs;
+
+    // TEMPORARY: For random generator
+
+    public SammCluster(Vector theDocs, Point2D sammon,
+          int [] sigWords, ClusterObj c)
+    {
+        documentIDs = theDocs;
+        this.sammon = sammon;
+        this.sigWords=sigWords;
+        this.cObj=c;
+    }
+
+    public boolean getSelected() {return selected;}
+    public void setSelected(boolean state){selected=state;}
+
+    double getX() {return sammon.x;}
+
+    double getY() {return sammon.y;}
+
+    public ClusterObj getClusterObj(){ return cObj;}
+
+    //int getNumDocs() {return numDocs;}
+
+    public Vector getIDs() {return documentIDs;}
+
+    void addDocumentID(Integer docID) {
+        documentIDs.addElement(docID);
+    }
+
+   // void setClusterCentroid(Vector centroid) {
+   //     this.centroid = centroid;
+
+    int [] getWords() { return sigWords;}
+
+    void dump()
+    {
+        //System.out.println("Centroid : " + centroid.toString());
+        //System.out.println("Doc IDs: " + documentIDs.toString());
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/SammKeywordPanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/SammKeywordPanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/sammon/SammKeywordPanel.java	(revision 21054)
@@ -0,0 +1,148 @@
+package vishnu.testvis.sammon;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import vishnu.testvis.visual.*;
+import vishnu.testvis.object.*;
+import java.util.*;
+import vishnu.testvis.treemap.table.*;
+
+class SammKeywordPanel extends JPanel
+{
+    RW_DataModel _dataModel;
+    //SammRWTable _dataModel;
+    JTable _table = new JTable();
+    JScrollPane theScrollPane;
+    JCheckBox checkBoxes[];
+    JLabel labels[];
+    Vishnu currentMain;
+    GraphicalPane parentPane;
+
+    public SammKeywordPanel(Vishnu currentMain, GraphicalPane parent)
+    {
+        this.currentMain = currentMain;
+        this.parentPane = parent;
+        
+        theScrollPane = new JScrollPane();
+
+         //setBackground(Color.white);
+        setPreferredSize(new Dimension(200,900));
+        setBorder(BorderFactory.createLoweredBevelBorder());
+        setLayout(new BorderLayout(10,0));
+        Font theFont = new java.awt.Font("Monospaced", 0, 12);
+
+        //JLabel title = new JLabel("Related keywords");
+        //title.setFont(theFont);
+        //add(title,"North");
+
+        _dataModel = new RW_DataModel();
+        //_dataModel = new SammRWTable();
+        _table.setModel(_dataModel);
+
+        _table.setAutoCreateColumnsFromModel(false);
+        _table.getColumnModel().getColumn(0).setPreferredWidth(20);
+        _table.getColumnModel().getColumn(1).setPreferredWidth(100);
+        _table.getColumnModel().getColumn(2).setPreferredWidth(50);
+        _table.setRowSelectionAllowed(false);
+        setupRenderer();
+
+        theScrollPane.getViewport().add(_table, null);
+        add(theScrollPane,"Center");
+
+        setVisible(true);
+
+        JButton refineButton = new JButton("Filter Documents");
+        refineButton.setBorder(BorderFactory.createRaisedBevelBorder());
+        refineButton.setMaximumSize(new Dimension(30,70));
+
+        MouseListener ml = new MouseAdapter()
+        {
+            public void mouseClicked(MouseEvent e)
+            {
+                // what does this do Shalini?
+                // Call the refine engine
+                refineEngine();
+            }
+        };
+
+        refineButton.addMouseListener(ml);
+        refineButton.setFont(theFont);
+        add(refineButton,"South");
+
+
+    }
+    public void refineEngine()
+    {
+       currentMain.log.startsection("FILTERING");
+       
+       parentPane.hLinkPanel.setText(_dataModel.getRefinedDocs());
+       String trec_id[] = _dataModel.getRefinedTrecIDs();
+       String filter_Words[] = _dataModel.getFilterWords();
+       String s = new String("");
+       for( int i = 0; i < filter_Words.length; i++ )
+       {
+            s += filter_Words[i] + " ";
+       }
+      
+       currentMain.log.log("FILTER_WORDS",s);
+       currentMain.log.log("FILTERED_DOCS",Integer.toString(trec_id.length));
+       
+       Vector performance = currentMain.queries.getPerformance(currentMain.query, trec_id);
+       
+       currentMain.log.startsection("PERFORMANCE");
+
+       if( performance.size() > 0 )
+       {
+            Integer relvTotal = (Integer)performance.elementAt(performance.size()-2);
+            Integer relvRetr = (Integer)performance.elementAt(performance.size()-1);
+            currentMain.log.log("RELEVANT_TOTAL",Integer.toString(relvTotal.intValue()));
+            currentMain.log.log("RELEVANT_RETRIEVED",Integer.toString(relvRetr.intValue()));
+            for( int i = 0; i < performance.size()-2; i++ )
+            {
+                Integer perf = (Integer)performance.elementAt(i);
+                String str = "PRECISION_" + Integer.toString((i+1)*10);
+                currentMain.log.log(str,Integer.toString(perf.intValue()));
+            }
+       }
+       else
+       {
+            currentMain.log.log("INFO","No relevance data available");
+       }
+     
+                   
+       currentMain.log.endsection("PERFORMANCE");
+       currentMain.log.endsection("FILTERING");
+    }
+        
+    // have dendroclusterobj/sammonclusterobj etc as subclasses
+    // of some abstract clusterObj superclass with some 
+    // core fields (docs, hitdocFreq etc)
+    public void update(ClusterObj c)
+    {
+        _dataModel.setData(currentMain.dataManager.getWords(), c);
+    }
+    
+    public void update2(vishnu.testvis.dendro.ClusterObj c)
+    {
+        _dataModel.setData(currentMain.dataManager.getWords(), c);
+    }
+
+    public void clear()
+    {
+        _dataModel.clear();
+    }
+    private void setupRenderer()
+    {
+        BarRenderer bar = new BarRenderer();
+        //-- Have a number painted in the middle of the bar
+        bar.setStringPainted(true);
+        //-- setup the background color of the bar
+        bar.setBackground(_table.getBackground());
+
+        //-- render the last column as a bar
+        _table.getColumnModel().getColumn(2).setCellRenderer(bar);
+    }
+
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/rectangle/ClusterGroupRect.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/rectangle/ClusterGroupRect.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/rectangle/ClusterGroupRect.java	(revision 21054)
@@ -0,0 +1,183 @@
+/**
+ * @(#)ClusterGroupRect.java  12/12/99
+ * @author Peter Au
+ * All rights reserved.
+ */
+package vishnu.testvis.treemap.rectangle;
+
+import vishnu.testvis.object.*;
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+
+public class ClusterGroupRect extends ZRect
+{
+  static final private Color __COLOUR=Color.black;
+  private ClusterGroupObj _data;
+
+  public ClusterGroupRect(float x1, float y1, float x2, float y2,
+    Color c, ClusterGroupObj o)
+  {
+    super(x1,y1,x2,y2,c, o.getGroupSize());
+    _data = o;
+  }
+
+
+  public ClusterGroupRect(float x1, float y1, float x2, float y2,ClusterGroupObj o)
+  {
+      super(x1,y1,x2,y2,__COLOUR, o.getGroupSize());
+      _data = o;
+  }
+
+
+
+  public void paint(Graphics oldg)
+  {
+    Graphics2D g = (Graphics2D) oldg;
+    centerText2(g,__COLOUR);
+    g.setColor(__COLOUR);
+    g.draw(getBounds2D());
+  }
+
+
+  public void paint(Graphics oldg, float f)
+  {
+    Graphics2D g = (Graphics2D) oldg;
+    centerText2(g,__COLOUR);
+    g.setColor(__COLOUR);
+    paintBorder(oldg, f);
+  }
+
+
+
+
+
+  /**
+   * !!! bug:- should check the boundary of the box and the length of the strings
+   * display at most two labels on the centre of the rectangle
+   * @param oldg is the display area
+   * @param c is the colour
+   */
+  private void centerText(Graphics2D g, Color c)
+  {
+    String labels = _data.getLabels();
+
+    if (labels!=null){
+// Font f = new Font("Courier",Font.BOLD,12);
+      Font f = g.getFont();
+      FontMetrics fm = g.getFontMetrics(f);
+      float ascent = (float) fm.getAscent();
+      float height = (float) fm.getHeight();
+      float x=getUpperLeftX(), y=getUpperLeftY(),w=(float)getWidth(), h=(float)getHeight();
+      float width0=0F, width1=0F, x0=0F, x1=0F, y0=0F, y1=0F;
+
+        g.setColor(c);
+        width0 = (float) fm.stringWidth(labels);
+        x0 = x + (w-width0)/2F;
+
+// g.setFont(f);
+//   AffineTransform transform
+//     = AffineTransform.getRotateInstance(-90.0*Math.PI/180.0,100.0,50.0);
+        g.drawString(labels,x0,y0);
+        /*
+        if (labels.length==1){
+          y0 = y + (h-height)/2F + ascent;
+          g.drawString(labels[0],x0,y0);
+        }
+        else
+        {
+          width1 = (float) fm.stringWidth(labels[1]);
+          x1 = x + (w-width1)/2F;
+          y0 = y + (h-(height*2F))/2F + ascent;
+          y1 = y0 + (height*1F);
+          g.drawString(labels[0],x0,y0);
+          g.drawString(labels[1],x1,y1);
+        }
+        */
+    }
+  }  //-- centerText
+
+
+
+AffineTransform transform;
+
+  //-- pre: _data.getLabels() has at least 2 elements
+  //-- post: draw two labels to the graphic
+  private void centerText2(Graphics2D g, Color c)
+  {
+    String labels = _data.getLabels();
+    if (labels!=null)
+    {
+      Font f;
+      FontMetrics fm;
+      int ascent, height;
+      int x=(int) getUpperLeftX(), y=(int)getUpperLeftY(),
+          w=(int) getWidth(),      h=(int)getHeight();
+      int width0=0, width1=0, x0=0, x1=0, y0=0, y1=0;
+
+      g.setColor(c);
+
+      for (int fs=12; fs>=6; fs-=2)
+	  {
+        f = new Font("Dialog",Font.BOLD,fs);    // <--- Font for the labels
+
+        fm = g.getFontMetrics(f);
+        ascent = fm.getAscent();
+        height = fm.getHeight();
+        width0 = fm.stringWidth(labels);
+        /*
+        width0 = fm.stringWidth(labels[0]);
+        width1 = fm.stringWidth(labels[1]);
+        */
+        //-- paint horizontally
+        /*if (Math.max(width0,width1)<w  &&  2*height<h)*/
+        if (width0<w  &&  height<h)
+        {
+          x0 = x + (w-width0)/2;
+          //x1 = x + (w-width1)/2;
+          y0 = y + (h-(height)) + ascent;
+          //y1 = y0 + (height*1);
+          g.setFont(f);
+          g.drawString(labels,x0,y0);
+          //g.drawString(labels[1],x1,y1);
+ //System.err.println(labels[0] +", "+labels[1]+ " painted in font size: "+fs +"    horizontal!");
+
+          break;
+        }
+        //-- paint vertically
+/*
+        else if (Math.max(width0,width1)<h  &&  2*height<w)
+        {
+          g.setFont(f);
+
+          x0 = x + (w-width0)/2;
+          x1 = x + (w-width1)/2;
+          y0 = y + (h-(height*2))/2 + ascent;
+          y1 = y0 + (height*1);
+          g.setFont(f);
+		  transform
+     		= AffineTransform.getRotateInstance(-90.0*Math.PI/180.0,
+                (double)(x+w)/2.0,(double)(y+h)/2.0);
+		 g.setTransform(transform);
+         System.err.println(transform.toString());
+
+         g.drawString(labels[0],x0,y0);
+         g.drawString(labels[1],x1,y1);
+         System.err.println(labels[0] +", "+labels[1]+ " painted in font size: "+fs +"    vertical!");
+         System.err.println("x: "+x+"  y: "+y+"  w: "+w+"  h: "+h );
+
+         transform
+            = AffineTransform.getRotateInstance(360.0*Math.PI/180.0,
+               (double)(x+w)/2.0,(double)(y+h)/2.0);
+         g.setTransform(transform);
+         System.err.println(transform.toString());
+
+         break;
+        }
+*/
+
+
+      }  // for
+
+    }  // if
+  }  // centerText2
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/rectangle/ClusterRect.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/rectangle/ClusterRect.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/rectangle/ClusterRect.java	(revision 21054)
@@ -0,0 +1,163 @@
+/**
+ * @(#)ClusterRect.java  12/12/99
+ * @author Peter Au
+ * All rights reserved.
+ */
+
+package vishnu.testvis.treemap.rectangle;
+
+import vishnu.testvis.object.ClusterObj;
+import java.awt.*;
+
+
+public class ClusterRect extends ZRect
+{
+    private ClusterObj _data;
+	public ClusterRect(float x1, float y1, float x2, float y2, Color c, ClusterObj o)
+    {
+		super(x1,y1,x2,y2,c,o.getDocsSize());
+        _data = o;
+	}
+
+  public void paint(Graphics oldg){
+    Graphics2D g = (Graphics2D) oldg;
+    g.setColor(getColor());
+    g.fill(getBounds2D());
+    g.setColor(Color.white);
+    g.draw(getBounds2D());
+  }
+
+  public ClusterObj getClusterObj(){return _data;}
+
+  public String getInfo(){return _data.getInfo();}
+
+
+  //------------------------------------------------------------------
+  private int _xRef=0, _yRef=0;   // used in paintLabels and createTipBox
+
+  /**
+   * paint the cluster's labels on the graphical pane
+   * @param oldg is the graphics to be pained
+   * @param w is the max width of the graphic
+   * @param h is the max heigth of the graphic
+   */
+  public void paintLabels(Graphics oldg, float w, float h){
+  Color tipBoxColor = new Color(255,255,180);
+  Graphics2D g = (Graphics2D) oldg;
+  Font f = new Font("TimesRoman",Font.PLAIN,12);
+  FontMetrics fm = g.getFontMetrics(f);
+  int ascent = fm.getAscent();
+  int height = fm.getHeight();
+  int boxWidth = 4 + Math.max(fm.stringWidth(_data.getLabel1()),
+                              fm.stringWidth(_data.getLabel2()) );
+  int boxHeight = 0 + height * 2;
+  Polygon poly;
+
+    g.setFont(f);
+
+    //-- setup the position of the tipbox
+    poly = createTipBox(boxWidth, boxHeight, w, h);
+
+    //-- paint the box
+    g.setColor(tipBoxColor);
+    g.fillPolygon(poly);
+    g.setColor(Color.black);
+    g.drawPolygon(poly);
+    //-- paint the text
+    g.drawString(_data.getLabel1(), _xRef+3F,
+                _yRef + (boxHeight-(height*2F))/2F + ascent);
+    g.drawString(_data.getLabel2(), _xRef+3F,
+                _yRef + (boxHeight-(height*2F))/2F + ascent + height);
+
+  }
+
+
+  private Polygon createTipBox(int boxW, int boxH, float maxW, float maxH){
+  int[] xs = new int[8];
+  int[] ys = new int[8];
+  int x=0, y=0,
+    arrowH=10,
+    arrowW=8,
+    arrowOffset=8,
+    gap=5; //-- gap is the space between the tipbox and the rectangle
+  float position=0.2F;  //-- set the box position
+
+    try{
+
+      //-- try RHS
+      x = (int) getLowerRightX() + gap;
+      y = (int) (getUpperLeftY() + getHeight()*position);
+      if ((x+boxW<=maxW) && (y+boxH<=maxH)){
+        xs[0]=xs[6]=xs[4]=xs[3]=xs[7] = x;
+        xs[1]=xs[2] = x + boxW;
+        xs[5] = x - arrowH;
+        ys[5]=ys[0]=ys[1]=ys[7] = y;
+        ys[3]=ys[2] = y + boxH;
+        ys[6] = y + arrowOffset;
+        ys[4] = y + arrowOffset + arrowW;
+        throw new Exception();
+      }
+
+      //-- try LHS
+      x = (int) getUpperLeftX() - gap - boxW;
+      if ((x>=0) && (y+boxH<=maxH)){
+        xs[0]=xs[7]=xs[6] = x;
+        xs[1]=xs[2]=xs[4]=xs[5] = x + boxW;
+        xs[3] = x + boxW + arrowH;
+        ys[7]=ys[0]=ys[1]=ys[3] = y;
+        ys[6]=ys[5] = y + boxH;
+        ys[2] = y + arrowOffset;
+        ys[4] = y + arrowOffset + arrowW;
+        throw new Exception();
+      }
+
+      //-- try top
+      x = (int) (getUpperLeftX() + getWidth()*position);
+      y = (int) getUpperLeftY() - boxH - gap;
+      if ((y>=0) && (x+boxW<=maxW)){
+        xs[0]=xs[7]=xs[6]=xs[4] = x;
+        xs[1]=xs[2] = x + boxW;
+        xs[5] = x + arrowOffset;
+        xs[3] = x + arrowOffset + arrowW;
+        ys[7]=ys[0]=ys[1] = y;
+        ys[6]=ys[5]=ys[3]=ys[2] = y + boxH;
+        ys[4] = y + boxH + arrowH;
+        throw new Exception();
+      }
+
+      //-- try bottom
+      y = (int) getLowerRightY() + gap;
+      if ((y+boxH<=maxH) && (x+boxW<=maxW)){
+        xs[7]=xs[0]=xs[6]=xs[2] = x;
+        xs[4]=xs[5] = x + boxW;
+        xs[1] = x + arrowOffset;
+        xs[3] = x + arrowOffset + arrowW;
+        ys[7]=ys[0]=ys[1]=ys[3]=ys[4]= y;
+        ys[6]=ys[5] = y + boxH;
+        ys[2] = y - arrowH;
+        throw new Exception();
+      }
+
+      //-- try inside the rectangle
+      x = (int) getUpperLeftX() + 5;
+      y = (int) getUpperLeftY() + 5 + 5;
+      xs[7]=xs[0]=xs[6]=xs[2] = x;
+      xs[4]=xs[5] = x + boxW;
+      xs[1] = x + 3;
+      xs[3] = x + 3 + 5;
+      ys[7]= ys[0]=ys[1]=ys[3]=ys[4] = y;
+      ys[6]= ys[5]   = y + boxH;
+      ys[2] = y - 5;
+
+    }
+    catch(Exception e){
+    }
+
+    _xRef = x;
+    _yRef = y;
+    return (new  Polygon(xs,ys,xs.length));
+  }
+
+
+}
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/rectangle/Rect.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/rectangle/Rect.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/rectangle/Rect.java	(revision 21054)
@@ -0,0 +1,119 @@
+/**
+ * @(#)Rect.java  12/12/99
+ * @author Peter Au
+ * All rights reserved.
+ */
+
+package vishnu.testvis.treemap.rectangle;
+
+import java.awt.geom.Rectangle2D;
+import java.awt.*;
+
+/**
+ * The Rect class contains direction and colour, the direction is calculated
+ * based on the initial points.
+ */
+abstract class Rect extends Rectangle2D.Float{
+//-- the rectangle will move to this direction during zooming
+final static protected int __NORTH=0, __EAST=1, __SOUTH=2, __WEST=3;
+private int _direction;
+//-- any two points to create a rectangle
+private float _left_x, _left_y, _right_x, _right_y;
+//-- color of the rectangle
+private Color _colour;
+
+  public Rect(float x1, float y1, float x2, float y2, Color c){
+    setup(x1, y1, x2, y2);
+    _colour = c;
+  }
+
+
+  public void showInfo(){
+    System.err.println( "x: "+getX() +"  y: "+getY()+"  w: "+getWidth()+"  h: "+getHeight());
+    System.err.println(   "    direction: "+getDirection_toString()+"  colour: "+
+        _colour.toString());
+  }
+
+
+  /**
+   * setup the direction and the corresponding coordination
+   */
+  private void setup(float x1, float y1, float x2, float y2){
+    _left_x=x1; _left_y=y1;
+    _right_x=x2; _right_y=y2;
+    //-- set up the direction
+    if (x1<x2)
+      if (y1<y2) _direction = __WEST;
+      else if (y1>y2) _direction = __SOUTH;
+      else System.err.println("Error - Rectangle box cannot be zero");
+    else if (x1>x2)
+      if (y1<y2) _direction = __NORTH;
+      else if (y1>y2) _direction = __EAST;
+      else System.err.println("Error - Rectangle box cannot be zero");
+    else
+      System.err.println("Error - Rectangle box cannot be zero");
+    //-- setup the corresponding coordination of the rectangle obj
+    switch (_direction) {
+      case __NORTH:
+        super.setRect(x2,y1,x1-x2,y2-y1);
+        break;
+      case __EAST:
+        super.setRect(x2,y2,x1-x2,y1-y2);
+        break;
+      case __SOUTH:
+        super.setRect(x1,y2,x2-x1,y1-y2);
+        break;
+      case __WEST:
+        super.setRect(x1,y1,x2-x1,y2-y1);
+        break;
+    }
+  }  //-- setup
+
+
+  public void paint(Graphics oldg){
+    Graphics2D g = (Graphics2D) oldg;
+    g.setColor(getColor());
+    g.fill( getBounds2D() );
+    g.setColor(Color.black);
+    g.draw( getBounds2D() );
+  }
+
+  public void paintBorder(Graphics oldg, float w){
+    Graphics2D g = (Graphics2D) oldg;
+    g.fill(new Rectangle2D.Float(getUpperLeftX(),getUpperLeftY(),
+       w, (float) getHeight()));
+    g.fill(new Rectangle2D.Float(getLowerRightX()-w,getUpperLeftY(),
+       w, (float) getHeight()));
+    g.fill(new Rectangle2D.Float(getUpperLeftX(),getUpperLeftY(),
+       (float) getWidth(),w));
+    g.fill(new Rectangle2D.Float(getUpperLeftX(),getLowerRightY()-w,
+       (float) getWidth(),w));
+  }
+
+  public boolean isNorth(){return _direction==__NORTH;}
+  public boolean isEast(){return _direction==__EAST;}
+  public boolean isSouth(){return _direction==__SOUTH;}
+  public boolean isWest(){return _direction==__WEST;}
+
+  public Color getColor(){return _colour;}
+  public float getUpperLeftX(){return ((float) getX());}
+  public float getUpperLeftY(){return ((float) getY());}
+  public float getLowerRightX(){return ((float) (getX()+getWidth()));}
+  public float getLowerRightY(){return ((float) (getY()+getHeight()));}
+  public int getDirection(){return _direction;}
+  public int getOppositeDirection(){return (_direction+2)%4;}
+
+  /**
+   * @return the direction of the rectangule as a string
+   */
+  public String getDirection_toString(){
+    switch (_direction){
+      case __NORTH: return "NORTH";
+      case __EAST: return "EAST";
+      case __SOUTH: return "SOUTH";
+      default: return "WEST";
+    }
+  }
+
+
+}  //-- Rect
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/rectangle/ZRect.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/rectangle/ZRect.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/rectangle/ZRect.java	(revision 21054)
@@ -0,0 +1,157 @@
+/**
+ * @(#)ZRect.java  12/12/99
+ * @author Peter Au
+ * All rights reserved.
+ */
+
+package vishnu.testvis.treemap.rectangle;
+
+import java.awt.*;
+
+public class ZRect extends Rect{
+//-- no of doc that the rectangle represented
+private int _capacity=0;
+
+//-- the new points that the rectangle will be translated (java coordination)
+protected float _t_left_x=0f, _t_left_y=0f, _t_right_x=0f, _t_right_y=0f;
+//-- if the rectangle has been choosen, set it to true
+private boolean _chosen=false;
+
+  public ZRect(float x1, float y1, float x2, float y2, Color c){
+    super(x1, y1, x2, y2, c);
+  }
+
+
+  public ZRect(float x1, float y1, float x2, float y2, Color c, int s){
+    super(x1, y1, x2, y2, c);
+    _capacity = s;
+  }
+
+  public int getCapacity(){return _capacity;}
+  public void setCapacity(int i){_capacity=i;}
+
+  public boolean isChosen(){return _chosen;}
+  public void setChosen(){_chosen=true;}
+  public void resetChosen(){_chosen=false;}
+
+  /**
+   * set the new coordinate, that the rectangle will move to
+   * pre: the coordinate is based on Java graphic coordination
+   *      x1,y1 is the left upper corner and
+   *      x2,y2 is the right lower corner
+   */
+  public void translateTo(float x1, float y1, float x2, float y2){
+    _t_left_x=x1; _t_left_y=y1;
+    _t_right_x=x2; _t_right_y=y2;
+  }
+
+
+
+  private static boolean almostEqual(float a, float b){
+  float tolerant = 0.00001F;
+    return  (Math.abs(a-b) <= tolerant);
+  }
+
+
+  /**
+   * @param x is the input value
+   * @param x1 is the orginal x-coordinate of the rectangle
+   * @param y1 is the orginal y-coordinate of the rectangle
+   * @param x2 is the translated x-coordinate
+   * @param y2 is the translated y-coordinate
+   * @return y, based on x
+   */
+  private static float newY(
+    float x,
+    float x1, float y1, float x2, float y2,
+    int totalFrame, int currentFrame){
+//    return (x1==x2)? y2: ((x*(y1-y2) + x1*y2 - x2*y1) / (x1-x2));
+    if (almostEqual(x1,x2))
+      return ((y2-y1)/(float) totalFrame) * (float) (currentFrame+1) + y1;
+    else
+      return ((x*(y1-y2) + x1*y2 - x2*y1) / (x1-x2));
+  }
+
+  /**
+   * @param y is the input value
+   * @param x1 is the orginal x-coordinate of the rectangle
+   * @param y1 is the orginal y-coordinate of the rectangle
+   * @param x2 is the translated x-coordinate
+   * @param y2 is the translated y-coordinate
+   * @return x, based on y
+   */
+  private static float newX(
+    float y,
+    float x1, float y1, float x2, float y2,
+    int totalFrame, int currentFrame){
+//   return (y1==y2)? x2: ((y*(x1-x2) - x1*y2 + x2*y1)/(y1-y2));
+    if (almostEqual(y1,y2))
+      return ((x2-x1)/(float) totalFrame) * (float) (currentFrame+1) + x1;
+    else
+      return ((y*(x1-x2) - x1*y2 + x2*y1)/(y1-y2));
+  }
+
+
+  /**
+   * pre: translateTo is called
+   * @param how many animated pictures u want to create
+   * @retrun a list of animation picture (rectangles with diff positions)
+   */
+  public ZRect[] zooming(int noOfFrame){
+  ZRect[] vec = new ZRect[noOfFrame];
+  float dist1, dist2, t1, t2;
+
+    switch (getDirection()){
+      case __NORTH: case __SOUTH:
+        dist1 = (_t_left_y - getUpperLeftY())/(float)noOfFrame;
+        dist2 = (_t_right_y - getLowerRightY())/(float)noOfFrame;
+        t1 = getUpperLeftY();
+        t2 = getLowerRightY();
+        //-- create the animated pictures, except the last image
+        for(int i=0;i<noOfFrame-1;i++){
+          t1 += dist1;
+          t2 += dist2;
+          vec[i] = new ZRect(
+            newX(t1,getUpperLeftX(),getUpperLeftY(),_t_left_x,_t_left_y,noOfFrame,i),
+            t1,
+            newX(t2,getLowerRightX(),getLowerRightY(),_t_right_x,_t_right_y,noOfFrame,i),
+            t2,
+            getColor());
+        }
+        vec[vec.length-1] =
+          new ZRect(_t_left_x, _t_left_y, _t_right_x, _t_right_y, getColor());
+
+        break;
+      default:
+        //-- for WEST and EAST
+        dist1 = (_t_left_x - getUpperLeftX())/(float)noOfFrame;
+        dist2 = (_t_right_x - getLowerRightX())/(float)noOfFrame;
+        t1 = getUpperLeftX();
+        t2 = getLowerRightX();
+
+        //-- create the animated pictures, except the last image
+        for(int i=0;i<noOfFrame-1;i++){
+          t1 += dist1;
+          t2 += dist2;
+          vec[i] = new ZRect(
+            t1,
+            newY(t1,getUpperLeftX(),getUpperLeftY(),_t_left_x,_t_left_y,noOfFrame,i),
+            t2,
+            newY(t2,getLowerRightX(),getLowerRightY(),_t_right_x,_t_right_y,noOfFrame,i),
+            getColor());
+        }
+        /**
+         * create the final picture here, the reason we don't do in the above
+         * for loop is because we avoid the 'tolerant' error from the floating
+         * point computation.
+         */
+        vec[vec.length-1] =
+          new ZRect(_t_left_x, _t_left_y, _t_right_x, _t_right_y, getColor());
+
+
+    }  //-- switch
+
+    return vec;
+  }
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/table/BarRenderer.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/table/BarRenderer.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/table/BarRenderer.java	(revision 21054)
@@ -0,0 +1,56 @@
+/**
+ * @(#)BarRenderer.java  12/12/99
+ * @author Peter Au
+ * All rights reserved.
+ */
+
+package vishnu.testvis.treemap.table;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.table.TableCellRenderer;
+
+//!!! the % in getString and getColor may WRONG !!!
+
+public class BarRenderer extends JProgressBar implements TableCellRenderer
+{
+
+  public BarRenderer(){
+    super(JProgressBar.HORIZONTAL);
+    setBorderPainted(false);
+    this.setFont(new Font("Arial",Font.PLAIN,9));
+  }
+
+
+  public BarRenderer(int min, int max){
+    super(min,max);
+    setBorderPainted(false);
+    this.setFont(new Font("Arial",Font.PLAIN,9));
+  }
+
+  public String getString(){
+    if (getValue()==0) return "";
+    else return String.valueOf(getValue())+"%";
+  }
+
+  public Component getTableCellRendererComponent(JTable table, Object value,
+    boolean isSelected, boolean hasFocus, int row, int column){
+  int n = 0;
+    if (value instanceof Number){
+      n = ((Number) value).intValue();
+    } else{
+      System.err.println("Error - item in bar render is not a number");
+    }
+    setValue(n);
+    setForeground(getColor(n));
+    return this;
+  }
+
+  private Color getColor(int value){
+    if (value < 40) return Color.green;
+    else if (value <80) return Color.yellow;
+    else
+      return Color.red;
+  }
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/table/RW_DataModel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/table/RW_DataModel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/table/RW_DataModel.java	(revision 21054)
@@ -0,0 +1,289 @@
+/**
+ * @(#)RW_DataModel.java  12/12/99
+ * @author Peter Au
+ * All rights reserved.
+ */
+
+package vishnu.testvis.treemap.table;
+
+import vishnu.testvis.object.ClusterObj;
+import vishnu.testvis.object.DocObj;
+import javax.swing.table.DefaultTableModel;
+import java.util.*;
+import vishnu.testvis.sammon.QSortAlgorithmTableLength;
+import vishnu.testvis.object.LengthTable;
+
+/**
+ * This table contains 3 columns:
+ *   - a tick box
+ *   - a related word
+ *   - and the hit document frequency
+ */
+public class RW_DataModel extends DefaultTableModel{
+static final private String[] __COLNAMES={"","Related Word","Hit Doc Freq"};
+private String[] _related_words;
+private ClusterObj _cObj;
+private Vector _colnames = new Vector(__COLNAMES.length);
+
+private Vector _docs = null;  // this is created inside 'setData'
+
+
+  public RW_DataModel()
+  {
+    //-- setup the column's names
+    for (int i=0;i<__COLNAMES.length;i++)
+      _colnames.addElement(__COLNAMES[i]);
+    clear();
+  }
+
+
+  //-- clear all data variables in this data model
+  public void clear()
+  {
+    _related_words = null;
+    _cObj = null;
+    _docs = null;
+    setDataVector(new Vector(0), _colnames);
+  }
+
+
+  //--------------------------------------------------------------------------
+  /**
+   * Set up the cluster data in the data model
+   * pre: the related-words are new to the data model
+   * @param rw is a list of related words
+   * @param c is the data of the cluster
+   */
+  public void setData(String[] rw, ClusterObj c)
+  {
+    Vector dataVec = new Vector(0);
+    Vector hdfVec;
+    Vector docs = new Vector();
+    _related_words = rw;
+    _cObj = c;
+
+    hdfVec = _cObj.getHitDocFreq();
+    _docs = new Vector();
+// invarient: _hdfVec.size() == rw.length
+
+    //-- for each related word do
+    for(int i=0;i<hdfVec.size();i++){
+      int freq = ((Vector)hdfVec.elementAt(i)).size();
+      if (freq>0)
+      {
+        Vector  entry;
+        entry = new Vector(__COLNAMES.length+1);
+        entry.addElement(new Boolean(false));
+        entry.addElement(_related_words[i]);
+        entry.addElement(new Integer(freq*100/_cObj.getDocsSize()));
+        dataVec.addElement(entry);
+
+        /**
+         * This extra cell is for internal used only.
+         * each entry consists of a vector of doc, those documents contain the
+         * related-word (with weight>0)
+         *
+         * This is used in the filtering routine
+         * so if I sort the above I must likewise sort this one.
+         */
+         docs.addElement(hdfVec.elementAt(i));
+      }
+    }  // for
+    LengthTable [] table = new LengthTable[dataVec.size()];
+    for (int i=0;i<dataVec.size();i++)
+    {
+      Vector ent =  (Vector)dataVec.elementAt(i);
+      Integer in = (Integer)ent.elementAt(2);
+      table[i]=new LengthTable(i,in.intValue());
+    }
+    QSortAlgorithmTableLength qSort = new QSortAlgorithmTableLength();
+    try
+    {
+      qSort.sort(table);
+    }
+    catch (Exception e)
+    {}
+
+    Vector newDataVec= new Vector(dataVec.size());
+    for (int i=dataVec.size()-1;i>=0;i--)
+    {
+      int index = table[i].index;
+      newDataVec.addElement(dataVec.elementAt(index));
+      _docs.addElement(docs.elementAt(index));
+
+    }
+
+    setDataVector(newDataVec, _colnames);
+  } // setData
+  
+  // overloaded, this one for dendro
+  // we pass on freq vector instead of obj
+  public void setData(String[] rw, vishnu.testvis.dendro.ClusterObj _cObj)
+  {
+    Vector dataVec = new Vector(0);
+    Vector hdfVec;
+    Vector docs = new Vector();
+    _related_words = rw;
+
+    hdfVec = _cObj.getHitDocFreq();
+    _docs = new Vector();
+// invarient: _hdfVec.size() == rw.length
+
+    //-- for each related word do
+    for(int i=0;i<hdfVec.size();i++){
+      int freq = ((Vector)hdfVec.elementAt(i)).size();
+      if (freq>0)
+      {
+        Vector  entry;
+        entry = new Vector(__COLNAMES.length+1);
+        entry.addElement(new Boolean(false));
+        entry.addElement(_related_words[i]);
+        entry.addElement(new Integer(freq*100/_cObj.getDocsSize()));
+        dataVec.addElement(entry);
+
+        /**
+         * This extra cell is for internal used only.
+         * each entry consists of a vector of doc, those documents contain the
+         * related-word (with weight>0)
+         *
+         * This is used in the filtering routine
+         * so if I sort the above I must likewise sort this one.
+         */
+         docs.addElement(hdfVec.elementAt(i));
+      }
+    }  // for
+    LengthTable [] table = new LengthTable[dataVec.size()];
+    for (int i=0;i<dataVec.size();i++)
+    {
+      Vector ent =  (Vector)dataVec.elementAt(i);
+      Integer in = (Integer)ent.elementAt(2);
+      table[i]=new LengthTable(i,in.intValue());
+    }
+    QSortAlgorithmTableLength qSort = new QSortAlgorithmTableLength();
+    try
+    {
+      qSort.sort(table);
+    }
+    catch (Exception e)
+    {}
+
+    Vector newDataVec= new Vector(dataVec.size());
+    for (int i=dataVec.size()-1;i>=0;i--)
+    {
+      int index = table[i].index;
+      newDataVec.addElement(dataVec.elementAt(index));
+      _docs.addElement(docs.elementAt(index));
+
+    }
+
+    setDataVector(newDataVec, _colnames);
+  } // setData
+
+
+  public String[] getRefinedTrecIDs()
+  {
+      Vector sVec = new Vector();
+      HashSet set = new HashSet();  //-- this can avoid duplicated items !
+      boolean isNewItem;
+      
+      for(int i=0;i<getDataVector().size();i++)
+      {
+            if (((Boolean) getValueAt(i,0)).booleanValue())
+            {
+                
+                Vector docs = (Vector) _docs.elementAt(i);
+
+                for (int j=0;j<docs.size();j++)
+                {
+                    isNewItem = set.add((DocObj) docs.elementAt(j));
+                    if (isNewItem)
+                    {
+                        sVec.addElement(((DocObj)docs.elementAt(j)).getTrecID());
+                        //System.out.println(((DocObj) docs.elementAt(j)).getTrecID());
+                    }
+                }
+            }
+      }
+      
+      String[] sStr = new String[sVec.size()];
+      for( int i = 0; i < sVec.size(); i++ )
+      {
+          sStr[i] = new String((String)sVec.elementAt(i));
+      }
+      return sStr;
+  }
+  
+  public String[] getFilterWords()
+  {
+        Vector sVect = new Vector();
+        
+        for(int i=0;i<getDataVector().size();i++)
+        {
+            if (((Boolean) getValueAt(i,0)).booleanValue())
+            {
+                sVect.addElement((String)getValueAt(i,1));
+            }   
+        }
+        
+        String sStr[] = new String[sVect.size()];
+        
+        for( int i = 0; i < sVect.size(); i++ )
+        {
+            sStr[i] = (String)sVect.elementAt(i);    
+        }
+        return sStr;
+  }
+
+  public String getRefinedDocs(){
+  String s = "";
+  HashSet set = new HashSet();  //-- this can avoid duplicated items !
+  boolean isNewItem;
+    for(int i=0;i<getDataVector().size();i++){
+
+      if (((Boolean) getValueAt(i,0)).booleanValue()) {
+       Vector docs = (Vector) _docs.elementAt(i);
+
+        for (int j=0;j<docs.size();j++){
+          isNewItem = set.add((DocObj) docs.elementAt(j));
+          if (isNewItem)
+          {
+            s += ((DocObj) docs.elementAt(j)).format4Fields();
+            //System.out.println(((DocObj) docs.elementAt(j)).format4Fields());
+          }
+            
+        }
+
+      }
+
+    }
+    s = "<html><font face=Arial size=+10> " + set.size()  + " documents found\n" + s +
+        "</font></html>\n";
+    return s;
+  }
+
+
+
+
+  //--------------------------------------------------------------------------
+  public Class getColumnClass(int col)
+  {
+    switch (col)
+    {
+      case 0: return Boolean.class;
+      case 1: return String.class;
+      case 2: return Integer.class;
+      default: return Object.class;
+    }
+  }
+
+  public int getColumnCount(){return __COLNAMES.length;}
+
+  public boolean isCellEditable(int row, int col){
+    return ((col==0) ? true : false);
+  }
+  public void setValueAt(Object value, int row, int col){
+    super.setValueAt(value,row,col);
+  }
+
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/visual/NavigatorPanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/visual/NavigatorPanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/visual/NavigatorPanel.java	(revision 21054)
@@ -0,0 +1,189 @@
+/**
+ * @(#)NavigatorPanel.java  12/12/99
+ * @author Peter Au
+ * All rights reserved.
+ */
+
+package vishnu.testvis.treemap.visual;
+
+import vishnu.testvis.visual.Vishnu;
+import vishnu.testvis.object.ClusterObj;
+import vishnu.testvis.object.DataManager;
+import vishnu.testvis.treemap.table.*;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import java.util.Vector;
+
+
+public class NavigatorPanel extends JComponent
+{
+    private JTable _table;
+    private JButton _showAllButton, _refineButton;
+    private JEditorPane _display;
+    private TMNavigator _outerFrame;
+    private ClusterObj _cObj;
+//public String _codeBase;
+
+    private RW_DataModel _dataModel;  //-- this is created within this class
+
+
+    public NavigatorPanel(JTable t, /*JButton b1,*/ JButton b2,
+        JEditorPane d, TMNavigator f)
+    {
+        _table = t;
+        /*_showAllButton = b1;
+        _showAllButton.setActionCommand("showAllDoc");
+        _showAllButton.setToolTipText("show ALL documents within the selected cluster");
+        _showAllButton.addActionListener(new ButtonHandler());*/
+
+        _refineButton = b2;
+        _refineButton.setActionCommand("refineDoc");
+        _refineButton.setToolTipText("show documents with any of the selected related-words");
+        _refineButton.addActionListener(new ButtonHandler());
+
+        _display = d;
+        _outerFrame = f;
+
+        _display.addHyperlinkListener(new HyperlinkListener() {
+                public void hyperlinkUpdate(HyperlinkEvent e)
+                {
+	            // Handle clicks; ignore mouseovers and other link-related events
+	                if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
+                    {
+	            // Get the HREF of the link and display it.
+                        String hpresult = e.getDescription();
+                        int index = Integer.parseInt(hpresult);
+                        System.out.println("hyperlink result: " + index);
+                        if (index > 0)
+                            _outerFrame._displayFrame.dataManager.fetchDocNo(index);
+	                }
+                }
+            });
+        _dataModel = new RW_DataModel();
+        _table.setModel(_dataModel);
+
+
+
+        _cObj = null;
+        unableButtons();
+        setupRenderer();
+    }
+
+    private void unableButtons()
+    {
+        //_showAllButton.setEnabled(false);
+        _refineButton.setEnabled(false);
+    }
+    private void enableButtons()
+    {
+        //_showAllButton.setEnabled(true);
+        _refineButton.setEnabled(true);
+    }
+
+    public void clear()
+    {
+        _cObj = null;
+        _dataModel.clear();
+        _display.setText("");
+        unableButtons();
+    }
+
+
+
+    public void setData(String[] rw, ClusterObj c)
+    {
+        _cObj = c;
+        _dataModel.setData(rw, _cObj);
+        enableButtons();
+        setupRenderer();
+    }
+
+
+    private void setupRenderer()
+    {
+        BarRenderer bar = new BarRenderer();
+        //-- Have a number painted in the middle of the bar
+        bar.setStringPainted(true);
+        //-- setup the background color of the bar
+        bar.setBackground(_table.getBackground());
+
+        //-- render the last column as a bar
+        _table.getColumnModel().getColumn(2).setCellRenderer(bar);
+    }
+    
+    public void showDocuments()
+    {
+        if( _cObj != null )
+        {
+            _display.setText(_cObj.formatDocs());
+        }
+    }
+     
+
+    class ButtonHandler implements ActionListener
+    {
+        public void actionPerformed(ActionEvent event)
+        {
+            if (_cObj!=null)
+            {
+                String action = event.getActionCommand();
+                if (action.equals("showAllDoc"))
+                {
+                    _outerFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+                    _display.setText(_cObj.formatDocs());
+                    _outerFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+                }
+                else if (action.equals("refineDoc"))
+                {
+                    _outerFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+                    _display.setText(   _dataModel.getRefinedDocs()   );
+                    _outerFrame.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+                    
+                    _outerFrame._displayFrame.log.startsection("FILTERING");
+       
+                    String trec_id[] = _dataModel.getRefinedTrecIDs();
+                    String filter_Words[] = _dataModel.getFilterWords();
+                    String s = new String("");
+                    for( int i = 0; i < filter_Words.length; i++ )
+                    {
+                            s += filter_Words[i] + " ";
+                    }
+      
+                   _outerFrame._displayFrame.log.log("FILTER_WORDS",s);
+                   _outerFrame._displayFrame.log.log("FILTERED_DOCS",Integer.toString(trec_id.length));
+       
+                   Vector performance = _outerFrame._displayFrame.queries.getPerformance(_outerFrame._displayFrame.query, trec_id);
+                   
+                   _outerFrame._displayFrame.log.startsection("PERFORMANCE");
+                    
+                   if( performance.size() > 0 )
+                   {
+                        Integer relvTotal = (Integer)performance.elementAt(performance.size()-2);
+                        Integer relvRetr = (Integer)performance.elementAt(performance.size()-1);
+                        _outerFrame._displayFrame.log.log("RELEVANT_TOTAL",Integer.toString(relvTotal.intValue()));
+                        _outerFrame._displayFrame.log.log("RELEVANT_RETRIEVED",Integer.toString(relvRetr.intValue()));
+                        for( int i = 0; i < performance.size()-2; i++ )
+                        {
+                            Integer perf = (Integer)performance.elementAt(i);
+                            String str = "PRECISION_" + Integer.toString((i+1)*10);
+                            _outerFrame._displayFrame.log.log(str,Integer.toString(perf.intValue()));
+                        }
+                   }
+                   else
+                   {
+                        _outerFrame._displayFrame.log.log("INFO","No relevance data available");
+                   }
+                
+                    _outerFrame._displayFrame.log.endsection("PERFORMANCE");
+                    _outerFrame._displayFrame.log.endsection("FILTERING");
+                    
+                }
+            }
+        }
+    }  //-- end ButtonHandler
+
+
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/visual/TMNavigator.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/visual/TMNavigator.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/visual/TMNavigator.java	(revision 21054)
@@ -0,0 +1,159 @@
+/**
+ * @(#)Navigator.java  12/12/99
+ * @author Peter Au
+ * All rights reserved.
+ */
+
+package vishnu.testvis.treemap.visual;
+import vishnu.testvis.visual.*;
+import vishnu.testvis.visual.Vishnu;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.text.html.*;
+import javax.swing.border.BevelBorder;
+
+
+public class TMNavigator extends JPanel
+{
+    final private int _TREEMAPWIDTH=300, _TREEMAPHEIGHT=350;
+    public final static int LIST_ON_LEFT = 0;
+    public final static int LIST_ON_RIGHT = 1;
+    private int listPos;
+
+//    JTextField _searchTF = new JTextField();
+//    JButton _searchButton = new JButton("Search");
+    public Vishnu _displayFrame;
+    JScrollPane _displayJSP = new JScrollPane();
+    JEditorPane _displayJEP = new JEditorPane();
+    JTable _table = new JTable();
+    JButton _showAllButton = new JButton("Show All Documents");
+    JButton _refineButton = new JButton("Filter");
+    NavigatorPanel navigator;
+//  = new NavigatorPanel(_table, _showAllButton, _refineButton, _displayJEP, this,_codeBase);
+
+    JButton _backButton = new JButton("<<   Back");
+    TreemapPanel _treemapPanel;
+//  = new TreemapPanel(_TREEMAPWIDTH,_TREEMAPHEIGHT, _backButton, navigator, _codeBase);
+
+
+
+	public TMNavigator(Vishnu displayFrame, int listPos)
+    {
+   	    _displayFrame = displayFrame;
+   	    this.listPos = listPos;
+   	    
+        navigator = new NavigatorPanel(_table, /*_showAllButton*/ _refineButton, _displayJEP, this);
+        _treemapPanel = new TreemapPanel(_TREEMAPWIDTH,_TREEMAPHEIGHT, _backButton, navigator, _displayFrame);
+        
+        buildGUI();
+        
+	    setSize(_TREEMAPWIDTH*2+40,_TREEMAPHEIGHT+340);
+        //setVisible(false);
+	    //setupEventHandler();
+	}
+
+    public void setVisible(boolean f)
+    {
+        super.setVisible(f);
+        _treemapPanel.setVisible(f);
+    }
+
+    void buildGUI()
+    {
+        JPanel tmpPanel;
+        JScrollPane tmpJSP;
+
+        //this.setLayout(new FlowLayout(FlowLayout.LEFT,10,10));
+        this.setLayout(null);
+//        _searchTF.setFont(new Font("Courier",Font.BOLD,16));
+//        _searchTF.setBounds(10,10,400,30);
+//        add(_searchTF,null);
+
+//        _searchButton.setActionCommand("search");
+//        _searchButton.setBounds(10+400+10,10,100,30);
+//        add(_searchButton,null);
+        _backButton.setBorder(BorderFactory.createEtchedBorder());
+        _backButton.setFont(new java.awt.Font("Monospaced", 0, 12));
+        _backButton.setBounds(10+_TREEMAPWIDTH+10,10+_TREEMAPHEIGHT+10,200,30);
+        add(_backButton,null);
+
+        if( listPos == 0 ) // list on left
+        {
+            tmpPanel = newJPanel(_treemapPanel,Color.gray,
+            10,10,_TREEMAPWIDTH+3,_TREEMAPHEIGHT+2);
+            add(tmpPanel,null);
+        }
+        if( listPos == 1 ) // list on right
+        {
+            tmpPanel = newJPanel(_treemapPanel,Color.gray,
+            10+_TREEMAPWIDTH+10,10,_TREEMAPWIDTH+3,_TREEMAPHEIGHT+2);
+            add(tmpPanel,null);
+        }
+
+
+        _displayJEP.setEditorKit(new HTMLEditorKit());
+        _displayJEP.setEditable(false);
+        Dimension sSize=this.getToolkit().getScreenSize();
+        tmpJSP = new JScrollPane();
+        tmpJSP.getViewport().add(_displayJEP);
+        tmpPanel = newJPanel(tmpJSP, Color.white,
+            10, 10+_TREEMAPHEIGHT+10+30+10, _TREEMAPWIDTH*2+10,
+            sSize.height>700?200:85);
+        add(tmpPanel,null);
+
+        //-- manually creating the columns
+        _table.setAutoCreateColumnsFromModel(false);
+        _table.getColumnModel().getColumn(0).setPreferredWidth(20);
+        _table.getColumnModel().getColumn(1).setPreferredWidth(_TREEMAPWIDTH*2/3);
+        _table.getColumnModel().getColumn(2).setPreferredWidth(_TREEMAPWIDTH/3);
+        _table.setRowSelectionAllowed(false);
+        tmpJSP = new JScrollPane();
+        tmpJSP.getViewport().add(_table);
+        
+        if( listPos == 0 )
+        {
+            tmpPanel = newJPanel(tmpJSP, Color.white,
+            10+_TREEMAPWIDTH+10, 10, _TREEMAPWIDTH, _TREEMAPHEIGHT+2);
+            add(tmpPanel,null);
+        }
+        if( listPos == 1 )
+        {
+            tmpPanel = newJPanel(tmpJSP, Color.white,
+            10, 10, _TREEMAPWIDTH, _TREEMAPHEIGHT+2);
+            add(tmpPanel,null);
+        }
+        
+        
+        _showAllButton.setFont(new java.awt.Font("Monospaced", 0, 12));
+        _showAllButton.setBounds(10+_TREEMAPWIDTH+10,10+_TREEMAPHEIGHT+10,200,30);
+        _showAllButton.setBorder(BorderFactory.createEtchedBorder());
+        //add(_showAllButton,null);
+
+        _refineButton.setFont(new java.awt.Font("Monospaced", 0, 12));
+        _refineButton.setBounds(10+_TREEMAPWIDTH+10+200,10+_TREEMAPHEIGHT+10,100,30);
+        _refineButton.setBorder(BorderFactory.createEtchedBorder());
+        add(_refineButton,null);
+
+    }
+
+
+    private JPanel newJPanel(Component c, Color bg,int x, int y, int w, int h)
+    {
+        JPanel p = new JPanel();
+        p.setBorder(new BevelBorder(BevelBorder.LOWERED));
+        p.setLayout(new BorderLayout());
+        p.setBounds(x,y,w,h);
+        p.setBackground(bg);
+        p.add(c,BorderLayout.CENTER);
+        return p;
+  }
+
+    public void update()
+    {
+        _treemapPanel.newSearch();
+    }
+
+
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/visual/TreemapPanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/visual/TreemapPanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/visual/TreemapPanel.java	(revision 21054)
@@ -0,0 +1,539 @@
+/**
+* name      TreemapPanel.java 
+* author    Peter Au
+* modified  Daniel Heesch (29.04.02)
+*/
+
+package vishnu.testvis.treemap.visual;
+
+import vishnu.testvis.object.DataManager;
+import vishnu.testvis.treemap.rectangle.ClusterRect;
+import vishnu.testvis.visual.*;
+import vishnu.testvis.object.*;
+import java.util.Stack;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import java.util.*;
+
+public class TreemapPanel extends Canvas
+{
+
+    private int _width, _height;
+    private JButton _popUpButton;
+    private NavigatorPanel _navigator;
+    private String _newTerms;
+    Vishnu _displayFrame;
+    Image _offScreenImage=null;
+    boolean _init_done=false, _drillDownFlag=false, _newSearchFlag=false, _popUpFlag=false;
+    Stack _visualObjs;
+    
+    JPopupMenu[] popups;
+    PopupMenu popup;
+
+    public TreemapPanel(int w, int h, JButton bb, NavigatorPanel n, Vishnu displayFrame)
+    {
+        _displayFrame = displayFrame;
+        _width=w; _height=h;
+        _popUpButton = bb;
+        _navigator = n;
+        _displayFrame = displayFrame;
+        setSize(_width,_height);
+        _visualObjs = new Stack();
+        addMouseListener(new MouseHandler());
+        _popUpButton.setActionCommand("popUp");
+        _popUpButton.setEnabled(false);
+        _popUpButton.setToolTipText("Go back to the previous level");
+        _popUpButton.addActionListener(new ButtonHandler());
+                   
+        popup = new PopupMenu();
+        
+        add(popup);
+
+    }
+
+    class MouseHandler extends MouseAdapter{}
+
+    class ButtonHandler implements ActionListener
+    {
+        public void actionPerformed(ActionEvent event)
+        {
+            String action = event.getActionCommand();
+            String st;
+            if (action.equals("popUp")){
+                processPopUp();
+            }
+        }
+    }
+    
+    public void update(Graphics oldg)
+    {
+        Graphics2D g = (Graphics2D) oldg;
+        Visualiser currentVObj;
+        
+        if (_newSearchFlag)
+        {
+            _visualObjs.push(new Visualiser(_displayFrame.dataManager, 0F,0F, _width, _height));
+            currentVObj = ((Visualiser) _visualObjs.peek());
+            currentVObj.createTreemapView(createImage(_width, _height));
+            
+            //-- draw the treemap view on the offscreen-buffer
+            currentVObj.drawTreemapView(_offScreenImage, this);
+            
+            _newSearchFlag = false;
+        }
+        else if (_drillDownFlag)
+        {
+            _displayFrame.refine = true;
+            currentVObj = (Visualiser) _visualObjs.peek();
+            
+            //-- create a list of image buffer for the animated pictures
+            Image iBuffer[]=new Image[Visualiser.__FRAME];
+            
+            for(int i=0;i<iBuffer.length;i++)
+                iBuffer[i]=createImage(_width,_height);
+
+            //-- prepare the zooming pictures of the current visaul object
+            currentVObj.prepare_zooming(iBuffer, this);
+
+            int s = _visualObjs.size();
+            float x = Visualiser.__borderwidth*s,
+                y = Visualiser.__borderheight*s;
+         
+            DataManager dm = new DataManager(_displayFrame);
+            dm.query(_newTerms);
+            _visualObjs.push(new Visualiser(dm,x, y, _width-x*2, _height-y*2));
+
+                /**
+                * create a treemap view for the new data
+                * before doing so, we have to copy the last zooming picture to the treemap
+                * this is because the zooming picture contains the borders
+                */
+                
+            Image tmpImg = createImage(_width, _height);
+            Graphics2D tmpGph = (Graphics2D) tmpImg.getGraphics();
+            tmpGph.drawImage(currentVObj.getLastZoomingImage(),0,0,this);
+            
+            //-- now create the treemap view
+            ((Visualiser) _visualObjs.peek()).createTreemapView(tmpImg);
+
+            //-- draw the treemap view (top element of the stack) on the offscreen buffer
+            ((Visualiser) _visualObjs.peek()).drawTreemapView(_offScreenImage, this);
+
+            //-- when everyting is done, we then show the animated pictures
+            //-- we use the 2nd element of the stack, not the top one
+            currentVObj.show_zoomIn(g,this);
+
+            _drillDownFlag = false;
+            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+        }
+        else if (_popUpFlag)
+        {
+
+            currentVObj = (Visualiser) _visualObjs.peek();
+            //-- redraw the treemap view on the offscreen buffer, as the previous
+            //-- level is gone
+            currentVObj.drawTreemapView(_offScreenImage, this);
+            //-- show the animated pictures
+            currentVObj.show_zoomOut(g,this);
+            _popUpFlag = false;
+        }
+        paint(g);
+    }
+
+
+    public void clearDisplay()
+    {
+        _navigator.clear();
+    }
+
+
+    public void processPopUp()
+    {
+        if (_visualObjs.size() > 1)
+        {
+            _displayFrame.log.log("UNREFINE_QUERY");
+            //-- get rid of the top obj in the stack
+            _visualObjs.pop();
+            //-- reset the necessary instance variable back to the default values
+            ((Visualiser) _visualObjs.peek()).setDefault();
+            //-- clear the table and display
+            clearDisplay();
+            if (_visualObjs.size()==1) _popUpButton.setEnabled(false);
+            _popUpFlag = true;
+            repaint();
+        }
+    }
+
+    public void newSearch()
+    {
+        clearDisplay();
+        _popUpButton.setEnabled(false);
+        _visualObjs = new Stack();
+        _newSearchFlag = true;
+        repaint();
+    }
+
+
+    private void processDrillDown(int x, int y){
+        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+        ClusterRect r = ((Visualiser) _visualObjs.peek()).drillDown(x,y);
+        //-- chose which cluster to drill down
+        if (r!=null){
+            //-- do sth on 'r'??? send it back to the server and do the clustering again
+            _popUpButton.setEnabled(true);
+            clearDisplay();
+            _drillDownFlag = true;
+            _newTerms = r.getClusterObj().getLabel();
+            _navigator.clear();  //-- clear the table and the display
+            repaint();
+
+        }
+        else
+            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+
+
+
+    private void processInspect(int x, int y){
+        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+        ClusterRect r = ((Visualiser) _visualObjs.peek()).inspect(x,y);
+        if (r!=null){
+            //-- draw a corner on the selected cluster
+            ((Visualiser) _visualObjs.peek()).drawCorner(r, _offScreenImage,
+                this, x, y, _width, _height);
+            //-- setup the data model in the table
+            _navigator.setData(((Visualiser) _visualObjs.peek()).getDataManager().getWords(),
+                r.getClusterObj());
+                
+            // and show all documents!
+            _navigator.showDocuments();            
+                
+            repaint();
+        }
+        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+    
+    
+    protected void processMouseEvent(MouseEvent e)
+    {
+        if (e.getID()==MouseEvent.MOUSE_CLICKED)
+        {       
+            
+        if (!_visualObjs.empty())
+        {
+            Font weak = new Font("Arial",Font.PLAIN,14);
+            Font bold = new Font("Arial",Font.BOLD,14);
+            
+            final ClusterRect r = ((Visualiser) _visualObjs.peek()).inspect(e.getX(),e.getY());
+            ClusterObj cObj = r.getClusterObj();
+            int numDocs = cObj.getDocsSize();
+            
+            boolean img_hasChanged;
+                    
+            popup.removeAll();
+            MenuItem menuItem = new MenuItem(numDocs + " documents");
+            menuItem.setFont(bold);
+            
+            menuItem.addActionListener(new ActionListener()
+            {
+                public void actionPerformed(ActionEvent actionEvent)
+                {
+                       
+                    if (r!=null)
+                    {
+                        
+                        //-- draw a corner on the selected cluster
+                //        ((Visualiser) _visualObjs.peek()).drawCorner(r, _offScreenImage,
+                //            this, x, y, _width, _height);
+                        //-- setup the data model in the table
+                        
+                        String str = r.getClusterObj().getLabel();
+                        _displayFrame.log.log("SELECTED_CLUSTER",str);
+                        _navigator.setData(((Visualiser) _visualObjs.peek()).getDataManager().getWords(),
+                        r.getClusterObj());
+                
+                        // and show all documents!
+                        _navigator.showDocuments();            
+                
+                        repaint();
+                    }
+         
+                }
+            });
+            
+            popup.add(menuItem);
+            popup.addSeparator();
+                  
+            menuItem = new MenuItem("Refined Query");
+            menuItem.setFont(bold);
+            menuItem.addActionListener(new ActionListener()
+            {
+                public void actionPerformed(ActionEvent actionEvent)
+                {
+                    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+                    
+                    // all this does is return the rectangle, on which drill down
+                    // is to be performed
+                    //ClusterRect r = ((Visualiser) _visualObjs.peek()).drillDown(x,y);
+                    //-- chose which cluster to drill down
+                    if (r!=null)
+                    {
+                        //-- do sth on 'r'??? send it back to the server and do the clustering again
+                        _popUpButton.setEnabled(true);
+                        clearDisplay();
+                        _drillDownFlag = true;
+                        _newTerms = r.getClusterObj().getLabel();
+                        _navigator.clear();  //-- clear the table and the display
+                        repaint();
+                    }
+                    else
+                    setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+                }
+            });
+            
+            popup.add(menuItem);
+            popup.addSeparator();
+            String label = cObj.getLabel();
+            
+            StringTokenizer st = new StringTokenizer(label);
+                       
+            while( st.hasMoreTokens() )
+            {
+                menuItem = new MenuItem(st.nextToken());
+                menuItem.setFont(bold);
+                menuItem.setEnabled(false);
+                popup.add(menuItem);
+            }
+                        
+            popup.show(this,e.getX(),e.getY());
+            
+            /*img_hasChanged = ((Visualiser) _visualObjs.peek()).drawTipBox(
+                _offScreenImage, this, e.getX(),e.getY(), _width,_height);
+            System.out.println("Changed?: " + img_hasChanged);
+            if (img_hasChanged)*/  repaint();
+        }
+        
+        }  // if mouseclick
+    }
+
+
+    //------------------------------------------------------------------
+
+    public void paint(Graphics oldg)
+    {
+        //System.out.println("Tree map paint called");
+        Graphics2D tmpGph, g = (Graphics2D) oldg;
+        Image tmpImg;
+        /**
+        * create off-scrren images for double-buffering
+        * There is a known deficiency bug (Java itself) with createImage in java applications.
+        * Workaround: to create necessary image buffers inside paint!
+        */
+        if (!_init_done){
+            _offScreenImage = createImage(_width, _height);
+            _init_done = true;
+        }
+
+        /*if(this.isVisible())*/ g.drawImage(_offScreenImage,0,0,this);
+
+    }  //-- paint
+
+
+
+}  //-- TreemapPanel
+
+
+        /*
+        addMouseMotionListener( new MouseMotionAdapter()
+        {
+            public void mouseMoved(MouseEvent e)
+            {
+                if (!_visualObjs.empty())
+                {
+                    final ClusterRect r = ((Visualiser) _visualObjs.peek()).inspect(e.getX(),e.getY());
+                    ClusterObj cObj = r.getClusterObj();
+                    int numDocs = cObj.getDocsSize();
+                    System.out.print("Mouse moved to ");           
+                    System.out.println(e.getX() + " " + e.getY());
+                    
+                    boolean img_hasChanged;
+            
+                    popup.removeAll();
+                    MenuItem menuItem = new MenuItem(numDocs + " documents");
+                    menuItem.addActionListener(new ActionListener()
+                    {
+                        public void actionPerformed(ActionEvent actionEvent)
+                        {
+                            System.out.println("Show documents");
+                    
+                            if (r!=null)
+                            {
+                                //-- draw a corner on the selected cluster
+                                //((Visualiser) _visualObjs.peek()).drawCorner(r, _offScreenImage,
+                                // this, x, y, _width, _height);
+                                //-- setup the data model in the table
+                                _navigator.setData(((Visualiser) _visualObjs.peek()).getDataManager().getWords(),
+                                r.getClusterObj());
+                
+                                // and show all documents!
+                                _navigator.showDocuments();            
+                
+                                 repaint();
+                            }
+         
+                        }
+                    });
+            
+                    popup.add(menuItem);
+                
+                    menuItem = new MenuItem("Drill down");
+                    menuItem.addActionListener(new ActionListener()
+                    {
+                        public void actionPerformed(ActionEvent actionEvent)
+                        {
+                            System.out.println("Drill down clicked");
+                        }
+                    });
+            
+                    popup.add(menuItem);
+            
+                    popup.show(this,e.getX(),e.getY());
+            
+                    img_hasChanged = ((Visualiser) _visualObjs.peek()).drawTipBox(
+                    _offScreenImage, this, e.getX(),e.getY(), _width,_height);
+                    System.out.println("Changed?: " + img_hasChanged);
+                    if (img_hasChanged)  repaint();
+                }
+            }
+        });*/
+        
+        /*  protected void processMouseEvent(MouseEvent e)
+    {
+
+        if (!_visualObjs.empty())
+        {
+            //-- if the mouse points outside the obj, then copy the original treemap view
+            //-- without the tipbox to the screen
+            if (e.getID()==MouseEvent.MOUSE_EXITED){
+                //-- get the most update view of the treemap
+                ((Visualiser) _visualObjs.peek()).drawTreemapView(_offScreenImage, this);
+                repaint();
+            }
+            else if (e.getID()==MouseEvent.MOUSE_CLICKED){
+
+                //-- if the user pressed the first mouse button
+                if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK)
+                {
+                    System.out.println("Mouse clicked");
+                    processInspect(e.getX(),e.getY());
+                    
+                }
+                //-- if the user pressed the third mouse button
+                else if ((e.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+                    processDrillDown(e.getX(),e.getY());
+
+            }
+        }
+
+    }*/
+
+    //------------------------------------------------------------------
+/*    protected void processMouseMotionEvent(MouseEvent e)
+    {
+        System.out.println("Outer Mouse in motion");
+        if (!_visualObjs.empty())
+        {
+            Font weak = new Font("Arial",Font.PLAIN,14);
+            Font bold = new Font("Arial",Font.BOLD,14);
+            
+            final ClusterRect r = ((Visualiser) _visualObjs.peek()).inspect(e.getX(),e.getY());
+            ClusterObj cObj = r.getClusterObj();
+            int numDocs = cObj.getDocsSize();
+            
+            System.out.println("Inner Mouse in motion");
+            System.out.println(e.getX() + " " + e.getY());
+            //System.out.println("Mouse in motion");
+            boolean img_hasChanged;
+                    
+            popup.removeAll();
+            MenuItem menuItem = new MenuItem(numDocs + " documents");
+            menuItem.setFont(bold);
+            
+            menuItem.addActionListener(new ActionListener()
+            {
+                public void actionPerformed(ActionEvent actionEvent)
+                {
+                    System.out.println("Show documents");
+                    
+                    if (r!=null)
+                    {
+                        //-- draw a corner on the selected cluster
+                //        ((Visualiser) _visualObjs.peek()).drawCorner(r, _offScreenImage,
+                //            this, x, y, _width, _height);
+                        //-- setup the data model in the table
+                        _navigator.setData(((Visualiser) _visualObjs.peek()).getDataManager().getWords(),
+                        r.getClusterObj());
+                
+                        // and show all documents!
+                        _navigator.showDocuments();            
+                
+                        repaint();
+                    }
+         
+                }
+            });
+            
+            popup.add(menuItem);
+            popup.addSeparator();
+                  
+            menuItem = new MenuItem("Refined Query");
+            menuItem.setFont(bold);
+            menuItem.addActionListener(new ActionListener()
+            {
+                public void actionPerformed(ActionEvent actionEvent)
+                {
+                    setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+                    
+                    // all this does is return the rectangle, on which drill down
+                    // is to be performed
+                    //ClusterRect r = ((Visualiser) _visualObjs.peek()).drillDown(x,y);
+                    //-- chose which cluster to drill down
+                    if (r!=null)
+                    {
+                        //-- do sth on 'r'??? send it back to the server and do the clustering again
+                        _popUpButton.setEnabled(true);
+                        clearDisplay();
+                        _drillDownFlag = true;
+                        _newTerms = r.getClusterObj().getLabel();
+                        _navigator.clear();  //-- clear the table and the display
+                        repaint();
+                    }
+                    else
+                    setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+                }
+            });
+            
+            popup.add(menuItem);
+            popup.addSeparator();
+            String label = cObj.getLabel();
+            
+            StringTokenizer st = new StringTokenizer(label);
+                       
+            while( st.hasMoreTokens() )
+            {
+                menuItem = new MenuItem(st.nextToken());
+                menuItem.setFont(bold);
+                menuItem.setEnabled(false);
+                popup.add(menuItem);
+            }
+                        
+            popup.show(this,e.getX(),e.getY());
+            
+            //img_hasChanged = ((Visualiser) _visualObjs.peek()).drawTipBox(
+            //   _offScreenImage, this, e.getX(),e.getY(), _width,_height);
+            //System.out.println("Changed?: " + img_hasChanged);
+            //if (img_hasChanged)
+            repaint();
+        }
+    }*/
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/visual/Visualiser.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/visual/Visualiser.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/treemap/visual/Visualiser.java	(revision 21054)
@@ -0,0 +1,671 @@
+/**
+ * @(#)Visualiser.java  12/12/99
+ * @author Peter Au
+ * All rights reserved.
+ */
+
+package vishnu.testvis.treemap.visual;
+
+import vishnu.testvis.object.*;
+import vishnu.testvis.treemap.rectangle.*;
+import java.lang.Exception;
+import java.awt.*;
+import java.awt.image.ImageObserver;
+import java.awt.geom.GeneralPath;
+
+public class Visualiser extends Canvas implements Runnable
+{
+
+  static private final int __FRAMEDELAY = 100;
+
+  static final int __FRAME=10;
+  static float __borderwidth=5F, __borderheight=5F;
+//-- the following variables specify the actual drawing area
+  final private float _upperX, _upperY, _width, _height;
+
+  private DataManager _data;
+  private Image _treemapView = null;  //-- static treemap view
+  private Image[] _zoomingImages;
+
+//-- rectangles represent the cluster-groups and the clusters
+  private ClusterGroupRect[] _gpRects = null;
+  private ClusterRect[][] _cRects = null;
+
+//-- the rectangle the mouse pointed to, used for tipbox
+  private ClusterRect _previousRect = null;
+
+//-- to identify which rectangle has a corner-paint
+  private ClusterRect _cornerRect = null;
+
+
+  /**
+   * Constructor: visualise the data into the specific size of pane
+   * @param d is the data obj
+   * @param x is the left upper x-coordinate of the display pane
+   * @param y is the left upper y-coordinate of the display pane
+   * @param w is the width of the display pane
+   * @param h is the height of the display pane
+   */
+  public Visualiser(DataManager d, float x, float y, float w, float h)
+  {
+    int noOfGroup;
+    _data = d;
+    _upperX=x; _upperY=y; _width=w; _height=h;
+
+    //-- allocate memory for _cRects and _gpRects
+    noOfGroup = _data.getClusterGroupsSize();
+    _gpRects = new ClusterGroupRect[noOfGroup];
+    _cRects = new ClusterRect[noOfGroup][];
+    for (int i=0; i<noOfGroup; i++)
+      _cRects[i] = new ClusterRect [_data.getClusterGroup(i).getClustersSize()];
+    // create rectangles for the corresponding groups and clusters
+    createRects();
+  }
+
+
+  static public void setBorder(float w, float h)
+  {
+    __borderwidth = w;
+    __borderheight = h;
+  }
+
+
+  public DataManager getDataManager(){return _data;}
+
+  //---------------------------------------------------------------------
+  /**
+   * set different variables back to default values
+   */
+  public void setDefault()
+  {
+    _previousRect = null;
+    _cornerRect = null;
+    try
+    {
+      for (int i=0;i<_gpRects.length; i++)
+        if (_gpRects[i].isChosen())
+          for(int j=0;j<_cRects[i].length;j++)
+            if (_cRects[i][j].isChosen())
+	    {
+              _gpRects[i].resetChosen();
+              _cRects[i][j].resetChosen();
+              throw new Exception();
+            }  //-- if
+    }
+    catch(Exception e)
+    {
+    }  //-- catch
+  }
+
+
+  //---------------------------------------------------------------------
+  /**
+   * @return a snapshot of the tree map
+   */
+  public Image getTreemapView(){
+    return _treemapView;
+  }
+
+
+  /**
+   * create a treemap view of the obj, only call once
+   * @param img
+   */
+  public void createTreemapView(Image img)
+  {
+  	final float GP_BORDERWIDTH = 1F; // <-- set the width of the border of each group
+    _treemapView = img;
+    Graphics2D g = (Graphics2D) _treemapView.getGraphics();
+    for (int i=0;i<_gpRects.length; i++)
+	{
+      for(int j=0;j<_cRects[i].length;j++)
+	  {
+        _cRects[i][j].paint(g);
+      }
+      _gpRects[i].paint(g);
+    } //-- for
+    //-- paint the border of each group
+    for (int i=0;i<_gpRects.length; i++)
+       _gpRects[i].paintBorder(g,GP_BORDERWIDTH);
+    // paint the outer border
+    ZRect fooRect = new ZRect(_upperX,_upperY,_upperX+_width,_upperY+_height,null);
+    g.setColor(Color.black);
+    fooRect.paintBorder(g, GP_BORDERWIDTH);
+
+  }  //-- create_pict
+
+
+
+  //---------------------------------------------------------------------
+  /**
+   * draw the treemap view on the image, together with the corner triangle
+   */
+  public void drawTreemapView(Image img, ImageObserver observer)
+  {
+    Graphics2D g = (Graphics2D) img.getGraphics();
+    g.drawImage(_treemapView,0,0,observer);
+    drawCorner(g);
+  }
+
+
+  /**
+   * draw a small triangle on the top-right corner of the _cornerRect and make
+   * sure that the tipbox is drawn as well
+   */
+  public void drawCorner(ClusterRect r, Image offScreenImg, ImageObserver observer,
+    int x, int y, float w, float h)
+  {
+    _cornerRect = r;
+    _previousRect = null; //-- force the tipbox to be repainted
+    drawTipBox(offScreenImg, observer, x, y, w, h);
+  }
+
+
+  /**
+   * draw a small triangle on the top-right corner of the _cornerRect
+   */
+  private void drawCorner(Graphics2D g)
+  {
+    if (_cornerRect!=null)
+    {
+      GeneralPath p;
+      float x=_cornerRect.getLowerRightX(), y=_cornerRect.getUpperLeftY(), w=8F;
+      p = new GeneralPath(GeneralPath.WIND_NON_ZERO);
+      if (Math.min(_cornerRect.getWidth(),_cornerRect.getHeight())<w)
+        w = (float) Math.min(_cornerRect.getWidth(),_cornerRect.getHeight());
+      p.moveTo(x-0.7F,   y+0.9F);
+      p.lineTo(x-0.7F-w, y+0.9F);
+      p.lineTo(x-0.7F,   y+0.9F+w);
+      p.closePath();
+      g.setColor(Color.black);
+      g.fill(p);
+    }
+  }
+
+
+  /**
+   * signal the caller (by returning ture) to update the screen, a tipbox
+   * with the label of the cluster is drawn on the offscreen-image accordingly
+   * @param offScreenImg is the image where the tip box to be drawn on
+   * @param observer
+   * @param x is the horizontal x position of the mouse event
+   * @param y is the vertical y position  of the mouse event
+   * @param w is the max width of the image to be used
+   * @param h is the max height of the image to be used
+   * @return true if the tipbox is drawn on the offScreenImg, otherwise false
+   */
+  public boolean drawTipBox(Image offScreenImg, ImageObserver observer,
+    int x, int y, float w, float h)
+  {
+    Graphics2D g = (Graphics2D) offScreenImg.getGraphics();
+    ClusterRect tmpRect=null;
+
+    //-- find out which rectangle the mouse points to
+    try{
+      for (int i=0;i<_gpRects.length; i++)
+        if (_gpRects[i].contains(x,y))
+          for(int j=0;j<_cRects[i].length;j++)
+            if (_cRects[i][j].contains(x,y))
+            {
+              tmpRect = _cRects[i][j];
+              throw new Exception();
+            }  //-- if
+
+      //-- the mouse is not pointed to any of the rectangles
+      if (_previousRect==null) return false;
+      else
+      {
+        //-- the previous pict has a tip box => draw the orginal pict
+        _previousRect = null;
+        drawTreemapView(offScreenImg, observer);
+        return true;
+      }
+    }
+    catch(Exception e){
+      //-- the mouse is pointed to a cluster's rectangle
+
+      if (_previousRect==tmpRect)
+      {
+        //-- if the tip box is already on the graphic, we don't need to update
+        return false;
+      }
+      else
+      {
+        _previousRect = tmpRect;
+        drawTreemapView(offScreenImg, observer);
+        if (_previousRect!=null)
+        {
+          _previousRect.paintLabels(g, w, h);
+        }
+        return true;
+      }
+
+    }  //-- catch
+  }  //-- drawTipBox
+
+
+
+  //---------------------------------------------------------------------
+  /**
+   * find out which rectangle the mouse point to (for inspect)
+   * @param x is the x-cordinate of the mouse position
+   * @param y is the y-cordinate of the mouse position
+   * @return a cluster rectangle that the mouse pointed to
+   */
+  public ClusterRect inspect(int x, int y)
+  {
+    return findClusterRect(x, y, false);
+  }
+
+  /**
+   * find out which rectangle the mouse point to (for drill down)
+   * @param x is the x-cordinate of the mouse position
+   * @param y is the y-cordinate of the mouse position
+   * @return a cluster rectangle that the mouse pointed to
+   */
+  public ClusterRect drillDown(int x, int y)
+  {
+    return findClusterRect(x, y, true);
+  }
+
+  /**
+   * @param x is the horizontal x position of the mouse event
+   * @param y is the vertical y position  of the mouse event
+   * @param setChosenFlag
+   * @return the cluster rectangle that the mouse points to, and if the
+   *   'setChosenFlag' is true, then set up the corresponding flag in
+   *   _gpRect and _cRect
+   */
+  private ClusterRect findClusterRect(int x, int y, boolean setChosenFlag)
+  {
+    //-- find out which rectangle the mouse points to
+    for (int i=0;i<_gpRects.length; i++)
+        if (_gpRects[i].contains(x,y))
+            for(int j=0;j<_cRects[i].length;j++)
+                if (_cRects[i][j].contains(x,y))
+                {
+                    if (setChosenFlag)
+                    {
+                        _gpRects[i].setChosen();
+                        _cRects[i][j].setChosen();
+                    }
+                    return _cRects[i][j];
+                }  //-- if
+    return null;
+  }
+
+
+  //---------------------------------------------------------------------
+  /**
+   * !!! BUG - Color is not fixed !!!
+   * initialise _gpRects and _cRects
+   * create_sub_rects is called
+   */
+  public void createRects()
+  {
+    float area = _width*_height;  //-- the area of the display
+    int noOfGroup = _data.getClusterGroupsSize();
+    float totalDocs = (float) _data.getDocCount();
+    float lx=_upperX, ly=_upperY, rx=_upperX+_width, ry=_upperY+_height, tmp=0F;
+    Color baseColor = Color.yellow;
+    ClusterGroupObj gpObj;
+    int gpSize;
+
+    for (int i=0;i<noOfGroup; i++)
+    {
+        gpObj = _data.getClusterGroup(i);
+        gpSize = gpObj.getGroupSize();
+
+    //??? Color is not fixed !!!
+        if (i==0) baseColor=Color.cyan;
+        if (i==1) baseColor=Color.green;
+        if (i==2) baseColor=Color.blue;
+        if (i==3) baseColor=Color.orange;
+        if (i==4) baseColor=Color.pink;
+        if (i==5) baseColor=Color.red;
+
+      //-- West
+        if (rx>lx && ry>ly)
+        {
+            tmp = rx;
+            rx = ((float)gpSize/totalDocs)*area/(ry-ly)+lx;
+
+            create_sub_rects(i, rx, ly, lx, ry,
+            gpObj.getClusters(), gpSize, baseColor);
+
+            _gpRects[i] = new ClusterGroupRect(lx,ly,rx,ry, baseColor, _data.getClusterGroup(i));
+            lx = tmp;
+        }
+      //-- North
+        else if (lx>rx && ry>ly)
+        {
+            tmp = ry;
+            ry = ((float)gpSize/totalDocs)*area/(lx-rx)+ly;
+
+            create_sub_rects(i, rx, ly, lx, ry,
+            gpObj.getClusters(), gpSize, baseColor);
+
+            _gpRects[i] = new ClusterGroupRect(lx,ly,rx,ry, baseColor, _data.getClusterGroup(i));
+            ly = tmp;
+        }
+      //-- East
+        else if (lx>rx && ly>ry)
+        {
+            tmp = rx;
+            rx = lx - ((float)gpSize/totalDocs)*area/(ly-ry);
+
+            create_sub_rects(i, rx, ly, lx, ry,
+                gpObj.getClusters(), gpSize, baseColor);
+
+            _gpRects[i] = new ClusterGroupRect(lx,ly,rx,ry, baseColor, _data.getClusterGroup(i));
+            lx = tmp;
+        }
+      //-- South
+        else if (rx>lx && ly>ry)
+        {
+            tmp = ry;
+            ry = ly - ((float)gpSize/totalDocs)*area/(rx-lx);
+
+            create_sub_rects(i, rx, ly, lx, ry,
+            gpObj.getClusters(), gpSize, baseColor);
+
+            _gpRects[i] = new ClusterGroupRect(lx,ly,rx,ry, baseColor, _data.getClusterGroup(i));
+            ly = tmp;
+        }
+    } //-- for
+  }  //-- createRects
+
+
+  /**
+   * !!! BUG: Color hasn't fixed !!!
+   * create the rectangles for the clusters in the specified group
+   * @param gpNo is the index of the Group currently working on
+   * @param lx is the initial left x-coordinate (based on Rect coordination)
+   * @param ly is the initial left y-coordinate (ditto)
+   * @param rx is the initial right x-coordinate (ditto)
+   * @param ry is the initial right y-coordinate (ditto)
+   * @param cs is the list of clusters in the group
+   * @param total is the total number of documents found in the group
+   * @param col is the base colour of the group
+   */
+  private void create_sub_rects(int gpNo, float lx, float ly, float rx, float ry,
+                             ClusterObj[] cs, int total, Color col)
+  {
+
+    ZRect dummy = new ZRect(lx,ly,rx,ry,null);
+    float tmp, area = (float) (dummy.getWidth()*dummy.getHeight());
+
+    for(int i=0;i<cs.length;i++)
+    {
+
+     if (rx>lx && ry>ly)
+     {
+       tmp = rx;
+        rx = ((float)cs[i].getDocsSize()/(float)total)*area/(ry-ly)+lx;
+        _cRects[gpNo][i] = new ClusterRect(lx,ly,rx,ry,col,cs[i]);
+        lx = tmp;
+      }
+      //-- North
+      else if (lx>rx && ry>ly)
+      {
+        tmp = ry;
+        ry = ((float)cs[i].getDocsSize()/(float)total)*area/(lx-rx)+ly;
+        _cRects[gpNo][i] = new ClusterRect(lx,ly,rx,ry,col,cs[i]);
+        ly = tmp;
+      }
+      //-- East
+      else if (lx>rx && ly>ry)
+      {
+        tmp = rx;
+        rx = lx - ((float)cs[i].getDocsSize()/(float)total)*area/(ly-ry);
+        _cRects[gpNo][i] = new ClusterRect(lx,ly,rx,ry,col,cs[i]);
+        lx = tmp;
+      }
+      //-- South
+      else if (rx>lx && ly>ry)
+      {
+        tmp = ry;
+        ry = ly - ((float)cs[i].getDocsSize()/(float)total)*area/(rx-lx);
+        _cRects[gpNo][i] = new ClusterRect(lx,ly,rx,ry,col,cs[i]);
+        ly = tmp;
+      }
+
+//??? Color hasn't fixed !!!
+      col = darker(col);
+
+    }
+  }  //-- create_sub_rects
+
+
+    /**
+     * @see        java.awt.Color#darker
+     */
+    private Color darker(Color c) {
+    double FACTOR = 0.85;
+      return new Color(Math.max((int)(c.getRed()  *FACTOR), 0),
+       Math.max((int)(c.getGreen()*FACTOR), 0),
+       Math.max((int)(c.getBlue() *FACTOR), 0));
+    }
+
+
+
+  //---------------------------------------------------------------------
+  //-- pre: _zoomImages can't be null
+  public Image getLastZoomingImage()
+  {
+    return _zoomingImages[_zoomingImages.length-1];
+  }
+
+  public Image getZoomingImage(int i){return _zoomingImages[i];}
+
+
+  public void show_zoomIn(Graphics oldg, ImageObserver observer)
+  {
+    Graphics2D g = (Graphics2D) oldg;
+    for(int i=0;i<_zoomingImages.length;i++)
+    {
+      //-- time delay for each frame
+      timeDelay();
+      g.drawImage(_zoomingImages[i],0,0,observer);
+    }
+    //-- this is time delay for the last frame and the new treemap view
+    timeDelay();
+  }
+
+
+  public void show_zoomOut(Graphics oldg, ImageObserver observer)
+  {
+    Graphics2D g = (Graphics2D) oldg;
+    for(int i=_zoomingImages.length-1;i>=0;i--)
+    {
+      //-- time delay for each frame
+      timeDelay();
+      g.drawImage(_zoomingImages[i],0,0,observer);
+    }
+    //-- this is time delay for the last frame and the new treemap view
+    timeDelay();
+  }
+
+
+
+  private void timeDelay()
+  {
+    Thread delayThread = new Thread(this);
+    delayThread.start();
+    try{
+      delayThread.join();
+    }
+    catch (InterruptedException ex)
+    {
+    }
+  }
+
+
+  public void run()
+  {
+    long lastTime=0, time=0;
+    do
+    {
+      time = System.currentTimeMillis();
+      try{
+        Thread.sleep(__FRAMEDELAY);
+      }
+      catch (InterruptedException ex)
+      {
+      }
+      lastTime = time;
+    } while (time - lastTime > __FRAMEDELAY);
+
+  }
+
+
+
+  //---------------------------------------------------------------------
+  /**
+   * draw the animated zooming pictures on the buffer
+   * @param a list of images that is used for animation pictures
+   * @param observer
+   */
+  public void prepare_zooming(Image[] buffer, ImageObserver observer)
+  {
+    _zoomingImages = buffer;
+    prepare_zooming_buffer(observer);
+    create_zooming_picts();
+  }
+
+  /**
+   * draw the border on each buffer
+   * @param a list of buffer
+   */
+  private void prepare_zooming_buffer(ImageObserver observer)
+  {
+    Graphics2D g;
+    if (_zoomingImages==null)
+    {
+      System.err.println("Error - zooming buffer cannot be empty!!!");
+    }
+    else
+    {
+      //-- copy the static view to one buffer
+      g = (Graphics2D) _zoomingImages[0].getGraphics();
+      g.drawImage(_treemapView,0,0,observer);
+      //-- clear the main treemap area and leave the border
+      g.setColor(Color.white);
+      g.fill(new Rectangle.Float(_upperX, _upperY, _width, _height));
+      //-- copy that buffer to other buffers
+      for(int k=1;k<_zoomingImages.length;k++)
+      {
+        g = (Graphics2D) _zoomingImages[k].getGraphics();
+        g.drawImage(_zoomingImages[0],0,0,observer);
+      }
+    }
+  }
+
+
+  private void create_zooming_picts()
+  {
+    int[] edgeSize = {0,0,0,0};
+    float w_y, e_y, n_x, s_x, portion, tmp;
+    ZRect[] tmpRects;
+    Graphics tmpGph;
+    /**
+     * pre-calculate the edgeSize
+     * the edgeSize represents the total number of docs on each border
+     */
+    for(int i=0;i<_gpRects.length;i++)
+      if (!_gpRects[i].isChosen())
+        edgeSize[_gpRects[i].getDirection()] += _gpRects[i].getCapacity();
+      else
+        for(int j=0;j<_cRects[i].length;j++)
+          if(!_cRects[i][j].isChosen())
+            edgeSize[_gpRects[i].getDirection()] += _cRects[i][j].getCapacity();
+
+    //-- initise the starting points on each side
+    w_y=_upperY;                e_y=_upperY+__borderheight;
+    n_x=_upperX+__borderwidth;  s_x=_upperX;
+
+
+    for(int i=0;i<_gpRects.length;i++)
+    {
+      //-- prepare zooming the whole group, if the user has NOT chosen a cluster
+      //-- within the group
+      if (!_gpRects[i].isChosen())
+      {
+        portion = ((float)_gpRects[i].getCapacity()) /
+                  ((float) edgeSize[_gpRects[i].getDirection()]);
+        if (_gpRects[i].isWest())
+        {
+          tmp = w_y + (portion * (float)(_height-__borderheight));
+          _gpRects[i].translateTo(_upperX, w_y, _upperX+__borderwidth, tmp);
+          w_y = tmp;
+        }
+        else if (_gpRects[i].isEast())
+        {
+          tmp = e_y + (portion * (float)(_height-__borderheight));
+          _gpRects[i].translateTo(_upperX+_width-__borderwidth, e_y, _upperX+_width, tmp);
+          e_y = tmp;
+        }
+        else if (_gpRects[i].isNorth())
+        {
+          tmp = n_x + (portion * (float)(_width-__borderwidth));
+          _gpRects[i].translateTo(n_x, _upperY, tmp, _upperY+__borderheight);
+          n_x = tmp;
+
+        }
+        else
+        {
+          tmp = s_x + (portion * (float)(_width-__borderwidth));
+          _gpRects[i].translateTo(s_x, _upperY+_height-__borderheight, tmp, _upperY+_height);
+          s_x = tmp;
+        }
+
+        //-- generate a list of animation picture
+        tmpRects = _gpRects[i].zooming(_zoomingImages.length);
+
+        //-- draw the rectangles on the offsreen buffer
+        for(int j=0;j<_zoomingImages.length;j++)
+          tmpRects[j].paint(_zoomingImages[j].getGraphics());
+
+      }
+      else
+        //-- prepare zooming each cluster within the group
+        for (int j=0;j<_cRects[i].length;j++)
+          if (!_cRects[i][j].isChosen())
+          {
+            portion = ((float) _cRects[i][j].getCapacity()) /
+                      ((float) edgeSize[_gpRects[i].getDirection()]);
+            if (_gpRects[i].isWest())
+            {
+              tmp = w_y + (portion * (float)(_height-__borderheight));
+              _cRects[i][j].translateTo(_upperX, w_y, _upperX+__borderwidth, tmp);
+              w_y = tmp;
+            }
+            else if (_gpRects[i].isEast())
+            {
+              tmp = e_y + (portion * (float)(_height-__borderheight));
+              _cRects[i][j].translateTo(_upperX+_width-__borderwidth, e_y, _upperX+_width, tmp);
+              e_y = tmp;
+            }
+            else if (_gpRects[i].isNorth())
+            {
+              tmp = n_x + (portion * (float)(_width-__borderwidth));
+              _cRects[i][j].translateTo(n_x, _upperY, tmp, _upperY+__borderheight);
+              n_x = tmp;
+            }
+            else
+            {
+              tmp = s_x + (portion * (float)(_width-__borderwidth));
+              _cRects[i][j].translateTo(s_x, _upperY+_height-__borderheight, tmp, _upperY+_height);
+              s_x = tmp;
+            }  //-- if
+
+            //-- generate a list of animation picture
+            tmpRects = _cRects[i][j].zooming(_zoomingImages.length);
+
+            //-- draw the rectangles on the offsreen buffer
+            for(int k=0;k<_zoomingImages.length;k++)
+              tmpRects[k].paint(_zoomingImages[k].getGraphics());
+          }  //-- if
+    }  //-- for
+  }  //-- prepare_zooming
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Cluster.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Cluster.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Cluster.java	(revision 21054)
@@ -0,0 +1,117 @@
+package vishnu.testvis.visual;
+
+import java.util.*;
+import java.net.*;
+import java.io.*;
+import java.lang.*;
+import java.util.zip.*;
+
+class Cluster
+{
+    static final int NOVALUE = 99999999;
+    static int MAX;
+    static final int EMPTYARRAY[] = {};
+    static int matrix[][];
+    static int data[][];
+    static int clusters[][];
+    static int keywords[];
+    static int maxDocuments;
+    static int finalCluster;
+
+    static void buildMatrix ()
+    {
+	float temp;
+	float theDiff;
+	int keyword1, keyword2;
+    	matrix = new int[MAX][MAX];
+        for (int word1=0;word1<MAX;word1++)
+        {
+	    keyword1 = keywords[word1];
+	    for (int word2=0; word2<MAX;word2++)
+            {
+		keyword2 = keywords[word2];
+                theDiff = 0;
+                for (int doc=0;doc<maxDocuments;doc++)
+                {	
+		    temp= data[doc][keyword1]-data[doc][keyword2];
+                    theDiff+= temp*temp;
+                }
+                temp = (int)Math.sqrt(theDiff);
+                matrix[word1][word2]=matrix[word2][word1]=(int)temp;
+            }
+         }
+	for (int row = 0;row <MAX; row++)
+	   matrix[row][row] = NOVALUE;
+    } 
+
+    static void findSmallest ()
+    {
+	int group = matrix[0].length-1;
+	int smallest = NOVALUE;
+	int smallRow = NOVALUE;
+	int smallCol = NOVALUE;
+	int row,col;
+	for (row =0; row<MAX; row++)
+	    for (col=0; col<MAX; col++)
+	       if (smallest > matrix[row][col])
+	        {
+		   smallest = matrix[row][col];
+		   smallRow = row;	
+		   smallCol = col;
+ 	        }
+	for (col=0; col<MAX; col++)
+	{
+	    if (matrix[smallRow][col] < matrix[smallCol][col])
+		matrix[smallRow][col] = matrix[smallCol][col];
+            matrix[smallCol][col] = NOVALUE;
+	}
+	addClusters(smallRow, smallCol);
+	for (row =0; row<MAX; row++)
+        {
+	    matrix[row][smallCol] = NOVALUE;
+            matrix[row][smallRow] = matrix[smallRow][row];
+	} 	
+    }
+
+    static void addClusters(int destination, int source)
+    {
+	int i;
+	int temp[] = new int[clusters[destination].length+clusters[source].length];
+	for (i = 0; i<clusters[destination].length;i++)
+	    temp[i] = clusters[destination][i];
+	for (i = 0; i<clusters[source].length;i++)
+	    temp[i+clusters[destination].length] = clusters[source][i];
+	clusters[destination] = temp;
+	clusters[source] = EMPTYARRAY;
+	finalCluster = destination;
+    }
+
+    static void preClusters()
+    {
+	clusters = new int[MAX][1];
+	for (int row =0; row <MAX; row++)
+	{
+	    clusters[row][0] = row;
+	}
+    }
+
+    static int[] maximunDistance (int[][] newData, int[] newKeywords, int documentCount)
+    {
+	data = newData;
+	keywords = newKeywords;
+	maxDocuments = documentCount;
+	MAX = keywords.length;
+	buildMatrix();
+	preClusters();
+	for (int i=1;i<MAX;i++)
+	{
+ 	    findSmallest();
+        }
+	int temp[] = new int[MAX];
+	for (int i=1;i<MAX;i++)
+	    temp[i] = keywords[clusters[finalCluster][i]];
+	return temp;
+    }
+}
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/CvManyPoints.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/CvManyPoints.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/CvManyPoints.java	(revision 21054)
@@ -0,0 +1,1124 @@
+package vishnu.testvis.visual;
+
+import java.awt.*;
+import java.applet.*;
+import java.awt.event.*;
+import java.util.*;
+import java.net.*;
+import java.io.*;
+import java.awt.Cursor;
+import vishnu.testvis.object.*;
+import vishnu.datablock.*;
+
+
+public class CvManyPoints extends Canvas
+{
+    protected Vishnu displayFrame;
+    protected RadVizPanel radVizPanel;
+    protected DataManager dataManager;
+    protected int [] docs;
+    
+    /** constant square shape flag */
+    static final int SQUARESHAPE = 0;
+    /** constant round shape flag */
+    static final int ROUNDSHAPE = 1;
+    /** constant diamond shape flag */
+    static final int DIAMONDSHAPE = 2;
+    /** constant maximum keywords allowed */
+    static final int ABSMAXKEYWORD = 1000;
+    /** constant maximum documents allowed */
+    static final int ABSMAXDOC = 1000;
+    /** count of the documents */
+    protected int documentCount = 1000;
+    /** circle array size */
+    protected int maxCircleCount = 40;
+    /** number of circles */
+    protected int circleCount = 40;
+    /** number of spokes */
+    protected int spokeCount = 16;
+    /** radius of the display in fp units */
+    protected float r = 10.0F;
+    /** locked flag */
+    protected boolean isLocked = false;
+    /** projected flag */
+    protected boolean isProjected = true;
+    /** document selected flag */
+    protected boolean atDoc = false;
+    /** grid flag */
+    protected boolean displayGrid = false;
+    /** rim flag */
+    protected boolean displayRim = true;
+    /** doc point shape flag */
+    protected int docsShape = SQUARESHAPE;
+    /** edge node shape flag */
+    protected int nodesShape = ROUNDSHAPE;
+    /** colour of nodes */
+    protected Color defNodesColor = Color.black;
+    /** colour of docs */
+    protected Color defDocsColor = Color.orange;
+    /** colour of highlighted nodes */
+    protected Color defHighlight = Color.blue;
+    /** colour of selected documents */
+    protected Color defSelect = Color.red;
+    /** Radius divided by radians */
+    protected float magic = (float)((double)r / (Math.PI / 2.0) );
+    /** projection factor */
+    protected float factor = 3F;
+    /** image object used for double buffering graphics */
+    protected Image image=null;
+    /** graphics used for double buffering graphics */
+    protected Graphics gImage=null;
+    /** double buffered image size */
+    protected int imgW,imgH;
+    /** dimensions of the canvas */
+    protected Dimension dim;
+    /** the integer location of 0,0 fp axis */
+    protected int centerX, centerY;
+    /** virtual pixel size and canvas fp dimensions */
+    protected float pixelSize, rWidth = 25.0F, rHeight = 25.0F;
+    /** array of edge node locations */
+    protected Point2D[] points= null;
+    /** selected edge node */
+    protected Point2D tp =null;
+    /** selected edge node index */
+    protected int tpIndex = 0;
+    /** angle stored for rotating edge nodes */
+    protected float lastAngle;
+    /** Full 2D linked list of data */
+    
+    protected Pt2DNode matrix= null;
+    /** Array of partial lists for each key word */
+    protected Pt2DNode columns[] = null;
+    /** The currently selected document if any */
+    protected Pt2DNode selectDoc = null;
+    /** A class that stores the keywords and whether they are displayed */
+    protected KeyWordArray keyWordArray;
+    /** Array of linked lists that stores the data to be displayed */
+    protected SparseMatrix theDataMatrix;
+    /** A class that maps displayed keywords to stored keywords */
+    protected MapKeyWords mapping;
+    /** A two dimensional array of integers used for clustering */
+    //protected int [][] fullMatrix;
+    protected float [][] fullMatrix;
+    /** array of data to draw target */
+    protected int circles[][] = new int[maxCircleCount][4];
+    /** array of data to draw rim */
+    protected int rim[] = new int[4];
+    /** array of data to draw spokes */
+    protected int spokes[][] = new int [spokeCount][2];
+    /** font for the mouse 'balloon' */
+    protected Font balloonFont = null;
+    /** metrics of the font for the mouse 'balloon' */
+    protected FontMetrics balloonFontMetrics = null;
+    /** Flag if drawing needs to just pasted from the buffer or redrawn */
+    protected boolean isDirty = true;
+    /** Scale the non-projected output if it goes off the page */
+    protected float scale=1.0F;
+    /** Last scale used when circles calculated (Flat mode only) */
+    protected float lastCircleScale = 1.0F;
+
+    /**
+     *  Rotate a Point2D around the 0.0,0.0 axis by phi angle (in radians)
+     *  @param Point2D around the 0.0,0.0 axis by
+     *  @param phi angle (in radians)
+     */
+    protected void rotatePt(Point2D pt,float phi)
+    {
+        float c = (float)Math.cos(phi), s = (float)Math.sin(phi),
+            r11 = c, r12 = s, r21 = -s, r22 = c;
+        float xNew = pt.x * r11 + pt.y * r21,
+            yNew = pt.x * r12 + pt.y * r22;
+        pt.x = xNew; pt.y = yNew;
+	
+    }
+    /**
+    * This is a public gateway for export of the keyWordsArray class
+    */
+    public KeyWordArray getKeyWords()
+    {
+        return keyWordArray;
+    }
+
+
+    /**
+    *  Fill the integer array circles[circleCount][4] with the correct numbers for
+    *  circleCount concentric circles about the 0.0,0.0 axis
+    */
+
+    protected void setCircles()
+    {
+        float  gap =r/4.0F, t = r/4.0F;
+        rim[0]=iX(-r); rim[1]=iY(r);
+        rim[2]= (int) (2.0F * r / pixelSize);
+        rim[3] = rim[2];
+        for (int count=0;count < circleCount; count++)
+        {
+            Point2D pc = new Point2D(-t,0.0F);
+            projection(pc);
+            circles[count][0]=iX(pc.x);
+            circles[count][2]=(int)((-pc.x) * 2.0F /pixelSize);
+            pc.x=0.0F; pc.y= t;
+            projection(pc);
+            circles[count][1]=iY(pc.y);
+            circles[count][3]=(int)(pc.y * 2.0F /pixelSize);
+            t += gap;
+        }
+        if (!isProjected) lastCircleScale=scale;
+    }
+
+    /** Create an array of linked lists that point only to the tuples affected by
+    *   each particular node, thus allowing faster update of the the locations
+    *   and easy display of only the tuples on that column of data, the membership
+    *   of these lists is subset of the larger list at 'matrix'
+    */
+    protected void setColumns()
+    {
+        int keyWordCount=keyWordArray.getNumWords();
+        columns = new Pt2DNode[keyWordCount];
+        Pt2DNode tails[] = new Pt2DNode[keyWordCount];
+        Pt2DNode k = null;
+        for (int i = 0; i< keyWordCount;i++) tails[i] = columns[i]=null;
+
+        if(keyWordCount==0) return;
+        Pt2DNode m = matrix;
+        SparseNode j=null;
+
+        // This may appear a bit obsure;
+
+        // columns[] is the start of an array of linked lists representing
+        // each of the keywords. tails[] is a parallel linked list representing the
+        // last added node to the columns[] lists;
+
+        // m loops through the rows and j iterates through the columns of each row
+        // when j finds a reference to a particular column[index] row m is at is added to
+        // the linked list for that column[index]
+
+        while (m != null)
+        {
+            j = m.tuple.row;
+            while (j != null)
+            {
+                k = new Pt2DNode(m.tuple);
+                int column = j.getColumn();
+                if(column > keyWordCount)
+                    System.out.println("col "+ column + " of " + keyWordCount);
+                else
+                {
+                    if (tails[column]!=null)
+                        tails[column].setNext(k);
+                    else columns[column] = k;
+                    tails[column]=k;
+                }
+                j=j.getNext();
+            }
+            m=m.getNext();
+        }
+    }
+    
+    /** Build a linked list of Points and tuples down connected to lists of nodes in the
+     * tuples across.
+     * @param dataItems the list
+     * @param refs[] the document titles
+     * @param docCount the number of documents
+     * @param wordCount the number of visible words
+     */
+    protected void setDatalists(SparseMatrix dataItems, String refs[],int docCount, int wordCount)
+    {
+        Pt2DNode m = null, n=null;
+        boolean state;
+        for (int co=0;co<docCount;co++)
+        {
+            SparseNode start = dataItems.getRow(co);
+            Point2D pn = new Point2D(0.0F,0.0F);
+            state = dataItemXY(pn,start);
+
+            n = new Pt2DNode(pn.x,pn.y,co,start,refs[co]);
+            if (state) projection(n.tuple.pnt);
+            n.tuple.state =state;
+            if (state)
+            {
+                n.tuple.iPnt.x=iX(n.tuple.pnt.x);
+                n.tuple.iPnt.y=iY(n.tuple.pnt.y);
+            }
+            if (m != null) m.setNext(n);
+            else matrix = n;
+            m=n;
+        }
+        setColumns();
+    }
+
+    /** Travers the linked lists setting new location values for the document points
+     * @param ma a linked list of document nodes
+     */
+    protected boolean setDocPoints(Pt2DNode ma)
+    {
+        while (ma != null)
+        {
+            ma.tuple.state = dataItemXY(ma.tuple.pnt,ma.tuple.row);
+            if (ma.tuple.state)
+            {
+                //c++;
+                projection(ma.tuple.pnt);
+                ma.tuple.iPnt.x=iX(ma.tuple.pnt.x);
+                ma.tuple.iPnt.y=iY(ma.tuple.pnt.y);
+                if (ma.tuple.iPnt.x<0 || ma.tuple.iPnt.x>dim.width ||
+                    ma.tuple.iPnt.y<0 || ma.tuple.iPnt.y>dim.height)
+                    return  true;
+            }
+            ma=ma.getNext();
+        }
+        return false;
+    }
+
+    /** Fill the integer array spokes[16][2] with the correct numbers for
+    *   16 radial points on the outermost circle, by defining a point on
+    *  the circle and rotating it by PI/8 radians 16 times
+    */
+    protected void setSpokes()
+    {
+        float phi=(float)Math.PI/(float)(spokeCount/2);
+        Point2D pt = new Point2D(0.0F,r);
+        for (int count=0;count < spokeCount;count++)
+        {
+            spokes[count][0]=iX(pt.x);
+            spokes[count][1]=iY(pt.y);
+            rotatePt(pt,phi);
+        }
+    }
+
+    /** set the key word nodes in an even circle round the rim */
+    protected void setKeyNodes()
+    {
+        int keyWordCount=keyWordArray.getNumEnabled();
+        float phi=(float)Math.PI/((float)keyWordCount/2.0F);
+        Point2D pt = new Point2D(0.0F,r);
+        for (int count=0;count < keyWordCount;count++)
+	    {
+		points[count] = new Point2D(pt.x,pt.y);
+		rotatePt(pt,phi);
+	    }
+    }
+    
+    /** find the x y coordinate of a tuple of data
+    *   @param theP point to be set
+    *   @param tuple linked list of data
+    *   @return true if tuple contains a reference to a visible keynode
+    */
+    protected boolean dataItemXY(Point2D theP, SparseNode tuple)
+    {
+        int index;
+        boolean result=false;
+        theP.x=0.0F; theP.y = 0.0F;
+        while (tuple != null)
+        {
+            if (keyWordArray.getState(tuple.getColumn()))
+            {
+                result = true;
+                index = mapping.mapWordsToNodes(tuple.getColumn());
+                theP.x += tuple.getValue()/r * points[index].x;
+                theP.y += tuple.getValue()/r * points[index].y;
+            }
+            tuple=tuple.getNext();
+        }
+        return result;
+    }
+
+    /** set the text balloon label at the correct location, to the correct size
+    *   and with the correct text
+    *   @param theString the text to display
+    *   @param x the x coordinate of the mouse
+    *   @param y the y coordinate of the mouse
+    */
+    void setBalloon(String theString,int x,int y)
+    {
+        Rectangle bounds1 = getBounds();
+	
+        x-=(bounds1.x)-30; y+=(bounds1.y)+20;
+	
+        radVizPanel.docLabel.setText(theString);
+        int width = balloonFontMetrics.stringWidth(theString)+2;
+        int height = balloonFontMetrics.getMaxAscent()+2;
+        radVizPanel.docLabel.setBounds(x,y,width,height);
+    }
+
+    /**  recalculate edge points and document positions in the light of
+    *   of changes to the keywords
+    */
+    public void recalc()
+    {
+        mapping = new MapKeyWords(keyWordArray);
+        tp =null;
+        scale=1.0F;
+        tpIndex = 0;
+        points= new Point2D[keyWordArray.getNumEnabled()];
+        setKeyNodes();
+        while (setDocPoints(matrix) && !isProjected) scale*=0.9F ;
+        repaint();
+    }
+
+    /**
+    * constructor sets everything up gets creates an instance of DataLink and
+    * gets the data matrix and the keywords calls code to initialise display
+    * @param DisplayFrame the central object of the display
+    * @param width of canvas
+    * @param height of canvas
+    * @throws IOException
+    */
+    public CvManyPoints(Vishnu df) throws IOException
+    {
+        setVisible(false);
+       	this.displayFrame = df;
+        this.radVizPanel = displayFrame.radVizPanel;
+        this.dataManager = displayFrame.dataManager;
+	
+       	documentCount=0;
+       	int keyWordCount=0;
+        keyWordArray = new KeyWordArray();
+	
+       	initgr();
+       	setCircles();
+       	setSpokes();
+       	/**
+       	* add an anonymous class to listen for size changed events
+       	*/
+	
+        addComponentListener(new ComponentAdapter(){
+		public void componentResized(ComponentEvent evt)
+		{
+		    initgr();
+		    setCircles();
+		    setSpokes();
+		    while (setDocPoints(matrix) && !isProjected) scale*=0.9F ;
+		    repaint();
+		}
+	    });
+	
+        // From Java in a Nutshell
+        // listen for mouse button pressed event and if on a node point
+        // record the current angle and set the value of tp
+        /**
+	 * add an anonomous class to listen for mouse pressed events
+	 */
+        addMouseListener(new MouseAdapter()
+	    {
+		public void mousePressed(MouseEvent evt)
+		{
+		    int intX = evt.getX();
+		    int intY = evt.getY();
+		    float x = fx(intX), y = fy(intY);
+		    tp = findPoint(x,y);
+		    if (tp == null){
+			selectDoc = findDoc(intX, intY);
+			if (selectDoc != null){
+			    StringBuffer buf = new StringBuffer("Doc: ");
+			    buf.append(selectDoc.tuple.ref);
+			    buf.append("  ");
+			    if (buf.length() > 30){
+				buf.setLength(30);
+				buf.append("... ");
+			    }
+			    String theString = new String(buf);
+			    //theOuterFrame.setDocSelectMenu(theString,selectDoc.tuple.index);
+			}   
+			//else theOuterFrame.setDocSelectMenu("No Document Selected      ",-1);
+			repaint();
+			return;
+		    }
+		    lastAngle=(float)Math.atan2(x,y);
+		    repaint();
+		}
+		
+		public void mouseClicked(MouseEvent event)
+		{
+		    int intX = event.getX();
+		    int intY = event.getY();
+		    
+		    selectDoc = findDoc(intX, intY);
+		    if (selectDoc != null){
+			if(event.getClickCount()>1){
+			    StringBuffer buf = new StringBuffer("Doc: ");
+			    buf.append(selectDoc.tuple.ref);
+			    buf.append("  ");
+			    if (buf.length() > 30){
+				buf.setLength(30);
+				buf.append("... ");
+			    }
+			    String theString = new String(buf);
+			    //theOuterFrame.setDocSelectMenu(theString,selectDoc.tuple.index);
+			    dataManager.fetch(selectDoc.tuple.index);
+			}
+		    }
+		}
+		
+		public void mouseReleased(MouseEvent event)
+		{
+		    Cursor releaseCursor = new Cursor(Cursor.DEFAULT_CURSOR);
+		    displayFrame.radVizPanel.setCursor(releaseCursor);
+		}
+	    });
+	
+        // listen for mouse button dragged event and if tp is set
+        // work out a new position for tp and the data points
+        /**
+	 * add an anonomous class to listen for mouse motion events
+	 */
+        addMouseMotionListener(new MouseMotionAdapter(){
+		public void mouseDragged(MouseEvent evt)
+		{
+		    Cursor dragCursor = new Cursor(Cursor.HAND_CURSOR);
+		    displayFrame.radVizPanel.setCursor(dragCursor);
+		    
+		    float x = fx(evt.getX()), y = fy(evt.getY());
+		    if (tp!=null){
+			float thisAngle = (float)Math.atan2(x,y);
+			if(isLocked){
+			    float phi = lastAngle-thisAngle;
+			    rotatePt(tp, phi);
+			}
+			else{
+			    tp.x = x;
+			    tp.y = y;
+			}
+			if (columns!= null){
+			    int index = mapping.mapNodesToWords(tpIndex);
+			    if (columns[index]!= null){
+				if(setDocPoints( columns[index] ) && !isProjected){
+				    do{
+					scale*=0.9;
+				    } while(setDocPoints( matrix ));
+				}
+				repaint();
+			    }
+			}
+			lastAngle=thisAngle;
+		    }
+		}
+		
+		
+		
+		/** listen for mouse movement and try and find a document
+		 * report its presence with a balloon label or ensure
+		 * the balloon label is hidden
+		 * @param evt the mouse event
+		 */
+		public void mouseMoved(MouseEvent evt)
+		{
+		    System.out.println("Moving");
+		    /* int intX = evt.getX();
+		    int intY = evt.getY();
+		    Pt2DNode theNode = findDoc(intX, intY);
+		    
+		    if (theNode != null){
+			if (atDoc) radVizPanel.docLabel.setVisible(false);
+			String theString = new String("Doc: " + theNode.tuple.ref);
+			setBalloon(theNode.tuple.ref,intX,intY);
+			radVizPanel.docLabel.setVisible(true);
+			atDoc = true;
+		    }
+		    else{
+			if (atDoc){
+			    radVizPanel.docLabel.setVisible(false);
+			}
+			atDoc = false;
+			}*/
+		    return;
+		}
+	    });
+    }
+
+    /* the Data loading happens in here now */
+    
+    public void update()
+    {
+        int maxWordsCount;
+        this.radVizPanel = displayFrame.radVizPanel;
+        this.dataManager = displayFrame.dataManager;
+
+        //if(dataManager.getDocCount()==0)return;
+        selectDoc = null;
+        tp =null;
+       	docs = dataManager.getDocs();
+       	documentCount = docs.length;
+        String [] words = dataManager.getWords();
+        maxWordsCount = words.length;
+        //maxWordsCount= in.setKeywordCount(maxWordsCount);
+        int keyWordCount = Math.min(maxWordsCount,12);
+       	keyWordArray = new KeyWordArray(words,words.length);
+        maxWordsCount = keyWordArray.getNumWords();
+
+
+       	mapping = new MapKeyWords(keyWordArray);
+
+        String titles[] = dataManager.getDescriptions();
+        if(titles==null) System.out.println("no descriptions read in");
+        points= new Point2D[keyWordCount];
+        balloonFont = new Font("Dialog", Font.PLAIN, 16);
+        balloonFontMetrics = getFontMetrics(balloonFont);
+        theDataMatrix = dataManager.getMatrix();
+        fullMatrix = theDataMatrix.getExpanded();
+        setKeyNodes();
+        setDatalists(theDataMatrix,titles,documentCount,maxWordsCount);
+        recalc();
+    }
+    /** Cluster the words round the edge
+    * @param type the type of clustering
+    */
+    public void cluster()
+    {
+        if(documentCount ==0) return;
+        int keyWordCount=keyWordArray.getNumEnabled();
+        int keylist[] = new int[keyWordCount];
+
+        for (int c=0;c<keylist.length;c++)
+        {
+            keylist[c]=mapping.mapNodesToWords(c);
+        }
+        if(fullMatrix==null) System.out.println("full matrix is null");
+        else
+        {
+            if(fullMatrix[0]==null) System.out.println("full matrix[0] is null");
+        }
+
+        keylist = HitCluster.maximunDistance (fullMatrix, keylist, documentCount);
+
+        mapping.reMapKeyWords(keylist,keyWordArray);
+
+        tp =null;
+        tpIndex = 0;
+        points= new Point2D[keyWordArray.getNumEnabled()];
+        setKeyNodes();
+        while (setDocPoints(matrix) && !isProjected) scale *=0.9;
+        repaint();
+    }
+    /**
+    * Overides setSize so that the drawing is correctly scaled on a setSize call
+    * @param new dimensions
+    */
+    public void setSize(Dimension d)
+    {
+        super.setSize(d);
+        initgr();
+        setCircles();
+        setSpokes();
+        while (setDocPoints(matrix) && !isProjected) scale*=0.9F ;
+    }
+    /**
+    * Overides setSize so that the drawing is correctly scaled on a setSize call
+    * @param new width
+    * @param new height
+    */
+    public void setSize(int x, int y)
+    {
+        Dimension d = new Dimension(x,y);
+        setSize(d);
+    }
+
+
+
+    /**
+    * Tidy up the image before canvas is destroyed a bit like a destructor in C
+    */
+    public void dispose()
+    {
+        gImage.dispose();
+    }
+
+
+    /** Set the shape of the edge word nodes
+    * @param state selects the shape
+    */
+    void setNodeShape(int state)
+    {
+        nodesShape = state;
+        repaint();
+    }
+
+    /** Set the shape of the centre document nodes
+    *  @param state selects the shape
+    */
+    void setDocShape(int state)
+    {
+        docsShape = state;
+        repaint();
+    }
+
+    /** switches the projection on and off
+    * @param state is the switch
+    */
+    public void projection(boolean state)
+    {
+        // to do: code goes here.
+        isProjected = state;
+        circleCount = state?40:4;
+        setCircles();
+        while (setDocPoints(matrix) && !isProjected) scale*=0.9;
+        repaint();
+    }
+
+    /**
+    * Display or not the grid
+    * @param switches on and off
+    */
+
+    public void grid(boolean state)
+    {
+        displayGrid=state;
+        repaint();
+    }
+
+    /**
+    * Display or not the rim
+    * @param switches on and off
+    */
+    public void rim(boolean state)
+    {
+        displayRim=state;
+        repaint();
+    }
+
+    /**
+    * Lock or not the edge nodes so they can only be moved in a circle
+    * @param switches on and off
+    */
+    public void lock(boolean state)
+    {
+        isLocked = state;
+    }
+
+
+    /**
+    * Flattens the projection slightly
+    *
+    */
+    public void flatter()
+    {
+        if(!isProjected)
+            scale/=0.9;
+        else
+        {
+            factor *=0.9;
+            setCircles();
+        }
+        while (setDocPoints(matrix)&& !isProjected) scale*=0.9;
+        repaint();
+    }
+
+    /**
+    * Heightens the projection slightly
+    *
+    */
+    public void higher()
+    {
+        if(!isProjected)
+            scale*=0.9;
+        else
+        {
+            factor /=0.9;
+            setCircles();
+        }
+        while (setDocPoints(matrix) && !isProjected) scale*=0.9;
+        repaint();
+    }
+
+
+
+
+    /**
+    * Translate to integer raster coordinate X from
+    * floating point coord X based around a notional centre
+    * @param x floating point x coordinate
+    * @return integer X coordinate
+    */
+    int iX(float x){return Math.round(centerX + x/pixelSize);}
+    /**
+    * Translate to integer raster coordinate Y from
+    * floating point coord Y based around a notional centre
+    * @param y floating point y coordinate
+    * @return integer Y coordinate
+    */
+    int iY(float y){return Math.round(centerY - y/pixelSize);}
+    /**
+    * Translate from integer raster coordinate X to
+    * floating point coord X based around a notional centre
+    * @param x integer x coordinate
+    * @return floating point X coordinate
+    */
+    float fx(int X){return (X - centerX) * pixelSize;}
+    /**
+    * Translate from integer raster coordinate Y to
+    * floating point coord Y based around a notional centre
+    * @param y integer y coordinate
+    * @return floating point Y coordinate
+    */
+    float fy(int Y){return (centerY - Y) * pixelSize;}
+
+    /**
+    * find if an edge keyword node is at the x y location
+    * @param x fp coordinate
+    * @param y fp coordinate
+    * @return fp Point at location or null
+    */
+    public Point2D findPoint(float x, float y)
+    {
+        int keyWordCount=keyWordArray.getNumEnabled();
+        for (int i=0;i<keyWordCount;i++)
+        {
+            int intX = iX(x), intY = iY(y),
+                lX = iX(points[i].x)-3, hX = lX+6,
+                lY = iY(points[i].y)-3, hY = lY+6;
+
+            if (intX > lX && intX < hX && intY > lY && intY < hY)
+            {
+
+                tpIndex = i;
+                return points[i];
+            }
+        }
+        return null;
+    }
+
+    /**
+    * find if a Doc node is at the x y location
+    * @param x int coordinate
+    * @param y int coordinate
+    * @return fp Point at location or null
+    */
+    public Pt2DNode findDoc(int intX, int intY)
+    {
+        Pt2DNode thePnt=matrix;
+        while (thePnt!= null)
+        {
+            if (thePnt.tuple.state)
+            {
+                int   lX = thePnt.tuple.iPnt.x-3, hX = lX+6,
+                    lY = thePnt.tuple.iPnt.y-3, hY = lY+6;
+                if (intX > lX && intX < hX && intY > lY && intY < hY) return thePnt;
+            }
+            thePnt = thePnt.getNext();
+        }
+        return null;
+    }
+    /** project the point radially from the centre using the arctan function
+    *  if isProjected is true or scale them it false
+    *  @param point to be projected
+    *  @param ingore scaling flag
+    */
+    protected void projection(Point2D p)
+    {
+        if (!isProjected)
+        {
+            if(scale!=1.0)
+            {
+                p.x*=scale;
+                p.y*=scale;
+            }
+            return;
+        }
+        float newRad;
+        float rad = (float)Math.sqrt(p.x*p.x+p.y*p.y);
+        float sine = p.y/rad;
+        float cosine = p.x/rad;
+        newRad =(float)Math.atan(rad/(factor*Math.PI))*magic;
+        p.x = newRad * cosine;
+        p.y = newRad * sine;
+    }
+
+    /** set up graphic settings such as centre and pixelsize
+    */
+    protected void initgr()
+    {
+        dim = getSize();
+        int maxX = dim.width - 1, maxY = dim.height - 1;
+        pixelSize = Math.max(rWidth/maxX, rHeight/maxY);
+        centerX = maxX/2; centerY = maxY/2;
+    }
+
+
+    /////////Athina has been here!!!!!///////////////////////////////////////
+
+    /** draw a for document node shape at raster x y
+    *   @param x location
+    *   @param y location
+    *   @param g Graphics object
+    */
+    protected void drawItemPoint(int x, int y, Graphics g)
+    {
+
+        if (docsShape == SQUARESHAPE)
+            oval(g,x,y);
+        else if (docsShape == ROUNDSHAPE)
+            rect(g,x,y);
+        else
+            diamond(g,x,y);
+
+    }
+
+    /**
+    *   draw a for keyword node shape at raster x y
+    *   @param x location
+    *   @param y location
+    *   @param g Graphics object
+    */
+    void drawNodes(int x, int y, Graphics g)
+    {
+        if (nodesShape == 0)
+            oval(g,x,y);
+        else if (nodesShape == 1)
+            rect(g,x,y);
+        else
+            diamond(g,x,y);
+
+    }
+
+
+    /**
+    *  draw the key name near the edgenode
+    *  @param g Graphics class
+    *  @param count index of the name to draw
+    *  @param select if the node is selected.
+    */
+    void drawKeyName(Graphics g, int count, boolean select)
+    {
+        FontMetrics fontMetrics = g.getFontMetrics();
+        Font font = new Font("arial", Font.PLAIN, 16);
+        String word = keyWordArray.getWord(mapping.mapNodesToWords(count));
+        int posX= iX(points[count].x)-2, posY= iY(points[count].y)-2;
+        int width = fontMetrics.stringWidth(word);
+        if (points[count].x < 0.0)
+            posX -= width - (int) (points[count].x/2.00);
+        else
+            posX += (int) (points[count].x/2.00);
+
+        if (points[count].y < 0.0)
+            posY += fontMetrics.getMaxAscent()- (int)(points[count].y/2.00);
+        else
+            posY -= (int) (points[count].y/2.00);
+        g.setFont(font);
+        g.drawString(word,posX,posY);
+        if (select){ g.drawRect(posX-2,posY-15,width+4,20);}
+    }
+
+    /**
+    * draw a box at the edge nodes
+    * @param g Graphics object
+    */
+    protected void drawEdgeNodes(Graphics g)
+    {
+        int keyWordCount=keyWordArray.getNumEnabled();
+
+        for (int count=0;count < keyWordCount; count++)
+        {
+            drawNodes(iX(points[count].x), iY(points[count].y), g);
+            drawKeyName(g,count,false);
+
+        }
+    }
+    /**
+    * Draw the documents that have the selected key word in them
+    * @param g Graphics object
+    */
+    protected void drawSelect(Graphics g)
+    {
+        if (tp!=null)
+        {
+            int x = iX(points[tpIndex].x), y = iY(points[tpIndex].y);
+            g.setColor(defSelect);
+            drawNodes(x, y, g);
+
+            Pt2DNode m = columns[mapping.mapNodesToWords(tpIndex)];
+            while (m != null)
+            {
+                x=m.tuple.iPnt.x;
+                y=m.tuple.iPnt.y;
+
+                drawItemPoint(x, y, g);
+
+                m=m.getNext();
+            }
+            g.setColor(defDocsColor);
+
+        }
+    }
+    /**
+    * draw the key word nodes that feature in the selected document
+    */
+    protected void drawDocSelect(Graphics g)
+    {
+        if (selectDoc==null) return;
+        g.setColor(defHighlight);
+        int x =selectDoc.tuple.iPnt.x, y =selectDoc.tuple.iPnt.y;
+        drawItemPoint(x, y, g);
+
+        SparseNode fn = selectDoc.tuple.row;
+        while (fn != null)
+        {
+            if (keyWordArray.getState(fn.getColumn()))
+            {
+                int index = mapping.mapWordsToNodes(fn.getColumn());
+                drawNodes(iX(points[index].x), iY(points[index].y), g);
+                drawKeyName(g,index,true);
+            }
+            fn=fn.getNext();
+
+        }
+        g.setColor(defNodesColor);
+
+    }
+
+    /**
+    * draw all the document crosses
+    * @param g Graphics object
+    */
+
+    protected void drawDocXs(Graphics g)
+    {
+        Pt2DNode m = matrix;
+        while (m != null)
+        {
+            if (m.tuple.state) drawItemPoint(m.tuple.iPnt.x, m.tuple.iPnt.y, g);
+            m=m.getNext();
+        }
+    }
+
+    /**
+    * draw the target
+    * @param g Graphics object
+    */
+
+    protected void drawTarget(Graphics g)
+    {
+        if (!isProjected && lastCircleScale!=scale)
+            setCircles();
+
+        if (displayRim)
+        {
+            g.setColor(Color.darkGray);
+            g.drawOval(rim[0],rim[1],rim[2],rim[3]);
+        }
+        if (displayGrid)
+        {
+            g.setColor(Color.lightGray);
+            for (int count=0;count < circleCount; count++)
+                g.drawOval(circles[count][0],circles[count][1],circles[count][2],circles[count][3]);
+            for (int count=0;count < spokeCount; count++)
+                g.drawLine(centerX, centerY,spokes[count][0],spokes[count][1]);
+        }
+    }
+
+    /**
+    * Repaint the screen overides canvas.repaint()
+    */
+
+    public void repaint()
+    {
+        isDirty=true;
+        super.repaint();
+    }
+
+    /**
+    * Repaint the screen overides canvas.update()
+    * to stop flickering
+    * @param g Graphics object
+    */
+
+    public void update(Graphics g)
+    {
+        paint(g);
+    }
+
+    /**
+    * draw a filled square with a centre at x y
+    * @param g Graphics object
+    * @param x location
+    * @param y location
+    */
+
+    public void rect(Graphics g, int x, int y)
+    {
+        g.drawRect(x-2,y-2,4,4);
+        g.fillRect(x-2,y-2,4,4);
+        repaint();
+    }
+
+    /**
+    * draw a filled circle with a centre at x y
+    * @param g Graphics object
+    * @param x location
+    * @param y location
+    */
+    public void oval(Graphics g, int x, int y)
+    {
+        g.drawOval(x-2,y-2,4,4);
+        g.fillOval(x-2,y-2,4,4);
+        repaint();
+    }
+
+    /**
+    * draw a filled diamond with a centre at x y
+    * @param g Graphics object
+    * @param x location
+    * @param y location
+    */
+    public void diamond(Graphics g, int x, int y)
+    {
+        int numpoints = 4;
+        int[] xpoints = new int[numpoints+1];
+        int[] ypoints = new int[numpoints+1];
+        for(int i=0; i < numpoints; i++) {
+            double angle = 2*Math.PI * i / numpoints;
+            xpoints[i] = (int)(x + 3*Math.cos(angle));
+            ypoints[i] = (int)(y - 3*Math.sin(angle));
+        }
+
+        g.fillPolygon(xpoints, ypoints, numpoints);
+        xpoints[numpoints] = xpoints[0];  ypoints[numpoints] = ypoints[0];
+        g.drawPolygon(xpoints, ypoints, numpoints+1);
+        repaint();
+    }
+
+    /**
+    * paint the whole thing
+    * @param g Graphics object
+    */
+
+    public void paint(Graphics g)
+    {
+	System.out.println("Repaint CvManyPoints");
+        if(!isVisible()) return;
+        if (isDirty==true)
+        {
+            if (imgW != dim.width || imgH != dim.height)
+            {
+                if( gImage != null ) gImage.dispose();
+                imgW = dim.width; imgH = dim.height;
+                image = createImage(imgW, imgH);
+                gImage = image.getGraphics();
+            }
+            gImage.setColor(Color.white);
+            gImage.fillRect(0, 0, imgW, imgH);
+
+            drawTarget(gImage);
+            gImage.setColor(defNodesColor);
+            drawEdgeNodes(gImage);
+            gImage.setColor(defDocsColor);
+            drawDocXs(gImage);
+
+            if (tp!=null)
+                drawSelect(gImage);
+            if (selectDoc!=null)
+                drawDocSelect(gImage);
+
+            //gImage.drawRect(1,1,dim.width-1,dim.height-1);
+        }
+        g.drawImage(image, 0, 0, null);
+
+        isDirty=false;
+    }
+}
+
+
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/HitCluster.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/HitCluster.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/HitCluster.java	(revision 21054)
@@ -0,0 +1,136 @@
+package vishnu.testvis.visual;
+
+import java.util.*;
+import java.net.*;
+import java.io.*;
+import java.lang.*;
+import java.util.zip.*;
+
+class HitCluster
+{
+    static final int NOVALUE = 99999999;
+    static int MAX;
+    static final int EMPTYARRAY[] = {};
+    static int matrix[][];
+    // static int data[][];
+    static float data [][];
+    static int clusters[][];
+    static int keywords[];
+    static int maxDocuments;
+
+    static void buildMatrix ()
+    {
+	    int count;
+	    int keyword1, keyword2;
+    	matrix = new int[MAX][MAX];
+        for (int word1=0;word1<MAX;word1++)
+        {
+	        keyword1 = keywords[word1];
+	        for (int word2=word1+1; word2<MAX;word2++)
+            {
+ 		        keyword2 = keywords[word2];
+                count = 0;
+                for (int doc=0;doc<maxDocuments;doc++)
+                {
+		            if ( (data[doc][keyword1] > 0) && (data[doc][keyword2] > 0))
+			            count++;
+	            }
+                matrix[word1][word2]=matrix[word2][word1]=count;
+            }
+        }
+	    for (int row = 0;row <MAX; row++)
+	    {
+	        matrix[row][row] = NOVALUE;
+	        for (int col = 0; col <row; col++)
+		    matrix[row][col] = matrix[col][row];
+	    }
+    }
+
+    static void findLargest ()
+    {
+	    int group = matrix[0].length-1;
+	    int largest = -1;
+	    int smallRow = NOVALUE;
+	    int smallCol = NOVALUE;
+	    int row,col;
+	    for (row =0; row<MAX; row++)
+	        for (col=row+1; col<MAX; col++)
+	            if (largest < matrix[row][col])
+	            {
+		            largest = matrix[row][col];
+		            smallRow = row;
+		            smallCol = col;
+ 	            }
+	    for (col=0; col<MAX; col++)
+	    {
+	        if (matrix[smallRow][col] > matrix[smallCol][col])
+		        matrix[smallRow][col] = matrix[smallCol][col];
+            matrix[smallCol][col] = -1;
+	    }
+	    clusters[smallRow] = addClusters(smallRow, smallCol);
+	    clusters[smallCol] = EMPTYARRAY;
+	    for (row =0; row<MAX; row++)
+        {
+	        matrix[row][smallCol] = -1;
+            matrix[row][smallRow] = matrix[smallRow][row];
+	    }
+    }
+
+    static int[] addClusters(int c1, int c2)
+    {
+	    int i;
+	    int temp[] = new int[clusters[c1].length+clusters[c2].length];
+	    if (keywords[clusters[c1][0]] < keywords[clusters[c2][0]])
+	    {
+	        for (i = 0; i<clusters[c1].length;i++)
+	    	    temp[i] = clusters[c1][i];
+	        for (i = 0; i<clusters[c2].length;i++)
+	    	    temp[i+clusters[c1].length] = clusters[c2][i];
+	    }
+	    else
+	    {
+	        for (i = 0; i<clusters[c2].length;i++)
+	    	    temp[i] = clusters[c2][i];
+	        for (i = 0; i<clusters[c1].length;i++)
+	    	    temp[i+clusters[c2].length] = clusters[c1][i];
+	    }
+	    return temp;
+    }
+
+    static void preClusters()
+    {
+	    clusters = new int[MAX][1];
+	    for (int row =0; row <MAX; row++)
+	    {
+	        clusters[row][0] = row;
+	    }
+    }
+
+    static int[] maximunDistance (float[][] newData, int[] newKeywords, int documentCount)
+    {
+	    data = newData;
+	    keywords = newKeywords;
+	    maxDocuments = documentCount;
+	    if (documentCount > data.length)
+	    {
+	        System.out.println ("WARNING matrix sent is shorter the document Count!");
+	        System.out.println ("I will use the shorter length only.");
+	        documentCount = data.length;
+        }
+	    MAX = keywords.length;
+	    buildMatrix();
+	    preClusters();
+	    for (int i=1;i<MAX;i++)
+	    {
+ 	        findLargest();
+        }
+	    int temp[] = new int[MAX];
+	    for (int i=0;i<MAX;i++)
+	    {
+	        temp[i] = keywords[clusters[0][i]];
+//System.out.println (i+" = "+keywords[i]+" = "+clusters[0][i]);
+    	}
+	    return temp;
+    }
+}
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Hlink.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Hlink.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Hlink.java	(revision 21054)
@@ -0,0 +1,118 @@
+
+/**
+ * Title:        VeniVidiVinci<p>
+ * Description:  An applet that displays visualisations of data sets and allows
+ * these visualisations to be manipulated and selections made<p>
+ * Copyright:    Copyright (c) Matthew Carey<p>
+ * Company:      Imperial College<p>
+ * @author Matthew Carey
+ * @version 1.0
+ */
+package vishnu.testvis.visual;
+
+import javax.swing.JEditorPane;
+import javax.swing.*;
+import javax.swing.text.html.*;
+import javax.swing.event.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.Vector;
+
+public class Hlink extends JEditorPane
+{
+    public JScrollPane linkPane;
+    public Vishnu currentMain;
+    public int index = 0;
+    public int numDisplayedDocs = 15;
+    private int width, length;
+
+    public Hlink(int width, int length , Vishnu currentMain)
+    {
+        this.currentMain=currentMain;
+        this.width=width;
+        this.length=length;
+        try
+        {
+            jbInit();
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+    public Hlink()
+    {
+        this.currentMain=null;
+        this.width=500;
+        this.length=300;
+        try
+        {
+            jbInit();
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public void jbInit() throws Exception
+    {
+        linkPane = new JScrollPane(this);
+        linkPane.setBorder(BorderFactory.createLoweredBevelBorder());
+        linkPane.setPreferredSize(new Dimension(width,length));
+        linkPane.setMinimumSize(new Dimension(width,length));
+        linkPane.setBorder(new javax.swing.border.EtchedBorder());
+        linkPane.getVerticalScrollBar().setUnitIncrement(10);
+
+        setLayout(new GridLayout(17,1));
+
+
+        setEditorKitForContentType("text/html",new HTMLEditorKit());
+        setEditorKit(new HTMLEditorKit());
+        setEditable(false);
+
+        addHyperlinkListener(new MyLinkListener());
+
+        setVisible(true);
+
+    }
+
+    public class MyLinkListener implements HyperlinkListener
+    {
+        public void hyperlinkUpdate(HyperlinkEvent he){
+            System.out.println("Inside hyperlinkevent ... ");
+            HyperlinkEvent.EventType type = he.getEventType();
+            System.out.println("Inside hyperlinkevent ... ");
+            if (type==HyperlinkEvent.EventType.ACTIVATED)
+                System.out.println("Mouse ACTIVATED -> " + he.getURL().toString());
+        }
+    }
+
+    public void update()
+    {
+       int [] docIDs = currentMain.dataManager.getDocs();
+        Vector subSet = new Vector();
+
+        for (int i = index ; i < index + numDisplayedDocs  ; i++)
+        {
+
+            // Check that the element exists
+            if (docIDs.length <= i) break;
+
+            subSet.addElement(new Integer(docIDs[i]));
+        }
+
+        String htmlDescription = currentMain.dataManager.getDescriptionStr(subSet);
+
+        setText(htmlDescription);
+
+    }
+
+    public void clear()
+    {
+        setText("");
+    }
+
+
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Ipoint2D.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Ipoint2D.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Ipoint2D.java	(revision 21054)
@@ -0,0 +1,17 @@
+package vishnu.testvis.visual;
+/** Integer point class 
+* (minimal)
+* @author Matthew Carey */
+
+public class Ipoint2D
+{
+   /** x value */
+   public int x;
+   /** y value */
+   public int y;
+   /** constructor 
+   * @param horizontal value
+   * @param vertical value
+   */
+   public Ipoint2D(int x, int y){this.x = x; this.y = y;}
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/KeyWordArray.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/KeyWordArray.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/KeyWordArray.java	(revision 21054)
@@ -0,0 +1,106 @@
+/** Class to store the keywords and their visible states
+* @author Matthew Carey
+*/
+package vishnu.testvis.visual;
+
+public class KeyWordArray
+{
+    /** constant default number enabled */
+    public static final int DEFAULT_ENABLED = 12;
+    /** the word array */
+    private String words[];
+    /** the enabled array */
+    private boolean enabled[];
+    /** the count */
+    private int count;
+    /**
+    * constructor
+    * @param word array
+    * @param count size
+    * @param number enabled
+    */
+    public KeyWordArray(String words[],int count,int numEnabled)
+    {
+        this.words = words;
+        this.count = count;
+        enabled = new boolean[count];
+        int theNumEnabled = count<numEnabled?count:numEnabled;
+        for (int c = 0; c < numEnabled && c < count; c++)
+            enabled[c]=true;
+        if (count > theNumEnabled)
+            for (int c = theNumEnabled; c < count; c++)
+                enabled[c]=false;
+    }
+    /**
+    * constructor
+    * @param word array
+    * @param count size
+    */
+    public KeyWordArray(String words[],int count)
+    {
+        this(words,count,DEFAULT_ENABLED);
+    }
+
+    public KeyWordArray()
+    {
+        this.count = 0;
+        this.words = null;
+        this.enabled = null;
+    }
+
+    /**
+    * Find the size of the array
+    * @return size of the array
+    */
+    public int getNumWords()
+    {
+        return count;
+    }
+
+    /**
+    * Find the word at index
+    * @param index of word
+    * @return word at index
+    */
+    public String getWord(int index)
+    {
+        if (index >=0 && index < count)
+           return this.words[index];
+        return "Error out of index";
+    }
+
+    /**
+    * Get state of word at index
+    * @param index of word
+    * @return state of word
+    */
+    public boolean getState(int index)
+    {
+        if (index >=0 && index < count)
+           return this.enabled[index];
+        return false;
+    }
+
+    /**
+    * Set state of word at index
+    * @param index of word
+    * @param new state of word
+    */
+    public void setState(int index, boolean state)
+    {
+        if (index >=0 && index < count)
+           this.enabled[index]=state;
+    }
+
+    /**
+    * Get number of enabled words
+    * @return number of enabled words
+    */
+    public int getNumEnabled()
+    {
+        int result = 0;
+        for (int c = 0; c < count; c++)
+            if (enabled[c]) result++;
+        return result;
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/KeywordPanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/KeywordPanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/KeywordPanel.java	(revision 21054)
@@ -0,0 +1,188 @@
+package vishnu.testvis.visual;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import java.awt.List;
+import java.applet.*;
+import javax.swing.*;
+
+/** A panel that is added to the frame containing an on screen list of keywords to select
+*   and deselect
+*   @author Athina Economou
+*/
+public class KeywordPanel extends JPanel
+{
+    /** the base frame */
+    protected RadVizPanel f;
+    protected KeyWordArray keyWordArray;
+    protected int wordCount;
+    protected Vector  selArrayIndex;
+    protected Vector  listArrayIndex;
+    protected List available;
+    protected List selected;
+    protected int largeFont = 16;
+    protected int smallFont = 14;
+
+
+    /** Constructor
+    * @param drawing canvas
+    * @param base frame
+    */
+    public KeywordPanel(final CvManyPoints canvas1, RadVizPanel f)
+    {
+        this.f = f;
+
+        keyWordArray = canvas1.getKeyWords();
+        wordCount = keyWordArray.getNumWords();
+        selArrayIndex = new Vector();
+        listArrayIndex = new Vector();
+        available = new List(12, false);
+        selected = new List(12, false);
+        // mc
+        SymAction lSymAction = new SymAction();
+
+//        Panel base = new Panel();
+        setLayout(new BorderLayout(10,0));
+        //setSize(50,400);
+        JPanel labels = new JPanel();
+        Font font1 = new Font("Monospaced", Font.PLAIN, largeFont);
+        labels.setFont(font1);
+        labels.setForeground(Color.black);
+        labels.add(new Label("Available           Displayed"));
+
+        JPanel rightPanel = new JPanel();
+        Font font = new Font("Dialog", Font.PLAIN, smallFont);
+        rightPanel.setFont(font);
+        JPanel leftPanel = new JPanel();
+        leftPanel.setFont(font);
+
+        for (int c=0; c< wordCount;c++)
+            if (keyWordArray.getState(c))
+            {
+                selected.add(keyWordArray.getWord(c));
+                selArrayIndex.addElement(new Integer(c));
+            }
+            else
+            {
+                available.add(keyWordArray.getWord(c));
+                listArrayIndex.addElement(new Integer(c));
+            }
+        available.addActionListener(lSymAction);
+        available.addItemListener(new ItemListener()
+            {
+                public void itemStateChanged(ItemEvent event)
+                {
+                    if(event==null) return;
+                    try
+                    {
+                        int index =((List)event.getItemSelectable()).getSelectedIndex();
+                        if(index >= 0)
+                        {
+                            String keywords = ((List)event.getItemSelectable()).getSelectedItem();
+                            Integer result = (Integer)listArrayIndex.elementAt(index);
+                            selected.add(keywords);
+                            selArrayIndex.addElement(result);
+
+                            available.remove(keywords);
+                            listArrayIndex.removeElementAt(index);
+
+                            for (int c=0; c<keyWordArray.getNumWords(); c++)
+                                keyWordArray.setState(c,false);
+
+                            for (int c=0;c<(int)selArrayIndex.size();c++)
+                            {
+                                Integer result1 = (Integer)selArrayIndex.elementAt(c);
+                                int index1 = result1.intValue();
+                             keyWordArray.setState(index1,true);
+                            } // for
+                            canvas1.recalc();
+                        } // if
+                    } //try
+                    catch (ArrayIndexOutOfBoundsException e)
+                    {
+                    } // catch
+                } //itemstate changed
+            }); // ItemListener
+            //mc
+            selected.addActionListener(lSymAction);
+            selected.addItemListener(new ItemListener()
+            {
+                public void itemStateChanged(ItemEvent event)
+                {
+                    //System.out.println("calling getSeleced index");
+                    try
+                    {
+                        int index =((List)event.getItemSelectable()).getSelectedIndex();
+                    //System.out.println("Index");
+                    //System.out.println(index);
+                        if(index >= 0)
+                        {
+                            String keywords = ((List)event.getItemSelectable()).getSelectedItem();
+                            Integer result = (Integer)selArrayIndex.elementAt(index);
+
+                            available.add(keywords);
+                            listArrayIndex.addElement(result);
+
+                            selected.remove(keywords);
+                            selArrayIndex.removeElementAt(index);
+
+                            for (int c=0; c<keyWordArray.getNumWords(); c++)
+                                keyWordArray.setState(c,false);
+
+                            for (int c=0;c<(int)selArrayIndex.size();c++)
+                            {
+                                Integer result1 = (Integer)selArrayIndex.elementAt(c);
+                                int index1 = result1.intValue();
+                                keyWordArray.setState(index1,true);
+                            } // for
+                            canvas1.recalc();
+                        } // if
+                    } // try
+                    catch (ArrayIndexOutOfBoundsException e)
+                    {
+                    }
+                }
+            });
+
+            leftPanel.add(available);
+            rightPanel.add(selected);
+            add(labels, "North");
+            add(rightPanel, "East");
+            add(leftPanel, "West");
+            //base.add(text, "South");
+            setVisible(true);
+
+    }
+    public void update()
+    {
+        keyWordArray = f.canvas1.getKeyWords();
+        wordCount = keyWordArray.getNumWords();
+        selected.removeAll();
+        available.removeAll();
+        selArrayIndex.removeAllElements();
+        listArrayIndex.removeAllElements();
+        for (int c=0; c< wordCount;c++)
+            if (keyWordArray.getState(c))
+            {
+                selected.add(keyWordArray.getWord(c));
+                selArrayIndex.addElement(new Integer(c));
+            }
+            else
+            {
+                available.add(keyWordArray.getWord(c));
+                listArrayIndex.addElement(new Integer(c));
+            }
+    }
+
+
+    /** A dummy action listener to absorb double mouse clicks */
+    class SymAction implements java.awt.event.ActionListener
+    {
+        public void actionPerformed(java.awt.event.ActionEvent event)
+        {
+            Object object = event.getSource();
+            //System.out.println("Action event");
+        }
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/ListPanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/ListPanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/ListPanel.java	(revision 21054)
@@ -0,0 +1,127 @@
+
+/**
+ * Title:        <p>
+ * Description:  <p>
+ * Copyright:    Copyright (c) <p>
+ * Company:      <p>
+ * @author
+ * @version 1.0
+ */
+package vishnu.testvis.visual;
+
+import javax.swing.*;
+import java.awt.*;
+import javax.swing.border.*;
+import java.awt.event.*;
+
+public class ListPanel extends JPanel
+{
+    JList documentsList = new JList();
+    BorderLayout borderLayout1 = new BorderLayout();
+    Border border1;
+    JLabel descriptionLabel = new JLabel();
+    Vishnu displayFrame;
+    JPanel southPanel = new JPanel();
+    JButton fetchButton = new JButton();
+
+    public ListPanel()
+    {
+        displayFrame=null;
+        try
+        {
+            jbInit();
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public ListPanel(Vishnu d)
+    {
+        displayFrame=d;
+        try
+        {
+            jbInit();
+        }
+        catch(Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void jbInit() throws Exception
+    {
+        border1 = BorderFactory.createEmptyBorder(10,10,10,10);
+        this.setLayout(borderLayout1);
+        this.setBorder(border1);
+        descriptionLabel.setFont(new java.awt.Font("Monospaced", 0, 14));
+        descriptionLabel.setText("Document descriptions");
+        documentsList.setFont(new java.awt.Font("Monospaced", 0, 12));
+        documentsList.setToolTipText("");
+        documentsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        documentsList.setVisibleRowCount(10);
+        documentsList.addMouseListener(new java.awt.event.MouseAdapter()
+        {
+
+            public void mouseClicked(MouseEvent e)
+            {
+                documentsList_mouseClicked(e);
+            }
+        });
+        fetchButton.setFont(new java.awt.Font("Monospaced", 0, 16));
+        fetchButton.setBorder(BorderFactory.createEtchedBorder());
+        fetchButton.setText("Fetch");
+        fetchButton.addMouseListener(new java.awt.event.MouseAdapter()
+        {
+
+            public void mouseClicked(MouseEvent e)
+            {
+                fetchButton_mouseClicked(e);
+            }
+        });
+        this.add(documentsList, BorderLayout.CENTER);
+        this.add(descriptionLabel, BorderLayout.NORTH);
+        this.add(southPanel, BorderLayout.SOUTH);
+        southPanel.add(fetchButton, null);
+    }
+
+    public void update()
+    {
+       documentsList.setListData(displayFrame.dataManager.getDescriptions());
+       repaint();
+    }
+
+    void documentsList_mouseClicked(MouseEvent e)
+    {
+        int index = documentsList.getSelectedIndex();
+        if (index >=0)
+            displayFrame.dataManager.fetch(index);
+    }
+
+    void fetchButton_mouseClicked(MouseEvent e)
+    {
+        int index = documentsList.getSelectedIndex();
+        if (index >=0)
+            displayFrame.dataManager.fetch(index);
+    }
+
+    void fetchButton_mouseEntered(MouseEvent e)
+    {
+
+    }
+
+    void fetchButton_mouseExited(MouseEvent e)
+    {
+
+    }
+
+    void fetchButton_mousePressed(MouseEvent e)
+    {
+
+    }
+
+    void fetchButton_mouseReleased(MouseEvent e)
+    {
+
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/MapKeyWords.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/MapKeyWords.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/MapKeyWords.java	(revision 21054)
@@ -0,0 +1,78 @@
+package vishnu.testvis.visual;
+ /** This class is maps the display array of keywords onto the stored.
+  *  array
+  *  @author Matthew Carey</a>
+  */
+public class MapKeyWords
+{
+   /** array map for display to stored */
+   protected int nodesToWord[] = null;
+   /** array map for stored to display */
+   protected int wordsToNodes[] = null;
+   /**
+   * map for display to stored
+   * @param display index
+   * @return stored index
+   */
+   public int mapNodesToWords(int index)
+   {
+        return nodesToWord[index];
+   }
+   /**
+   * map for stored to display
+   * @param stored index
+   * @return the display index
+   */
+   public int mapWordsToNodes(int index)
+   {
+        return wordsToNodes[index];
+   }
+   /**
+   * constructor
+   * @param keywordarray instance containing list of key words and states
+   */
+   public MapKeyWords(KeyWordArray keyWordArray)
+   {
+        int c, n=0;
+        nodesToWord = null;
+        wordsToNodes = null;
+        nodesToWord = new int [keyWordArray.getNumEnabled()];
+        wordsToNodes = new int [keyWordArray.getNumWords()];
+        int totalKeyWord=keyWordArray.getNumWords();
+        for (c=0;c < totalKeyWord; c++)
+        {
+             if (keyWordArray.getState(c))
+             {
+                wordsToNodes[c]=n;
+                nodesToWord[n]=c;
+                n++;
+             }
+             else
+                wordsToNodes[c]=-1;
+        }
+   }
+   /**
+   * re-Map the KeyWords with a new vector
+   * @param array the new vector
+   * @param keywordarray instance containing list of key words and states
+   */
+   public void reMapKeyWords(int array[],KeyWordArray keyWordArray)
+   {
+        if (array.length != keyWordArray.getNumEnabled())
+            return;
+        int c;
+        for (c=0;c<array.length;c++)
+        {
+            if (!keyWordArray.getState(array[c]))
+               return;
+        }
+
+        for (c=0;c<array.length;c++)
+        {
+                wordsToNodes[array[c]]=c;
+                nodesToWord[c]=array[c];
+        }
+   }
+
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/MultiLineLabel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/MultiLineLabel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/MultiLineLabel.java	(revision 21054)
@@ -0,0 +1,136 @@
+package vishnu.testvis.visual;
+
+import java.awt.*;
+import java.util.*;
+
+/**
+ * A custom component that displays multiple lines of text with specified
+ * margins and alignment.  In Java 1.1, we could extend Component instead
+ * of Canvas, making this a more efficient "Lightweight component"
+ * from Java Examples in a Nutshell,
+ * @author Flanagan, David - O'Rielly 1997 NY
+ */
+public class MultiLineLabel extends Canvas {
+  // User-specified attributes
+  protected String label;             // The label, not broken into lines
+  protected int margin_width;         // Left and right margins
+  protected int margin_height;        // Top and bottom margins
+  protected int alignment;            // The alignment of the text.
+  public static final int LEFT = 0, CENTER = 1, RIGHT = 2; // alignment values
+  // Computed state values
+  protected int num_lines;            // The number of lines
+  protected String[] lines;           // The label, broken into lines
+  protected int[] line_widths;        // How wide each line is
+  protected int max_width;            // The width of the widest line
+  protected int line_height;          // Total height of the font
+  protected int line_ascent;          // Font height above baseline
+  protected boolean measured = false; // Have the lines been measured?
+
+  // Here are five versions of the constructor
+  public MultiLineLabel(String label, int margin_width,
+                        int margin_height, int alignment) {
+    this.label = label;                 // Remember all the properties
+    this.margin_width = margin_width;
+    this.margin_height = margin_height;
+    this.alignment = alignment;
+    newLabel();                         // Break the label up into lines
+  }
+  public MultiLineLabel(String label, int margin_width, int margin_height) {
+    this(label, margin_width, margin_height, LEFT);
+  }
+  public MultiLineLabel(String label, int alignment) {
+    this(label, 10, 10, alignment);
+  }
+  public MultiLineLabel(String label) { this(label, 10, 10, LEFT); }
+  public MultiLineLabel() { this(""); }
+
+  // Methods to set and query the various attributes of the component
+  // Note that some query methods are inherited from the superclass.
+  public void setLabel(String label) {
+    this.label = label;
+    newLabel();               // Break the label into lines
+    measured = false;         // Note that we need to measure lines
+    repaint();                // Request a redraw
+  }
+  public void setFont(Font f) {
+    super.setFont(f);         // tell our superclass about the new font
+    measured = false;         // Note that we need to remeasure lines
+    repaint();                // Request a redraw
+  }
+  public void setForeground(Color c) {
+    super.setForeground(c);   // tell our superclass about the new color
+    repaint();                // Request a redraw (size is unchanged)
+  }
+  public void setAlignment(int a) { alignment = a; repaint(); }
+  public void setMarginWidth(int mw) { margin_width = mw; repaint(); }
+  public void setMarginHeight(int mh) { margin_height = mh; repaint(); }
+  public String getLabel() { return label; }
+  public int getAlignment() { return alignment; }
+  public int getMarginWidth() { return margin_width; }
+  public int getMarginHeight() { return margin_height; }
+
+  /**
+   * This method is called by a layout manager when it wants to
+   * know how big we'd like to be.  In Java 1.1, getPreferredSize() is
+   * the preferred version of this method.  We use this deprecated version
+   * so that this component can interoperate with 1.0 components.
+   */
+  public Dimension getPreferredSize() {
+    if (!measured) measure();
+    return new Dimension(max_width + 2*margin_width,
+                         num_lines * line_height + 2*margin_height);
+  }
+
+  /**
+   * This method is called when the layout manager wants to know
+   * the bare minimum amount of space we need to get by.
+   * For Java 1.1, we'd use getMinimumSize().
+   */
+  public Dimension getMinimumSize() { return getPreferredSize(); }
+
+  /**
+   * This method draws the label (same method that applets use).
+   * Note that it handles the margins and the alignment, but that
+   * it doesn't have to worry about the color or font--the superclass
+   * takes care of setting those in the Graphics object we're passed.
+   */
+  public void paint(Graphics g) {
+    int x, y;
+    Dimension size = this.getSize();  // use getSize() in Java 1.1
+    if (!measured) measure();
+    y = line_ascent + (size.height - num_lines * line_height)/2;
+    for(int i = 0; i < num_lines; i++, y += line_height) {
+      switch(alignment) {
+      default:
+      case LEFT:    x = margin_width; break;
+      case CENTER:  x = (size.width - line_widths[i])/2; break;
+      case RIGHT:   x = size.width - margin_width - line_widths[i]; break;
+      }
+      g.drawString(lines[i], x, y);
+    }
+  }
+
+  /** This internal method breaks a specified label up into an array of lines.
+   *  It uses the StringTokenizer utility class. */
+  protected synchronized void newLabel() {
+    StringTokenizer t = new StringTokenizer(label, "\n");
+    num_lines = t.countTokens();
+    lines = new String[num_lines];
+    line_widths = new int[num_lines];
+    for(int i = 0; i < num_lines; i++) lines[i] = t.nextToken();
+  }
+
+  /** This internal method figures out how the font is, and how wide each
+   *  line of the label is, and how wide the widest line is. */
+  protected synchronized void measure() {
+    FontMetrics fm = this.getFontMetrics(this.getFont());
+    line_height = fm.getHeight();
+    line_ascent = fm.getAscent();
+    max_width = 0;
+    for(int i = 0; i < num_lines; i++) {
+      line_widths[i] = fm.stringWidth(lines[i]);
+      if (line_widths[i] > max_width) max_width = line_widths[i];
+    }
+    measured = true;
+  }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/PlainPanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/PlainPanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/PlainPanel.java	(revision 21054)
@@ -0,0 +1,153 @@
+/**
+ * Title:        <p>
+ * Description:  <p>
+ * Copyright:    Copyright (c) <p>
+ * Company:      <p>
+ * @author
+ * @version 1.0
+ */
+package vishnu.testvis.visual;
+import javax.swing.*;
+import javax.swing.event.*;
+import java.io.*;
+import java.util.Date;
+
+import javax.swing.JPanel;
+import java.awt.*;
+import javax.swing.border.*;
+import java.awt.event.*;
+
+public class PlainPanel extends JPanel
+{
+    Vishnu displayFrame;
+    BorderLayout borderLayout1;
+    Border border1;
+    JScrollPane theScrollPane;
+    JEditorPane theEditorPane;
+
+
+  // This method returns an HTML table
+    public String makeHTMLTable()
+    {
+        String [] descriptions = displayFrame.dataManager.getDescriptions();
+        int [] docNums = displayFrame.dataManager.getDocs();
+        // Set up an output stream we can print the table to.
+        // This is easier than concatenating strings all the time.
+        int number;
+        if (docNums==null) number = 0;
+        else number = docNums.length;
+
+        StringWriter sout = new StringWriter();
+        PrintWriter out = new PrintWriter(sout);
+
+        // Print out the table
+	Dimension d = displayFrame.getSize();
+	int totalWidth = d.width-40;
+	int textWidth = totalWidth - 100;
+	
+        out.println("<TABLE BORDER= 2 WIDTH=" + totalWidth + "><TR>");
+
+        out.println("<TH WIDTH= 100>Document Number</TH>");
+        out.println("<TH WIDTH = " + textWidth + ">Sample Text</TH></TR>");
+
+        for(int i=0; i < docNums.length; i++)
+        {
+            out.print("<TR><TD><A HREF=\"");
+            out.print(docNums[i]);
+            out.print("\">");
+            out.print(docNums[i]);
+            out.print("</A></TD><TD>");
+            out.print(descriptions [i]);
+            out.println("</TD></TR>");
+        }
+        out.println("</TABLE>");
+        out.close();
+
+        // Get the string of HTML from the StringWriter and return it.
+        return sout.toString();
+    }
+
+    public PlainPanel()
+    {
+        try
+        {
+            jbInit();
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+
+    public PlainPanel(Vishnu d)
+    {
+        displayFrame = d;
+        try
+        {
+            jbInit();
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+
+// The sychronized qualifier is placed here because when two plain
+// panels are created in different threads almost simulataniously
+// we seem to get interference happening
+    public synchronized void jbInit() throws Exception
+    {
+        borderLayout1 = new BorderLayout();
+        theEditorPane = new JEditorPane();
+
+        border1 = BorderFactory.createEmptyBorder(10,10,10,10);
+        theEditorPane.setEditable(false);               // we're browsing not editing
+        theEditorPane.setContentType("text/html");      // must specify HTML text
+        theEditorPane.setText("<TABLE BORDER= 2 WIDTH=640><TR>"+
+                                "<TH WIDTH= 100>Document Number</TH>" +
+                                "<TH WIDTH = 520>Sample Text</TH></TR>" +
+                                "</TABLE>");  // specify the text to display
+        theEditorPane.setFont(new java.awt.Font("Monospaced", 0, 12));
+    // Set up the JEditorPane to handle clicks on hyperlinks
+        theEditorPane.addHyperlinkListener(new HyperlinkListener() {
+                public void hyperlinkUpdate(HyperlinkEvent e)
+                {
+	            // Handle clicks; ignore mouseovers and other link-related events
+	                if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
+                    {
+	            // Get the HREF of the link and display it.
+                        String hpresult = e.getDescription();
+                        int index = Integer.parseInt(hpresult);
+                        if (index > 0)
+                            displayFrame.dataManager.fetchDocNo(index);
+	                }
+                }
+            });
+
+        // Put the JEditorPane in a scrolling window and display it.
+
+        this.setLayout(borderLayout1);
+        this.setFont(new java.awt.Font("Monospaced", 0, 12));
+        this.setBorder(border1);
+        theScrollPane = new JScrollPane();
+        //theScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
+        this.add(theScrollPane, BorderLayout.CENTER);
+        theScrollPane.getViewport().add(theEditorPane, null);
+        setSize(600, 400);
+        setVisible(true);
+    }
+
+    public void update()
+    {
+       theEditorPane.setText(makeHTMLTable());
+    }
+
+    public void paint(Graphics g)
+    {
+	update();
+    }
+
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Pnt2DTupleNode.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Pnt2DTupleNode.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Pnt2DTupleNode.java	(revision 21054)
@@ -0,0 +1,42 @@
+/**
+* The head of the rows of floating point lists contains drawing and
+* title information about a document as well as the document index
+* @author Matthew Carey
+*/
+package vishnu.testvis.visual;
+
+import vishnu.datablock.*;
+
+public class Pnt2DTupleNode
+{
+    /** index of the document */
+    public int index;
+    /** start of linked list */
+    public SparseNode row;
+    /** cartesian floating point location */
+    public Point2D pnt;
+    /** raster integer locatoin */
+    public Ipoint2D iPnt;
+    /** drawn or not */
+    public boolean state;
+    /** document title */
+    public String ref;
+    /**
+    * Constructor
+    * @param cartesian x
+    * @param cartesain y
+    * @param index
+    * @param linked list of data
+    * @param title
+    */
+    Pnt2DTupleNode(float x, float y ,int i, SparseNode fn, String reference)
+    {
+        state = false;
+        index = i;
+        pnt = new Point2D(x,y);
+        iPnt = new Ipoint2D(0,0);
+        ref=reference;
+        if (reference==null) ref = "Not defined";
+        row=fn;
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Pt2DNode.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Pt2DNode.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Pt2DNode.java	(revision 21054)
@@ -0,0 +1,53 @@
+package vishnu.testvis.visual;
+import vishnu.datablock.*;
+/**
+* Downward linked list wrapper class enables several lists to share
+* same row nodes
+* @author Matthew Carey
+*/
+
+public class Pt2DNode
+{
+     /** next node */
+     Pt2DNode next;
+     /** row data */
+     Pnt2DTupleNode tuple;
+     /** constructor
+     * @param another node to link to
+     */
+     Pt2DNode(Pnt2DTupleNode t)
+     {
+        tuple = t;
+        next = null;
+     }
+     /**
+     * constructor
+     * @param cartesian x
+     * @param cartesian y
+     * @param document index
+     * @param data row
+     * @param document title
+     */
+     Pt2DNode(float x, float y ,int i, SparseNode t, String s)
+     {
+        tuple = new Pnt2DTupleNode(x,y,i,t,s);
+        next = null;
+     }
+     /**
+     * Get next node
+     * @return next node
+     */
+     Pt2DNode getNext()
+     {
+        return next;
+     }
+     /**
+     * Set next node
+     * @param another node to be next
+     */
+     public void setNext(Pt2DNode n)
+     {
+        n.next=next;
+        next = n;
+     }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/RadVizPanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/RadVizPanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/RadVizPanel.java	(revision 21054)
@@ -0,0 +1,300 @@
+package vishnu.testvis.visual;
+
+import java.awt.*;
+import javax.swing.*;
+import javax.swing.border.*;
+import java.awt.event.*;
+
+public class RadVizPanel extends JPanel
+{
+    protected CvManyPoints canvas1;
+    public java.awt.Label docLabel;
+    protected KeywordPanel keyWordPanel;
+    protected Panel south = new Panel();
+
+    protected Border border1;
+
+    protected JPanel buttonPanel = new JPanel();
+    protected JPanel buttonBasePanel = new JPanel();
+    protected JPanel labelPanel = new JPanel();
+    protected JLabel buttonLabel = new JLabel();
+    protected JButton zoomInButton = new JButton();
+    protected JButton zoomOutButton = new JButton();
+    protected JButton resetButton = new JButton();
+    protected JButton clusterButton = new JButton();
+
+    protected JCheckBox showGridCheckBox = new JCheckBox();
+    protected JCheckBox lockCheckBox = new JCheckBox();
+    protected JCheckBox showRimCheckBox = new JCheckBox();
+    protected JCheckBox projectCheckBox = new JCheckBox();
+
+    protected int smallFont = 14;
+    protected int largeFont = 16;
+    protected Border border3;
+    protected BorderLayout borderLayout1 = new BorderLayout(10,0);
+    protected Vishnu parent;
+
+    public RadVizPanel(Vishnu p)
+    {
+        parent = p;
+        try
+        {
+            jbInit();
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+
+
+    private void jbInit() throws Exception
+    {
+        border1 = BorderFactory.createEmptyBorder(5,5,5,5);
+        border3 = BorderFactory.createEmptyBorder(10,10,10,10);
+        this.setLayout(borderLayout1);
+        this.setBorder(border3);
+        this.setPreferredSize(new Dimension(600, 400));
+        canvas1 = new CvManyPoints (parent);
+	
+	// This is used as a balloon by the canvas1
+        docLabel = new java.awt.Label("  ");
+        docLabel.setVisible(false);
+
+	// place it out of view if does become visible
+        docLabel.setBounds(12,-20,10,10); 
+        docLabel.setBackground(new Color(16776895));
+        docLabel.setFont(new Font("Dialog", Font.PLAIN, largeFont));
+	
+        add(docLabel);
+        add(canvas1, "Center");
+	
+        keyWordPanel = new KeywordPanel(canvas1, this);
+	keyWordPanel.setVisible(false);
+	
+        buttonBasePanel.setLayout(new BorderLayout(10,10));
+        buttonPanel.setLayout(new GridLayout(0,2,10,10));
+        buttonLabel.setText("Settings");
+        buttonLabel.setForeground(Color.black);
+        buttonLabel.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+	
+        zoomInButton.setText("Zoom in");
+        zoomInButton.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        zoomInButton.setBorder(BorderFactory.createEtchedBorder());
+        zoomInButton.addMouseListener(new java.awt.event.MouseAdapter()
+	    {
+		public void mouseClicked(MouseEvent e)
+		{
+		    zoomInButton_mouseClicked(e);
+		}
+	    });
+        zoomOutButton.setText("Zoom Out");
+        zoomOutButton.setFont(new java.awt.Font("Monospaced", 0,largeFont));
+        zoomOutButton.setBorder(BorderFactory.createEtchedBorder());
+        zoomOutButton.addMouseListener(new java.awt.event.MouseAdapter()
+	    {
+		public void mouseClicked(MouseEvent e)
+		{
+		    zoomOutButton_mouseClicked(e);
+		}
+	    });
+        resetButton.setText("Reset");
+        resetButton.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        resetButton.setBorder(BorderFactory.createEtchedBorder());
+        resetButton.addMouseListener(new java.awt.event.MouseAdapter()
+	    {
+		public void mouseClicked(MouseEvent e)
+		{
+		    resetButton_mouseClicked(e);
+		}
+	    });
+        clusterButton.setText("Cluster");
+        clusterButton.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        clusterButton.setBorder(BorderFactory.createEtchedBorder());
+        clusterButton.addMouseListener(new java.awt.event.MouseAdapter()
+	    {
+		public void mouseClicked(MouseEvent e)
+		{
+		    clusterButton_mouseClicked(e);
+		}
+	    });
+	
+        showGridCheckBox.setText("Show Grid");
+        showGridCheckBox.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        showGridCheckBox.setSelected(false);
+        showGridCheckBox.setBorder(BorderFactory.createEtchedBorder());
+        showGridCheckBox.addItemListener(new java.awt.event.ItemListener()
+	    {
+		public void itemStateChanged(ItemEvent e)
+		{
+		    showGridCheckBox_itemStateChanged(e);
+		}
+	    });
+        showRimCheckBox.setText("Show Rim");
+        showRimCheckBox.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        showRimCheckBox.setSelected(true);
+        showRimCheckBox.setBorder(BorderFactory.createEtchedBorder());
+        showRimCheckBox.addItemListener(new java.awt.event.ItemListener()
+	    {
+		public void itemStateChanged(ItemEvent e)
+		{
+		    showRimCheckBox_itemStateChanged(e);
+		}
+	    });
+	
+        projectCheckBox.setText("Projection");
+        projectCheckBox.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        projectCheckBox.setSelected(true);
+        projectCheckBox.setBorder(BorderFactory.createEtchedBorder());
+        projectCheckBox.addItemListener(new java.awt.event.ItemListener()
+	    {
+		public void itemStateChanged(ItemEvent e)
+		{
+		    projectCheckBox_itemStateChanged(e);
+		}
+	    });
+        lockCheckBox.setText("Lock Radius");
+        lockCheckBox.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        lockCheckBox.setSelected(false);
+        lockCheckBox.setBorder(BorderFactory.createEtchedBorder());
+        lockCheckBox.addItemListener(new java.awt.event.ItemListener()
+	    {
+		public void itemStateChanged(ItemEvent e)
+		{
+		    lockCheckBox_itemStateChanged(e);
+		}
+	    });
+	
+	
+        buttonPanel.add(zoomInButton);
+        buttonPanel.add(zoomOutButton);
+        buttonPanel.add(resetButton);
+        buttonPanel.add(clusterButton);
+        buttonPanel.add(showGridCheckBox);
+        buttonPanel.add(showRimCheckBox);
+        buttonPanel.add(projectCheckBox);
+        buttonPanel.add(lockCheckBox);
+	
+        labelPanel.add(buttonLabel);
+        buttonBasePanel.add(buttonPanel,"Center");
+        buttonBasePanel.add(labelPanel,"North");
+        south.setLayout(new BorderLayout(10,0));
+	south.add(keyWordPanel,"West");
+        south.add(buttonBasePanel,"East");
+        south.setVisible(false);
+        add(south,"South");
+    }
+    
+    
+    public void setBothVisible(boolean f)
+    {
+        keyWordPanel.setVisible(f);
+        canvas1.setVisible(f);
+        south.setVisible(f);
+    }
+    
+    
+    public void addNotify()
+    {
+	//Record the size of the window prior to calling parents addNotify.
+        Dimension d = getSize();
+
+        super.addNotify();
+	
+        if (fComponentsAdjusted)
+	    return;
+	
+	// Adjust components according to the insets
+        setSize(getInsets().left + getInsets().right + d.width,
+		getInsets().top + getInsets().bottom + d.height);
+        Component components[] = getComponents();
+        for (int i = 0; i < components.length; i++)
+	    {
+		Point p = components[i].getLocation();
+		p.translate(getInsets().left, getInsets().top);
+		components[i].setLocation(p);
+	    }
+        fComponentsAdjusted = true;
+    }
+    
+    
+    void  zoomOutButton_mouseClicked(MouseEvent e)
+    {
+        canvas1.higher();
+    }
+
+
+    void  zoomInButton_mouseClicked(MouseEvent e)
+    {
+        canvas1.flatter();
+    }
+
+
+    void  resetButton_mouseClicked(MouseEvent e)
+    {
+        canvas1.recalc();
+    }
+
+
+    void  clusterButton_mouseClicked(MouseEvent e)
+    {
+        canvas1.cluster();
+    }
+
+
+    void showGridCheckBox_itemStateChanged(ItemEvent e)
+    {
+        canvas1.grid(showGridCheckBox.isSelected());
+    }
+
+
+    void showRimCheckBox_itemStateChanged(ItemEvent e)
+    {
+        canvas1.rim(showRimCheckBox.isSelected());
+    }
+
+
+    void projectCheckBox_itemStateChanged(ItemEvent e)
+    {
+        canvas1.projection(projectCheckBox.isSelected());
+    }
+
+
+    void lockCheckBox_itemStateChanged(ItemEvent e)
+    {
+        canvas1.lock(lockCheckBox.isSelected());
+    }
+
+
+    public void update()
+    {
+        canvas1.update();
+        keyWordPanel.update();
+    }
+
+
+    public void setVisible(boolean f)
+    {
+        super.setVisible(f);
+        setBothVisible(f);
+    }
+
+
+    public void paint(Graphics g)
+    {
+	System.out.println("Updating RadVizPanel");
+	//canvas1.update();
+	keyWordPanel.update();
+    }
+    
+    protected boolean fComponentsAdjusted = false;
+}
+
+
+
+
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/SimplePanel.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/SimplePanel.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/SimplePanel.java	(revision 21054)
@@ -0,0 +1,205 @@
+
+/**
+ * Title:        VeniVidiVinci<p>
+ * Description:  An applet that displays visualisations of data sets and allows
+ * these visualisations to be manipulated and selections made<p>
+ * Copyright:    Copyright (c) Matthew Carey<p>
+ * Company:      Imperial College<p>
+ * @author Matthew Carey
+ * @version 1.0
+ */
+package vishnu.testvis.visual;
+
+import javax.swing.*;
+import javax.swing.plaf.basic.*;
+import java.awt.*;
+import java.awt.event.*;
+
+
+/** Panel containing the graphical search interface */
+public class SimplePanel extends JPanel {
+
+    JTextField textField;
+    Vishnu currentMain;
+
+
+    public class InfoLabel extends JLabel {
+        void update() {
+            int startIndex = localHLinkPanel.index +1;
+            int endIndex = localHLinkPanel.index + localHLinkPanel.numDisplayedDocs;
+            int numDocs = currentMain.dataManager.getDocCount();
+            if (numDocs < endIndex) endIndex = numDocs;
+
+            setText("Documents "+ startIndex + "-" + endIndex + " out of " + numDocs);
+        }
+
+    }
+
+    InfoLabel infoLabel = new InfoLabel();
+
+    Hlink localHLinkPanel;
+
+    JButton backButton, nextButton;
+    public SimplePanel(Vishnu theMain)
+    {
+        this.currentMain = theMain;
+        localHLinkPanel = new Hlink(600,400,currentMain);
+        try
+        {
+            jbInit();
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+    public SimplePanel()
+
+    {
+        this.currentMain = null;
+        localHLinkPanel = new Hlink();
+        try
+        {
+            jbInit();
+        }
+        catch(Exception e)
+        {
+            e.printStackTrace();
+        }
+    }
+    private void jbInit() throws Exception
+    {
+        setName("Simple ");
+
+
+        backButton = new JButton("<<");
+        backButton.setBorder(BorderFactory.createEtchedBorder());
+        backButton.setEnabled(false);
+        backButton.addMouseListener( new MouseAdapter() {
+            public void mouseClicked(MouseEvent e) {
+
+                if (!backButton.isEnabled()) return;
+
+                nextButton.setEnabled(true);
+
+                localHLinkPanel.index -= localHLinkPanel.numDisplayedDocs;
+                localHLinkPanel.update();
+                infoLabel.update();
+
+                if (localHLinkPanel.index == 0) {
+                    backButton.setEnabled(false);
+                }
+            }
+        });
+        nextButton = new JButton(">>");
+        nextButton.setBorder(BorderFactory.createEtchedBorder());
+        nextButton.setEnabled(false);
+        nextButton.addMouseListener( new MouseAdapter()
+        {
+            public void mouseClicked(MouseEvent e)
+            {
+
+                if (!nextButton.isEnabled()) return;
+
+                backButton.setEnabled(true);
+
+                localHLinkPanel.index += localHLinkPanel.numDisplayedDocs;
+                localHLinkPanel.update();
+                infoLabel.update();
+
+                if (localHLinkPanel.index + localHLinkPanel.numDisplayedDocs >= currentMain.dataManager.getDocCount())
+                {
+                    nextButton.setEnabled(false);
+                }
+            }
+        });
+
+        //
+
+        // Set the new layout
+        GridBagLayout gridBag = new GridBagLayout();
+        setLayout(gridBag);
+
+        GridBagConstraints constraints = new GridBagConstraints();
+
+        constraints.fill = GridBagConstraints.BOTH;
+        constraints.insets = new Insets(10,3,10,3);
+        constraints.ipadx = 0;
+        constraints.ipady = 8;
+
+
+
+        // Link panel
+        constraints.ipady = 4;
+        constraints.gridx = 0;
+        constraints.gridy = GridBagConstraints.RELATIVE;
+        constraints.gridwidth = GridBagConstraints.REMAINDER;
+        constraints.gridheight = 8;
+
+        gridBag.setConstraints(localHLinkPanel.linkPane,constraints);
+        add(localHLinkPanel.linkPane);
+
+        // Info label
+        constraints.gridx = 0;
+        constraints.gridy = GridBagConstraints.RELATIVE;
+        constraints.gridwidth = 6;
+        constraints.gridheight = GridBagConstraints.REMAINDER;
+
+        infoLabel.setMinimumSize(new Dimension(500,20));
+        infoLabel.setMaximumSize(new Dimension(500,20));
+        gridBag.setConstraints(infoLabel,constraints);
+        add(infoLabel);
+
+        // Back button
+
+        constraints.fill = GridBagConstraints.NONE;
+        constraints.gridx = GridBagConstraints.RELATIVE;
+        constraints.gridy = 9;
+        constraints.gridwidth = 2;
+        constraints.gridheight = GridBagConstraints.REMAINDER;
+
+        Dimension buttonDimension = new Dimension(55,30);
+        backButton.setMaximumSize(buttonDimension);
+        backButton.setPreferredSize(buttonDimension);
+        backButton.setMinimumSize(buttonDimension);
+
+        gridBag.setConstraints(backButton,constraints);
+        add(backButton);
+
+        // Next button
+        constraints.gridx = GridBagConstraints.RELATIVE;
+        constraints.gridy = 9;
+        constraints.gridwidth = 2;
+        constraints.gridheight = GridBagConstraints.REMAINDER;
+
+
+        nextButton.setMaximumSize(buttonDimension);
+        nextButton.setPreferredSize(buttonDimension);
+        nextButton.setMinimumSize(buttonDimension);
+
+        gridBag.setConstraints(nextButton,constraints);
+        add(nextButton);
+
+        // Global layout
+        setSize(600,400);
+        setPreferredSize(new Dimension(600,400));
+        setMinimumSize(new Dimension(600,400));
+
+        setVisible(true);
+    }
+
+
+    public void update() {
+
+        localHLinkPanel.index = 0;
+
+        infoLabel.update();
+        localHLinkPanel.update();
+
+        if (currentMain.dataManager.getDocCount() > localHLinkPanel.numDisplayedDocs) {
+            nextButton.setEnabled(true);
+        }
+
+    }
+
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Vishnu.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Vishnu.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/Vishnu.java	(revision 21054)
@@ -0,0 +1,445 @@
+package vishnu.testvis.visual;
+import vishnu.testvis.treemap.visual.TMNavigator;
+import vishnu.testvis.sammon.*;
+import vishnu.testvis.object.*;
+import vishnu.testvis.evaluate.*;
+import vishnu.testvis.dendro.*;
+import java.awt.*;
+import javax.swing.*;
+import java.awt.event.*;
+import java.beans.*;
+import javax.swing.event.*;
+import java.io.*;
+import java.util.Vector;
+import java.util.Properties;
+import java.applet.*;
+import java.net.*;
+import vishnu.datablock.*;
+
+public class Vishnu extends JApplet
+{
+    public JTabbedPane theTabbedPane = null;
+    public PlainPanel plainPanel = null;
+    public GraphicalPane sammonPanel = null;
+    public RadVizPanel radVizPanel = null;
+    public DendroPanel dendroPanel = null;
+    public JButton findButton = null;
+    
+    public DataManager dataManager = null;
+    
+    public Logging log = null;
+    public Queries queries = null;
+    public boolean searchStarted = false;
+    public boolean firstSearch = true;
+    public boolean refine = false;
+    
+    public JTextField searchBox = null;
+    public JComboBox collCombo = null;
+    public JComboBox viewCombo = null;
+    protected int largeFont = 16;
+    
+    public String query = null;
+    public String coll = null;
+    public String view = null;
+    
+    public String server_address = null;
+    
+    protected Dimension size = null;
+        
+    public void init()
+    {
+	String library = getParameter("library");
+	if (library == null ) {
+	    System.err.println("Error: library parameter is null!!");
+	    // what should we do?
+	    return;
+	}
+	String viscgi = getParameter("viscgi");
+	
+	query = getParameter("query");
+        coll = getParameter("collection");
+	view = getParameter("view");
+	
+	server_address = library;
+	if (viscgi != null) {
+	    // if viscgi parameter is set (even if empty) we assume that
+	    // the server is running  cgi-like with arguments in the url
+	    server_address += viscgi;
+	    server_address = tidy_URL(server_address, true);
+	} else {
+	    server_address = tidy_URL(server_address, false);
+	}
+	
+	// What kind of engine the dataManager should use
+	String engine = getParameter("engine");
+ 
+	String w = getParameter("width");
+	String h = getParameter("height");
+	
+	if( (w != null) && (h != null) )
+	    size = new Dimension(Integer.parseInt(w),Integer.parseInt(h));
+        else size = new Dimension(1000,800);
+	
+	dataManager = new DataManager(this, engine);
+	
+        this.setSize(size); 
+        
+	theTabbedPane = new JTabbedPane();
+
+	radVizPanel = new RadVizPanel(this);
+        radVizPanel.setName("RadialVis");
+        
+        plainPanel = new PlainPanel(this);
+        plainPanel.setName("PlainVis");
+        
+        sammonPanel = new GraphicalPane(this);
+        sammonPanel.setName("SammonVis");
+        
+        dendroPanel = new DendroPanel(this);
+        dendroPanel.setName("DendroVis");
+	
+	findButton = new JButton();
+        try {
+	    jbInit();
+	} catch(Exception e){e.printStackTrace();}
+	
+	initCollections();
+	
+	if( query != null && !query.equals("null") ){
+	    searchBox.setText(query);
+	    findQuery();
+	}
+    }
+
+    protected void initCollections() {
+
+	Collection current_collection=null;
+	String current_view = null;
+	if( coll == null || coll.equals("") || coll.equals("null") ) {
+	    current_collection = ((Collection)dataManager.collections.elementAt(0));
+	    coll = current_collection.dir;
+	} else {
+	    // try to find the one specified in the parameters
+	    for( int i = 0; i < dataManager.collections.size(); i++ ){
+		Collection c = (Collection)dataManager.collections.elementAt(i);
+		if( c.dir.equals(coll) ){
+		    current_collection = c;
+		    current_view = view; // use the one from the parameters
+		    break;
+		}
+	    }
+	}
+	if (current_collection == null) {
+	    // could still be null if we specified an invalid collection in the parameters
+	    current_collection = ((Collection)dataManager.collections.elementAt(0));
+	    coll = current_collection.dir;
+	}
+	if (current_view == null) {
+	    if (current_collection.views != null) {
+		current_view = (String)current_collection.views[0];
+	    }
+	}
+	dataManager.currentCollection = current_collection;
+	dataManager.currentView = current_view;
+	this.view = current_view;
+	dataManager.requestCollection();
+	System.out.println("Requesting collection " + current_collection.name);
+	collCombo.setSelectedItem(current_collection);
+	if (viewCombo != null && current_view != null) {
+	    viewCombo.setSelectedItem(current_view);
+	    
+	}
+	    
+    }
+
+    protected void jbInit() throws Exception
+    {
+	JPanel pane = new JPanel();
+	setContentPane(pane);
+	pane.setBorder(BorderFactory.createLineBorder(Color.black, 2));
+        pane.setLayout(new BorderLayout());
+        
+	JPanel searchPanel = new JPanel();
+	searchPanel.setLayout(new BorderLayout(5,5));
+	searchPanel.setBackground(SystemColor.control);
+        searchBox = new JTextField();
+	searchBox.setPreferredSize(new Dimension((int)(size.width/5), 26));
+        searchBox.setEditable(true);
+        searchBox.addKeyListener(new java.awt.event.KeyAdapter(){
+                public void keyPressed(KeyEvent e)
+                {
+                    findButton_keyPressed(e);
+                }
+            });
+	
+	JLabel searchLabel = new JLabel();
+        searchLabel.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        searchLabel.setForeground(Color.black);
+        searchLabel.setText("Search:");
+
+	searchPanel.add(searchLabel, BorderLayout.WEST);
+	searchPanel.add(searchBox, BorderLayout.CENTER);
+
+	JPanel collPanel = new JPanel();
+	collPanel.setLayout(new BorderLayout(5,5));
+	collPanel.setBackground(SystemColor.control);
+
+	collCombo = new JComboBox(dataManager.collections);
+	collCombo.setEditable(false);
+	collCombo.addItemListener(new java.awt.event.ItemListener() {
+		public void itemStateChanged(ItemEvent i) {
+		    if (i.getStateChange()==ItemEvent.SELECTED){
+			dataManager.currentCollection = (Collection)i.getItem();
+			collCombo.hidePopup();
+			coll = dataManager.currentCollection.dir;
+			if (dataManager.currentCollection.views != null) {
+			    viewCombo.setModel(new DefaultComboBoxModel(dataManager.currentCollection.views));
+			    viewCombo.setEnabled(true);
+			    view = (String)viewCombo.getItemAt(0);
+			    dataManager.currentView = view;
+
+			} else {
+			    viewCombo.setModel(new DefaultComboBoxModel());
+			    viewCombo.setEnabled(false);
+			    view = null;
+			    dataManager.currentView = view;
+			}
+			dataManager.requestCollection();
+
+			update();
+			
+		    }
+		    
+		}
+		});
+	collCombo.setRenderer(new javax.swing.DefaultListCellRenderer() {
+		public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
+		{
+		    setText(((Collection)value).name);
+		    setFont(list.getFont());
+		    return this;
+		}
+	    });
+
+	JLabel collLabel = new JLabel();           
+	collLabel.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        collLabel.setForeground(Color.black); 
+        collLabel.setText("Collection:");
+	
+	collPanel.add(collLabel, BorderLayout.WEST);
+	collPanel.add(collCombo, BorderLayout.CENTER);
+
+	JPanel viewPanel = new JPanel();
+	viewPanel.setLayout(new BorderLayout(5,5));
+	viewPanel.setBackground(SystemColor.control);
+
+	viewCombo = new JComboBox();
+	viewCombo.setEnabled(false);
+
+	JLabel viewLabel = new JLabel();           
+	viewLabel.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        viewLabel.setForeground(Color.black); 
+        viewLabel.setText("View:");
+	
+	viewPanel.add(viewLabel, BorderLayout.WEST);
+	viewPanel.add(viewCombo, BorderLayout.CENTER);
+
+	JPanel topPanel = new JPanel();
+        topPanel.setLayout(new BorderLayout(10,10));
+	topPanel.setBackground(SystemColor.control);
+        topPanel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+	
+	JPanel backPanel = new JPanel();   
+        backPanel.setPreferredSize(new Dimension(size.width,size.height));
+        backPanel.setBackground(SystemColor.control);
+        backPanel.setLayout(new BorderLayout());
+        
+	theTabbedPane.setBackground(SystemColor.control);
+        theTabbedPane.setFont(new java.awt.Font("Monospaced", Font.BOLD, largeFont));
+        theTabbedPane.addChangeListener(new javax.swing.event.ChangeListener(){
+		public void stateChanged(ChangeEvent e)
+		{
+		    theTabbedPane_stateChanged(e);
+		}
+	    });
+	
+        findButton.setBackground(SystemColor.control);
+        findButton.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        findButton.setBorder(BorderFactory.createEtchedBorder());
+        findButton.setText("Find");
+        findButton.addKeyListener(new java.awt.event.KeyAdapter(){
+		public void keyPressed(KeyEvent e)
+		{
+		    findButton_keyPressed(e);
+		}
+	    });
+        
+        findButton.addMouseListener(new java.awt.event.MouseAdapter(){
+		public void mouseClicked(MouseEvent e)
+		{
+		    findButton_mouseClicked(e);
+		}
+	    });
+	
+        pane.add(backPanel, BorderLayout.CENTER);
+        backPanel.add(theTabbedPane, BorderLayout.CENTER);
+	
+        theTabbedPane.add(plainPanel, "PlainVis");
+        theTabbedPane.add(radVizPanel, "RadialVis");
+        theTabbedPane.add(sammonPanel,"SammonVis");
+        theTabbedPane.add(dendroPanel,"DendroVis");
+        theTabbedPane.setForegroundAt(0,Color.blue);
+	
+        pane.add(topPanel, BorderLayout.NORTH);
+	
+	JPanel boxesPanel = new JPanel();
+	boxesPanel.setLayout(new BorderLayout(5,5));
+	boxesPanel.setBackground(SystemColor.control);
+	boxesPanel.add(collPanel, BorderLayout.WEST);
+	boxesPanel.add(viewPanel, BorderLayout.EAST);
+	
+	topPanel.add(boxesPanel, BorderLayout.WEST);
+	topPanel.add(searchPanel, BorderLayout.CENTER);
+	topPanel.add(findButton, BorderLayout.EAST);
+	
+        theTabbedPane.setPreferredSize(size);
+
+    }
+    
+    
+    public void update()
+    {
+	plainPanel.update();
+        radVizPanel.update();
+        dendroPanel.update();        
+        sammonPanel.update();
+    }
+    
+        
+    void findQuery()
+    {
+	refine = false;
+	
+        int docCount;
+	String text = null;
+	
+	text = (String)searchBox.getText();
+	if(text.trim().length() < 1) return;
+	query = text.trim();
+	
+	if (viewCombo != null) {
+	    String view1 = null;
+	    if (viewCombo.isEnabled()) {
+		view1 = (String)viewCombo.getSelectedItem();	    
+	    }
+	    if (!view1.equals(this.view)) {
+		this.view = view1;
+		dataManager.currentView = view1;
+		dataManager.requestCollection(); // resets previous data
+	    }
+	}
+        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+	
+	int count = 1;
+	String q = query;
+	docCount = dataManager.query(q);
+	if (docCount == 0 ) {
+      	    JOptionPane.showMessageDialog(this,"No documents found related to " + text);
+	    setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+	    return;
+	}
+
+        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+    
+
+    void theTabbedPane_stateChanged(ChangeEvent e)
+    {
+	for( int i = 0; i < theTabbedPane.getTabCount(); i++ )
+        {
+            theTabbedPane.setForegroundAt(i,Color.black);
+        }
+        
+        int tab = theTabbedPane.getSelectedIndex();
+        
+        theTabbedPane.setForegroundAt(tab,Color.blue);
+    }
+
+
+  void findButton_keyPressed(KeyEvent e)
+  {
+      int key = e.getKeyCode();
+      if (key==10)
+	  {
+	      findButton.setSelected(true);
+	      findQuery();
+	      findButton.setSelected(false);
+	  }
+  }
+    
+    void findButton_mouseClicked(MouseEvent e)
+    {
+	findQuery();
+    }
+    
+   
+    // Tidy up URLs
+    //
+    // Ensure a URL address (as string) has a protocol, host, and file.
+    //
+    // If the URL is a CGI script URL, it should be tidied up so that it is
+    // appropriate to tage attrib=value pairs on the end.  This means it
+    // must either end with a "?" or (if it contains a question-mark
+    // internally) end with a "&".
+    // taken from Phind.java, Greenstone digital library.
+    String tidy_URL(String address, boolean isCGI) {
+
+	// System.err.println("tidy URL: " + address);
+	
+	// make sure the URL has protocol, host, and file
+	if (address.startsWith("http")) {
+	    // the address has all the necessary components
+	} else if (address.startsWith("/")) {
+	    // there is not protocol and host
+	    URL document = getDocumentBase();
+	    String port = "";
+	    if (document.getPort()!=-1) {
+		port = ":" + document.getPort();
+	    }
+	    address = "http://" + document.getHost() + port  + address;
+	} else {
+	    // this URL is relative to the directory the document is in
+	    URL document = getDocumentBase();
+	    String directory = document.getFile();
+	    int end = directory.lastIndexOf('/');
+	    String port = "";
+	    if (document.getPort()!=-1) {
+		port = ":" + document.getPort();
+	    }
+	    directory = directory.substring(0,end + 1);
+	    address = "http://" + document.getHost() + port + directory + address;
+	    
+	}
+	
+	// if the URL is a cgi script, make sure it has a "?" in ti,
+	// and that it ends with a "?" or "&"
+	if (isCGI) {
+	    if (address.indexOf((int) '?') == -1) { 
+		address = address + "?";
+	    } else if (!address.endsWith("?")) {
+		address = address + "&";
+	    }
+	}
+
+	return address;
+    }
+
+}
+
+
+
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/VishnuSingle.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/VishnuSingle.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/testvis/visual/VishnuSingle.java	(revision 21054)
@@ -0,0 +1,118 @@
+package vishnu.testvis.visual;
+import vishnu.testvis.treemap.visual.TMNavigator;
+import vishnu.testvis.sammon.*;
+import vishnu.testvis.object.*;
+import vishnu.testvis.evaluate.*;
+import vishnu.testvis.dendro.*;
+import java.awt.*;
+import javax.swing.*;
+import java.awt.event.*;
+import java.beans.*;
+import javax.swing.event.*;
+import java.io.*;
+import java.util.Vector;
+import java.util.Properties;
+import java.applet.*;
+import java.net.*;
+import vishnu.datablock.*;
+
+public class VishnuSingle extends Vishnu
+{
+
+    protected void initCollections() {
+	// put in a dummy Collection
+	Collection c = new Collection(coll, coll, coll, 0, coll, null);
+	dataManager.currentCollection = c;
+    }
+
+    // overwrite this to remove the collection stuff - cos we are working within one collection only
+    protected void jbInit() throws Exception {
+	JPanel pane = new JPanel();
+	setContentPane(pane);
+	pane.setBorder(BorderFactory.createLineBorder(Color.black, 2));
+        pane.setLayout(new BorderLayout());
+        
+	JPanel searchPanel = new JPanel();
+	searchPanel.setLayout(new BorderLayout(5,5));
+	searchPanel.setBackground(SystemColor.control);
+        searchBox = new JTextField();
+	searchBox.setPreferredSize(new Dimension((int)(size.width/5), 26));
+        searchBox.setEditable(true);
+        searchBox.addKeyListener(new java.awt.event.KeyAdapter(){
+                public void keyPressed(KeyEvent e)
+                {
+                    findButton_keyPressed(e);
+                }
+            });
+	
+	JLabel searchLabel = new JLabel();
+        searchLabel.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        searchLabel.setForeground(Color.black);
+        searchLabel.setText("Search:");
+
+	searchPanel.add(searchLabel, BorderLayout.WEST);
+	searchPanel.add(searchBox, BorderLayout.CENTER);
+
+
+	JPanel topPanel = new JPanel();
+        topPanel.setLayout(new BorderLayout(10,10));
+	topPanel.setBackground(SystemColor.control);
+        topPanel.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
+	
+	JPanel backPanel = new JPanel();   
+        backPanel.setPreferredSize(new Dimension(size.width,size.height));
+        backPanel.setBackground(SystemColor.control);
+        backPanel.setLayout(new BorderLayout());
+        
+	theTabbedPane.setBackground(SystemColor.control);
+        theTabbedPane.setFont(new java.awt.Font("Monospaced", Font.BOLD, largeFont));
+        theTabbedPane.addChangeListener(new javax.swing.event.ChangeListener(){
+		public void stateChanged(ChangeEvent e)
+		{
+		    theTabbedPane_stateChanged(e);
+		}
+	    });
+	
+        findButton.setBackground(SystemColor.control);
+        findButton.setFont(new java.awt.Font("Monospaced", 0, largeFont));
+        findButton.setBorder(BorderFactory.createEtchedBorder());
+        findButton.setText("Find");
+        findButton.addKeyListener(new java.awt.event.KeyAdapter(){
+		public void keyPressed(KeyEvent e)
+		{
+		    findButton_keyPressed(e);
+		}
+	    });
+        
+        findButton.addMouseListener(new java.awt.event.MouseAdapter(){
+		public void mouseClicked(MouseEvent e)
+		{
+		    findButton_mouseClicked(e);
+		}
+	    });
+	
+        pane.add(backPanel, BorderLayout.CENTER);
+        backPanel.add(theTabbedPane, BorderLayout.CENTER);
+	
+        theTabbedPane.add(plainPanel, "PlainVis");
+        theTabbedPane.add(radVizPanel, "RadialVis");
+        theTabbedPane.add(sammonPanel,"SammonVis");
+        theTabbedPane.add(dendroPanel,"DendroVis");
+        theTabbedPane.setForegroundAt(0,Color.blue);
+	
+        pane.add(topPanel, BorderLayout.NORTH);
+	
+	topPanel.add(searchPanel, BorderLayout.CENTER);
+	topPanel.add(findButton, BorderLayout.EAST);
+	
+        theTabbedPane.setPreferredSize(size);
+
+    }
+    
+    
+}
+
+
+
+
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/util/Base64.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/util/Base64.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/util/Base64.java	(revision 21054)
@@ -0,0 +1,1281 @@
+package vishnu.util;
+
+import java.io.*;
+/**
+ * Encodes and decodes to and from Base64 notation.
+ *
+ * <p>
+ * Change Log:
+ * </p>
+ * <ul>
+ *  <li>v2.0.1 - Fixed an error when decoding a single byte, that is, when the
+ *   encoded data was a single byte.</li>
+ *  <li>v2.0 - I got rid of methods that used booleans to set options. 
+ *   Now everything is more consolidated and cleaner. The code now detects
+ *   when data that's being decoded is gzip-compressed and will decompress it
+ *   automatically. Generally things are cleaner. You'll probably have to
+ *   change some method calls that you were making to support the new
+ *   options format (<tt>int</tt>s that you "OR" together).</li>
+ *  <li>v1.5.1 - Fixed bug when decompressing and decoding to a             
+ *   byte[] using <tt>decode( String s, boolean gzipCompressed )</tt>.      
+ *   Added the ability to "suspend" encoding in the Output Stream so        
+ *   you can turn on and off the encoding if you need to embed base64       
+ *   data in an otherwise "normal" stream (like an XML file).</li>  
+ *  <li>v1.5 - Output stream pases on flush() command but doesn't do anything itself.
+ *      This helps when using GZIP streams.
+ *      Added the ability to GZip-compress objects before encoding them.</li>
+ *  <li>v1.4 - Added helper methods to read/write files.</li>
+ *  <li>v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.</li>
+ *  <li>v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream
+ *      where last buffer being read, if not completely full, was not returned.</li>
+ *  <li>v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.</li>
+ *  <li>v1.3.3 - Fixed I/O streams which were totally messed up.</li>
+ * </ul>
+ *
+ * <p>
+ * I am placing this code in the Public Domain. Do with it as you will.
+ * This software comes with no guarantees or warranties but with
+ * plenty of well-wishing instead!
+ * Please visit <a href="http://iharder.net/xmlizable">http://iharder.net/base64</a>
+ * periodically to check for updates or to contribute improvements.
+ * </p>
+ *
+ * @author Robert Harder
+ * @author rob@iharder.net
+ * @version 2.0
+ */
+public class Base64
+{
+    
+/* ********  P U B L I C   F I E L D S  ******** */   
+    
+    
+    /** No options specified. Value is zero. */
+    public final static int NO_OPTIONS = 0;
+    
+    /** Specify encoding. */
+    public final static int ENCODE = 1;
+    
+    
+    /** Specify decoding. */
+    public final static int DECODE = 0;
+    
+    
+    /** Specify that data should be gzip-compressed. */
+    public final static int GZIP = 2;
+    
+    
+    /** Don't break lines when encoding (violates strict Base64 specification) */
+    public final static int DONT_BREAK_LINES = 8;
+    
+    
+/* ********  P R I V A T E   F I E L D S  ******** */  
+    
+    
+    /** Maximum line length (76) of Base64 output. */
+    private final static int MAX_LINE_LENGTH = 76;
+    
+    
+    /** The equals sign (=) as a byte. */
+    private final static byte EQUALS_SIGN = (byte)'=';
+    
+    
+    /** The new line character (\n) as a byte. */
+    private final static byte NEW_LINE = (byte)'\n';
+    
+    
+    /** The 64 valid Base64 values. */
+    private final static byte[] ALPHABET =
+    {
+        (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+        (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
+        (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 
+        (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
+        (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
+        (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
+        (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 
+        (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
+        (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', 
+        (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/'
+    };
+    
+    /** 
+     * Translates a Base64 value to either its 6-bit reconstruction value
+     * or a negative number indicating some other meaning.
+     **/
+    private final static byte[] DECODABET =
+    {   
+        -9,-9,-9,-9,-9,-9,-9,-9,-9,                 // Decimal  0 -  8
+        -5,-5,                                      // Whitespace: Tab and Linefeed
+        -9,-9,                                      // Decimal 11 - 12
+        -5,                                         // Whitespace: Carriage Return
+        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 14 - 26
+        -9,-9,-9,-9,-9,                             // Decimal 27 - 31
+        -5,                                         // Whitespace: Space
+        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,              // Decimal 33 - 42
+        62,                                         // Plus sign at decimal 43
+        -9,-9,-9,                                   // Decimal 44 - 46
+        63,                                         // Slash at decimal 47
+        52,53,54,55,56,57,58,59,60,61,              // Numbers zero through nine
+        -9,-9,-9,                                   // Decimal 58 - 60
+        -1,                                         // Equals sign at decimal 61
+        -9,-9,-9,                                      // Decimal 62 - 64
+        0,1,2,3,4,5,6,7,8,9,10,11,12,13,            // Letters 'A' through 'N'
+        14,15,16,17,18,19,20,21,22,23,24,25,        // Letters 'O' through 'Z'
+        -9,-9,-9,-9,-9,-9,                          // Decimal 91 - 96
+        26,27,28,29,30,31,32,33,34,35,36,37,38,     // Letters 'a' through 'm'
+        39,40,41,42,43,44,45,46,47,48,49,50,51,     // Letters 'n' through 'z'
+        -9,-9,-9,-9                                 // Decimal 123 - 126
+        /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 127 - 139
+        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 140 - 152
+        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 153 - 165
+        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 166 - 178
+        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 179 - 191
+        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 192 - 204
+        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 205 - 217
+        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 218 - 230
+        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,     // Decimal 231 - 243
+        -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9         // Decimal 244 - 255 */
+    };
+    
+    private final static byte BAD_ENCODING    = -9; // Indicates error in encoding
+    private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding
+    private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding
+
+    
+    /** Defeats instantiation. */
+    private Base64(){}
+    
+    
+    
+/* ********  E N C O D I N G   M E T H O D S  ******** */    
+    
+    
+    /**
+     * Encodes the first three bytes of array <var>threeBytes</var>
+     * and returns a four-byte array in Base64 notation.
+     *
+     * @param threeBytes the array to convert
+     * @return four byte array in Base64 notation.
+     * @since 1.3
+     */
+    private static byte[] encode3to4( byte[] threeBytes )
+    {   
+        return encode3to4( threeBytes, 3 );
+    }   // end encodeToBytes
+    
+    
+    
+    /**
+     * Encodes up to the first three bytes of array <var>threeBytes</var>
+     * and returns a four-byte array in Base64 notation.
+     * The actual number of significant bytes in your array is
+     * given by <var>numSigBytes</var>.
+     * The array <var>threeBytes</var> needs only be as big as
+     * <var>numSigBytes</var>.
+     *
+     * @param threeBytes the array to convert
+     * @param numSigBytes the number of significant bytes in your array
+     * @return four byte array in Base64 notation.
+     * @since 1.3
+     */
+    private static byte[] encode3to4( byte[] threeBytes, int numSigBytes )
+    {  
+        byte[] dest = new byte[4];
+        encode3to4( threeBytes, 0, numSigBytes, dest, 0 );
+        return dest;
+    }
+    
+     /**
+     * Encodes up to the first three bytes of array <var>threeBytes</var>
+     * and returns a four-byte array in Base64 notation.
+     * The actual number of significant bytes in your array is
+     * given by <var>numSigBytes</var>.
+     * The array <var>threeBytes</var> needs only be as big as
+     * <var>numSigBytes</var>.
+     * Code can reuse a byte array by passing a four-byte array as <var>b4</var>.
+     *
+     * @param b4 A reusable byte array to reduce array instantiation
+     * @param threeBytes the array to convert
+     * @param numSigBytes the number of significant bytes in your array
+     * @return four byte array in Base64 notation.
+     * @since 1.5.1
+     */
+    private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes )
+    {
+        encode3to4( threeBytes, 0, numSigBytes, b4, 0 );
+        return b4;
+    }   // end encode3to4
+
+    
+    /**
+     * Encodes up to three bytes of the array <var>source</var>
+     * and writes the resulting four Base64 bytes to <var>destination</var>.
+     * The source and destination arrays can be manipulated
+     * anywhere along their length by specifying 
+     * <var>srcOffset</var> and <var>destOffset</var>.
+     * This method does not check to make sure your arrays
+     * are large enough to accomodate <var>srcOffset</var> + 3 for
+     * the <var>source</var> array or <var>destOffset</var> + 4 for
+     * the <var>destination</var> array.
+     * The actual number of significant bytes in your array is
+     * given by <var>numSigBytes</var>.
+     *
+     * @param source the array to convert
+     * @param srcOffset the index where conversion begins
+     * @param numSigBytes the number of significant bytes in your array
+     * @param destination the array to hold the conversion
+     * @param destOffset the index where output will be put
+     * @return the <var>destination</var> array
+     * @since 1.3
+     */
+    private static byte[] encode3to4( 
+     byte[] source, int srcOffset, int numSigBytes,
+     byte[] destination, int destOffset )
+    {
+        //           1         2         3  
+        // 01234567890123456789012345678901 Bit position
+        // --------000000001111111122222222 Array position from threeBytes
+        // --------|    ||    ||    ||    | Six bit groups to index ALPHABET
+        //          >>18  >>12  >> 6  >> 0  Right shift necessary
+        //                0x3f  0x3f  0x3f  Additional AND
+        
+        // Create buffer with zero-padding if there are only one or two
+        // significant bytes passed in the array.
+        // We have to shift left 24 in order to flush out the 1's that appear
+        // when Java treats a value as negative that is cast from a byte to an int.
+        int inBuff =   ( numSigBytes > 0 ? ((source[ srcOffset     ] << 24) >>>  8) : 0 )
+                     | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 )
+                     | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 );
+
+        switch( numSigBytes )
+        {
+            case 3:
+                destination[ destOffset     ] = ALPHABET[ (inBuff >>> 18)        ];
+                destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
+                destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>>  6) & 0x3f ];
+                destination[ destOffset + 3 ] = ALPHABET[ (inBuff       ) & 0x3f ];
+                return destination;
+                
+            case 2:
+                destination[ destOffset     ] = ALPHABET[ (inBuff >>> 18)        ];
+                destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
+                destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>>  6) & 0x3f ];
+                destination[ destOffset + 3 ] = EQUALS_SIGN;
+                return destination;
+                
+            case 1:
+                destination[ destOffset     ] = ALPHABET[ (inBuff >>> 18)        ];
+                destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ];
+                destination[ destOffset + 2 ] = EQUALS_SIGN;
+                destination[ destOffset + 3 ] = EQUALS_SIGN;
+                return destination;
+                
+            default:
+                return destination;
+        }   // end switch
+    }   // end encode3to4
+    
+    
+    
+    /**
+     * Serializes an object and returns the Base64-encoded
+     * version of that serialized object. If the object
+     * cannot be serialized or there is another error,
+     * the method will return <tt>null</tt>.
+     * The object is not GZip-compressed before being encoded.
+     *
+     * @param serializableObject The object to encode
+     * @return The Base64-encoded object
+     * @since 1.4
+     */
+    public static String encodeObject( java.io.Serializable serializableObject )
+    {
+        return encodeObject( serializableObject, NO_OPTIONS );
+    }   // end encodeObject
+    
+
+
+    /**
+     * Serializes an object and returns the Base64-encoded
+     * version of that serialized object. If the object
+     * cannot be serialized or there is another error,
+     * the method will return <tt>null</tt>.
+     * <p>
+     * Valid options:<pre>
+     *   GZIP: gzip-compresses object before encoding it.
+     *   DONT_BREAK_LINES: don't break lines at 76 characters
+     *     <i>Note: Technically, this makes your encoding non-compliant.</i>
+     * </pre>
+     * <p>
+     * Example: <code>encodeObject( myObj, Base64.GZIP )</code> or
+     * <p>
+     * Example: <code>encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
+     *
+     * @param serializableObject The object to encode
+     * @param options Specified options
+     * @return The Base64-encoded object
+     * @see Base64#GZIP
+     * @see Base64#DONT_BREAK_LINES
+     * @since 2.0
+     */
+    public static String encodeObject( java.io.Serializable serializableObject, int options )
+    {
+        // Streams
+        java.io.ByteArrayOutputStream  baos  = null; 
+        java.io.OutputStream           b64os = null; 
+        java.io.ObjectOutputStream     oos   = null; 
+        java.util.zip.GZIPOutputStream gzos  = null;
+        
+        // Isolate options
+        int gzip           = (options & GZIP);
+        int dontBreakLines = (options & DONT_BREAK_LINES);
+        
+        try
+        {
+            // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream
+            baos  = new java.io.ByteArrayOutputStream();
+            b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines );
+    
+            // GZip?
+            if( gzip == GZIP )
+            {
+                gzos = new java.util.zip.GZIPOutputStream( b64os );
+                oos  = new java.io.ObjectOutputStream( gzos );
+            }   // end if: gzip
+            else
+                oos   = new java.io.ObjectOutputStream( b64os );
+            
+            oos.writeObject( serializableObject );
+        }   // end try
+        catch( java.io.IOException e )
+        {
+            e.printStackTrace();
+            return null;
+        }   // end catch
+        finally
+        {
+            try{ oos.close();   } catch( Exception e ){}
+            try{ gzos.close();  } catch( Exception e ){}
+            try{ b64os.close(); } catch( Exception e ){}
+            try{ baos.close();  } catch( Exception e ){}
+        }   // end finally
+        
+        return new String( baos.toByteArray() );
+    }   // end encode
+    
+    
+
+    /**
+     * Encodes a byte array into Base64 notation.
+     * Does not GZip-compress data.
+     *
+     * @param source The data to convert
+     * @since 1.4
+     */
+    public static String encodeBytes( byte[] source )
+    {
+        return encodeBytes( source, 0, source.length, NO_OPTIONS );
+    }   // end encodeBytes
+    
+
+
+    /**
+     * Encodes a byte array into Base64 notation.
+     * <p>
+     * Valid options:<pre>
+     *   GZIP: gzip-compresses object before encoding it.
+     *   DONT_BREAK_LINES: don't break lines at 76 characters
+     *     <i>Note: Technically, this makes your encoding non-compliant.</i>
+     * </pre>
+     * <p>
+     * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
+     * <p>
+     * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
+     *
+     *
+     * @param source The data to convert
+     * @param options Specified options
+     * @see Base64#GZIP
+     * @see Base64#DONT_BREAK_LINES
+     * @since 2.0
+     */
+    public static String encodeBytes( byte[] source, int options )
+    {   
+        return encodeBytes( source, 0, source.length, options );
+    }   // end encodeBytes
+    
+    
+    /**
+     * Encodes a byte array into Base64 notation.
+     * Does not GZip-compress data.
+     *
+     * @param source The data to convert
+     * @param off Offset in array where conversion should begin
+     * @param len Length of data to convert
+     * @since 1.4
+     */
+    public static String encodeBytes( byte[] source, int off, int len )
+    {
+        return encodeBytes( source, off, len, NO_OPTIONS );
+    }   // end encodeBytes
+    
+    
+
+    /**
+     * Encodes a byte array into Base64 notation.
+     * <p>
+     * Valid options:<pre>
+     *   GZIP: gzip-compresses object before encoding it.
+     *   DONT_BREAK_LINES: don't break lines at 76 characters
+     *     <i>Note: Technically, this makes your encoding non-compliant.</i>
+     * </pre>
+     * <p>
+     * Example: <code>encodeBytes( myData, Base64.GZIP )</code> or
+     * <p>
+     * Example: <code>encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES )</code>
+     *
+     *
+     * @param source The data to convert
+     * @param off Offset in array where conversion should begin
+     * @param len Length of data to convert
+     * @param options Specified options
+     * @see Base64#GZIP
+     * @see Base64#DONT_BREAK_LINES
+     * @since 2.0
+     */
+    public static String encodeBytes( byte[] source, int off, int len, int options )
+    {
+        // Isolate options
+        int dontBreakLines = ( options & DONT_BREAK_LINES );
+        int gzip           = ( options & GZIP   );
+        
+        // Compress?
+        if( gzip == GZIP )
+        {
+            java.io.ByteArrayOutputStream  baos  = null;
+            java.util.zip.GZIPOutputStream gzos  = null;
+            Base64.OutputStream            b64os = null;
+            
+    
+            try
+            {
+                // GZip -> Base64 -> ByteArray
+                baos = new java.io.ByteArrayOutputStream();
+                b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines );
+                gzos  = new java.util.zip.GZIPOutputStream( b64os ); 
+            
+                gzos.write( source, off, len );
+                gzos.close();
+            }   // end try
+            catch( java.io.IOException e )
+            {
+                e.printStackTrace();
+                return null;
+            }   // end catch
+            finally
+            {
+                try{ gzos.close();  } catch( Exception e ){}
+                try{ b64os.close(); } catch( Exception e ){}
+                try{ baos.close();  } catch( Exception e ){}
+            }   // end finally
+
+            return new String( baos.toByteArray() );
+        }   // end if: compress
+        
+        // Else, don't compress. Better not to use streams at all then.
+        else
+        {
+            // Convert option to boolean in way that code likes it.
+            boolean breakLines = dontBreakLines == 0;
+            
+            int    len43   = len * 4 / 3;
+            byte[] outBuff = new byte[   ( len43 )                      // Main 4:3
+                                       + ( (len % 3) > 0 ? 4 : 0 )      // Account for padding
+                                       + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines      
+            int d = 0;
+            int e = 0;
+            int len2 = len - 2;
+            int lineLength = 0;
+            for( ; d < len2; d+=3, e+=4 )
+            {
+                encode3to4( source, d+off, 3, outBuff, e );
+
+                lineLength += 4;
+                if( breakLines && lineLength == MAX_LINE_LENGTH )
+                {   
+                    outBuff[e+4] = NEW_LINE;
+                    e++;
+                    lineLength = 0;
+                }   // end if: end of line
+            }   // en dfor: each piece of array
+
+            if( d < len )
+            {
+                encode3to4( source, d+off, len - d, outBuff, e );
+                e += 4;
+            }   // end if: some padding needed
+
+            return new String( outBuff, 0, e );
+        }   // end else: don't compress
+        
+    }   // end encodeBytes
+    
+
+    
+    
+    
+/* ********  D E C O D I N G   M E T H O D S  ******** */
+    
+    
+    /**
+     * Decodes the first four bytes of array <var>fourBytes</var>
+     * and returns an array up to three bytes long with the
+     * decoded values.
+     *
+     * @param fourBytes the array with Base64 content
+     * @return array with decoded values
+     * @since 1.3
+     */
+    private static byte[] decode4to3( byte[] fourBytes )
+    {
+        byte[] outBuff1 = new byte[3];
+        int    count    = decode4to3( fourBytes, 0, outBuff1, 0 );
+        byte[] outBuff2 = new byte[ count ];
+        
+        for( int i = 0; i < count; i++ )
+            outBuff2[i] = outBuff1[i];
+        
+        return outBuff2;
+    }
+    
+    
+    
+    
+    /**
+     * Decodes four bytes from array <var>source</var>
+     * and writes the resulting bytes (up to three of them)
+     * to <var>destination</var>.
+     * The source and destination arrays can be manipulated
+     * anywhere along their length by specifying 
+     * <var>srcOffset</var> and <var>destOffset</var>.
+     * This method does not check to make sure your arrays
+     * are large enough to accomodate <var>srcOffset</var> + 4 for
+     * the <var>source</var> array or <var>destOffset</var> + 3 for
+     * the <var>destination</var> array.
+     * This method returns the actual number of bytes that 
+     * were converted from the Base64 encoding.
+     * 
+     *
+     * @param source the array to convert
+     * @param srcOffset the index where conversion begins
+     * @param destination the array to hold the conversion
+     * @param destOffset the index where output will be put
+     * @return the number of decoded bytes converted
+     * @since 1.3
+     */
+    private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset )
+    {
+        // Example: Dk==
+        if( source[ srcOffset + 2] == EQUALS_SIGN )
+        {
+            // Two ways to do the same thing. Don't know which way I like best.
+            //int outBuff =   ( ( DECODABET[ source[ srcOffset    ] ] << 24 ) >>>  6 )
+            //              | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 );
+            int outBuff =   ( ( DECODABET[ source[ srcOffset    ] ] & 0xFF ) << 18 )
+                          | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 );
+            
+            destination[ destOffset ] = (byte)( outBuff >>> 16 );
+            return 1;
+        }
+        
+        // Example: DkL=
+        else if( source[ srcOffset + 3 ] == EQUALS_SIGN )
+        {
+            // Two ways to do the same thing. Don't know which way I like best.
+            //int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] << 24 ) >>>  6 )
+            //              | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+            //              | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 );
+            int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] & 0xFF ) << 18 )
+                          | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
+                          | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) <<  6 );
+            
+            destination[ destOffset     ] = (byte)( outBuff >>> 16 );
+            destination[ destOffset + 1 ] = (byte)( outBuff >>>  8 );
+            return 2;
+        }
+        
+        // Example: DkLE
+        else
+        {
+            try{
+            // Two ways to do the same thing. Don't know which way I like best.
+            //int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] << 24 ) >>>  6 )
+            //              | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 )
+            //              | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 )
+            //              | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 );
+            int outBuff =   ( ( DECODABET[ source[ srcOffset     ] ] & 0xFF ) << 18 )
+                          | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 )
+                          | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) <<  6)
+                          | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF )      );
+
+            
+            destination[ destOffset     ] = (byte)( outBuff >> 16 );
+            destination[ destOffset + 1 ] = (byte)( outBuff >>  8 );
+            destination[ destOffset + 2 ] = (byte)( outBuff       );
+
+            return 3;
+            }catch( Exception e){
+                System.out.println(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset     ] ]  ) );
+                System.out.println(""+source[srcOffset+1]+  ": " + ( DECODABET[ source[ srcOffset + 1 ] ]  ) );
+                System.out.println(""+source[srcOffset+2]+  ": " + ( DECODABET[ source[ srcOffset + 2 ] ]  ) );
+                System.out.println(""+source[srcOffset+3]+  ": " + ( DECODABET[ source[ srcOffset + 3 ] ]  ) );
+                return -1;
+            }   //e nd catch
+        }
+    }   // end decodeToBytes
+    
+    
+    
+    
+    /**
+     * Very low-level access to decoding ASCII characters in
+     * the form of a byte array. Does not support automatically
+     * gunzipping or any other "fancy" features.
+     *
+     * @param source The Base64 encoded data
+     * @param off    The offset of where to begin decoding
+     * @param len    The length of characters to decode
+     * @return decoded data
+     * @since 1.3
+     */
+    public static byte[] decode( byte[] source, int off, int len )
+    {
+        int    len34   = len * 3 / 4;
+        byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output
+        int    outBuffPosn = 0;
+        
+        byte[] b4        = new byte[4];
+        int    b4Posn    = 0;
+        int    i         = 0;
+        byte   sbiCrop   = 0;
+        byte   sbiDecode = 0;
+        for( i = off; i < off+len; i++ )
+        {
+            sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits
+            sbiDecode = DECODABET[ sbiCrop ];
+            
+            if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better
+            {
+                if( sbiDecode >= EQUALS_SIGN_ENC )
+                {
+                    b4[ b4Posn++ ] = sbiCrop;
+                    if( b4Posn > 3 )
+                    {
+                        outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn );
+                        b4Posn = 0;
+                        
+                        // If that was the equals sign, break out of 'for' loop
+                        if( sbiCrop == EQUALS_SIGN )
+                            break;
+                    }   // end if: quartet built
+                    
+                }   // end if: equals sign or better
+                
+            }   // end if: white space, equals sign or better
+            else
+            {
+                System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" );
+                return null;
+            }   // end else: 
+        }   // each input character
+                                   
+        byte[] out = new byte[ outBuffPosn ];
+        System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); 
+        return out;
+    }   // end decode
+    
+    
+    
+    
+    /**
+     * Decodes data from Base64 notation, automatically
+     * detecting gzip-compressed data and decompressing it.
+     *
+     * @param s the string to decode
+     * @return the decoded data
+     * @since 1.4
+     */
+    public static byte[] decode( String s )
+    {   
+        byte[] bytes = s.getBytes();
+        bytes = decode( bytes, 0, bytes.length );
+        
+        // Check to see if it's gzip-compressed
+        // GZIP Magic Two-Byte Number: 0x8b1f (35615)
+        if( bytes.length >= 2 )
+        {
+            
+            int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);       
+            if(
+            bytes != null &&      // In case decoding returned null
+            bytes.length >= 4  && // Don't want to get ArrayIndexOutOfBounds exception
+            java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) 
+            {
+                java.io.ByteArrayInputStream  bais = null;
+                java.util.zip.GZIPInputStream gzis = null;
+                java.io.ByteArrayOutputStream baos = null;
+                byte[] buffer = new byte[2048];
+                int    length = 0;
+
+                try
+                {
+                    baos = new java.io.ByteArrayOutputStream();
+                    bais = new java.io.ByteArrayInputStream( bytes );
+                    gzis = new java.util.zip.GZIPInputStream( bais );
+
+                    while( ( length = gzis.read( buffer ) ) >= 0 )
+                    {
+                        baos.write(buffer,0,length);
+                    }   // end while: reading input
+
+                    // No error? Get new bytes.
+                    bytes = baos.toByteArray();
+
+                }   // end try
+                catch( java.io.IOException e )
+                {
+                    // Just return originally-decoded bytes
+                }   // end catch
+                finally
+                {
+                    try{ baos.close(); } catch( Exception e ){}
+                    try{ gzis.close(); } catch( Exception e ){}
+                    try{ bais.close(); } catch( Exception e ){}
+                }   // end finally
+
+            }   // end if: gzipped
+        }   // end if: bytes.length >= 2
+        
+        return bytes;
+    }   // end decode
+
+
+    
+
+    /**
+     * Attempts to decode Base64 data and deserialize a Java
+     * Object within. Returns <tt>null</tt> if there was an error.
+     *
+     * @param encodedObject The Base64 data to decode
+     * @return The decoded and deserialized object
+     * @since 1.5
+     */
+    public static Object decodeToObject( String encodedObject )
+    {
+        // Decode and gunzip if necessary
+        byte[] objBytes = decode( encodedObject );
+        
+        java.io.ByteArrayInputStream  bais = null;
+        java.io.ObjectInputStream     ois  = null;
+        Object obj = null;
+        
+        try
+        {
+            bais = new java.io.ByteArrayInputStream( objBytes );
+            ois  = new java.io.ObjectInputStream( bais );
+        
+            obj = ois.readObject();
+        }   // end try
+        catch( java.io.IOException e )
+        {
+            e.printStackTrace();
+            obj = null;
+        }   // end catch
+        catch( java.lang.ClassNotFoundException e )
+        {
+            e.printStackTrace();
+            obj = null;
+        }   // end catch
+        finally
+        {
+            try{ bais.close(); } catch( Exception e ){}
+            try{ ois.close();  } catch( Exception e ){}
+        }   // end finally
+        
+        return obj;
+    }   // end decodeObject
+    
+    
+    /* ********  I N N E R   C L A S S   I N P U T S T R E A M  ******** */
+    
+    
+    
+    /**
+     * It will read data from another
+     * {@link java.io.InputStream}, given in the constructor,
+     * and encode/decode to/from Base64 notation on the fly.
+     *
+     * @see Base64
+     * @see java.io.FilterInputStream
+     * @since 1.3
+     */
+    public static class InputStream extends java.io.FilterInputStream
+    {
+        private int     options;        // Options specified
+        private boolean encode;         // Encoding or decoding
+        private int     position;       // Current position in the buffer
+        private byte[]  buffer;         // Small buffer holding converted data
+        private int     bufferLength;   // Length of buffer (3 or 4)
+        private int     numSigBytes;    // Number of meaningful bytes in the buffer
+        private int     lineLength;
+        private boolean breakLines;     // Break lines at less than 80 characters
+        
+        
+        /**
+         * Constructs a InputStream in DECODE mode.
+         *
+         * @param in the {@link java.io.InputStream} from which to read data.
+         * @since 1.3
+         */
+        public InputStream( java.io.InputStream in )
+        {   
+            this( in, DECODE );
+        }   // end constructor
+        
+        
+        /**
+         * Constructs a InputStream in
+         * either ENCODE or DECODE mode.
+         * <p>
+         * Valid options:<pre>
+         *   ENCODE or DECODE: Encode or Decode as data is read.
+         *   DONT_BREAK_LINES: don't break lines at 76 characters
+         *     (only meaningful when encoding)
+         *     <i>Note: Technically, this makes your encoding non-compliant.</i>
+         * </pre>
+         * <p>
+         * Example: <code>new Base64.InputStream( in, Base64.DECODE )</code>
+         *
+         *
+         * @param in the {@link java.io.InputStream} from which to read data.
+         * @param options Specified options
+         * @see Base64#ENCODE
+         * @see Base64#DECODE
+         * @see Base64#DONT_BREAK_LINES
+         * @since 2.0
+         */
+        public InputStream( java.io.InputStream in, int options )
+        {   
+            super( in );
+            this.options      = options;
+            this.breakLines   = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
+            this.encode       = (options & ENCODE) == ENCODE;
+            this.breakLines = breakLines;
+            this.encode = encode;
+            this.bufferLength = encode ? 4 : 3;
+            this.buffer   = new byte[ bufferLength ];
+            this.position = -1;
+            this.lineLength = 0;
+        }   // end constructor
+        
+        /**
+         * Reads enough of the input stream to convert
+         * to/from Base64 and returns the next byte.
+         *
+         * @return next byte
+         * @since 1.3
+         */
+        public int read() throws java.io.IOException 
+        { 
+            // Do we need to get data?
+            if( position < 0 )
+            {
+                if( encode )
+                {
+                    byte[] b3 = new byte[3];
+                    int numBinaryBytes = 0;
+                    for( int i = 0; i < 3; i++ )
+                    {
+                        try
+                        { 
+                            int b = in.read();
+                            
+                            // If end of stream, b is -1.
+                            if( b >= 0 )
+                            {
+                                b3[i] = (byte)b;
+                                numBinaryBytes++;
+                            }   // end if: not end of stream
+                            
+                        }   // end try: read
+                        catch( java.io.IOException e )
+                        {   
+                            // Only a problem if we got no data at all.
+                            if( i == 0 )
+                                throw e;
+                            
+                        }   // end catch
+                    }   // end for: each needed input byte
+                    
+                    if( numBinaryBytes > 0 )
+                    {
+                        encode3to4( b3, 0, numBinaryBytes, buffer, 0 );
+                        position = 0;
+                        numSigBytes = 4;
+                    }   // end if: got data
+                    else
+                    {
+                        return -1;
+                    }   // end else
+                }   // end if: encoding
+                
+                // Else decoding
+                else
+                {
+                    byte[] b4 = new byte[4];
+                    int i = 0;
+                    for( i = 0; i < 4; i++ )
+                    {
+                        // Read four "meaningful" bytes:
+                        int b = 0;
+                        do{ b = in.read(); }
+                        while( b >= 0 && DECODABET[ b & 0x7f ] <= WHITE_SPACE_ENC );
+                        
+                        if( b < 0 )
+                            break; // Reads a -1 if end of stream
+                        
+                        b4[i] = (byte)b;
+                    }   // end for: each needed input byte
+                    
+                    if( i == 4 )
+                    {
+                        numSigBytes = decode4to3( b4, 0, buffer, 0 );
+                        position = 0;
+                    }   // end if: got four characters
+                    else if( i == 0 ){
+                        return -1;
+                    }   // end else if: also padded correctly
+                    else
+                    {
+                        // Must have broken out from above.
+                        throw new java.io.IOException( "Improperly padded Base64 input." );
+                    }   // end 
+                    
+                }   // end else: decode
+            }   // end else: get data
+            
+            // Got data?
+            if( position >= 0 )
+            {
+                // End of relevant data?
+                if( /*!encode &&*/ position >= numSigBytes )
+                    return -1;
+                
+                if( encode && breakLines && lineLength >= MAX_LINE_LENGTH )
+                {
+                    lineLength = 0;
+                    return '\n';
+                }   // end if
+                else
+                {
+                    lineLength++;   // This isn't important when decoding
+                                    // but throwing an extra "if" seems
+                                    // just as wasteful.
+                    
+                    int b = buffer[ position++ ];
+
+                    if( position >= bufferLength )
+                        position = -1;
+
+                    return b & 0xFF; // This is how you "cast" a byte that's
+                                     // intended to be unsigned.
+                }   // end else
+            }   // end if: position >= 0
+            
+            // Else error
+            else
+            {   
+                // When JDK1.4 is more accepted, use an assertion here.
+                throw new java.io.IOException( "Error in Base64 code reading stream." );
+            }   // end else
+        }   // end read
+        
+        
+        /**
+         * Calls {@link #read} repeatedly until the end of stream
+         * is reached or <var>len</var> bytes are read.
+         * Returns number of bytes read into array or -1 if
+         * end of stream is encountered.
+         *
+         * @param dest array to hold values
+         * @param off offset for array
+         * @param len max number of bytes to read into array
+         * @return bytes read into array or -1 if end of stream is encountered.
+         * @since 1.3
+         */
+        public int read( byte[] dest, int off, int len ) throws java.io.IOException
+        {
+            int i;
+            int b;
+            for( i = 0; i < len; i++ )
+            {
+                b = read();
+                
+                //if( b < 0 && i == 0 )
+                //    return -1;
+                
+                if( b >= 0 )
+                    dest[off + i] = (byte)b;
+                else if( i == 0 )
+                    return -1;
+                else
+                    break; // Out of 'for' loop
+            }   // end for: each byte read
+            return i;
+        }   // end read
+        
+    }   // end inner class InputStream
+    
+    
+    
+    
+    
+    
+    /* ********  I N N E R   C L A S S   O U T P U T S T R E A M  ******** */
+    
+    
+    
+    /**
+     * A OutputStream will write data to another
+     * {@link java.io.OutputStream}, given in the constructor,
+     * and encode/decode to/from Base64 notation on the fly.
+     *
+     * @see Base64
+     * @see java.io.FilterOutputStream
+     * @since 1.3
+     */
+    public static class OutputStream extends java.io.FilterOutputStream
+    {
+        private int     options;
+        private boolean encode;
+        private int     position;
+        private byte[]  buffer;
+        private int     bufferLength;
+        private int     lineLength;
+        private boolean breakLines;
+        private byte[]  b4; // Scratch used in a few places
+        private boolean suspendEncoding;
+        
+        /**
+         * Constructs a OutputStream in ENCODE mode.
+         *
+         * @param out the {@link java.io.OutputStream} to which data will be written.
+         * @since 1.3
+         */
+        public OutputStream( java.io.OutputStream out )
+        {   
+            this( out, ENCODE );
+        }   // end constructor
+        
+        
+        /**
+         * Constructs a OutputStream in
+         * either ENCODE or DECODE mode.
+         * <p>
+         * Valid options:<pre>
+         *   ENCODE or DECODE: Encode or Decode as data is read.
+         *   DONT_BREAK_LINES: don't break lines at 76 characters
+         *     (only meaningful when encoding)
+         *     <i>Note: Technically, this makes your encoding non-compliant.</i>
+         * </pre>
+         * <p>
+         * Example: <code>new Base64.OutputStream( out, Base64.ENCODE )</code>
+         *
+         * @param out the {@link java.io.OutputStream} to which data will be written.
+         * @param options Specified options.
+         * @see Base64#ENCODE
+         * @see Base64#DECODE
+         * @see Base64#DONT_BREAK_LINES
+         * @since 1.3
+         */
+        public OutputStream( java.io.OutputStream out, int options )
+        {   
+            super( out );
+            this.options      = options;
+            this.breakLines   = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES;
+            this.encode       = (options & ENCODE) == ENCODE;
+            this.bufferLength = encode ? 3 : 4;
+            this.buffer       = new byte[ bufferLength ];
+            this.position     = 0;
+            this.lineLength   = 0;
+            this.suspendEncoding = false;
+            this.b4           = new byte[4];
+        }   // end constructor
+        
+        
+        /**
+         * Writes the byte to the output stream after
+         * converting to/from Base64 notation.
+         * When encoding, bytes are buffered three
+         * at a time before the output stream actually
+         * gets a write() call.
+         * When decoding, bytes are buffered four
+         * at a time.
+         *
+         * @param theByte the byte to write
+         * @since 1.3
+         */
+        public void write(int theByte) throws java.io.IOException
+        {
+            // Encoding suspended?
+            if( suspendEncoding )
+            {
+                super.out.write( theByte );
+                return;
+            }   // end if: supsended
+            
+            // Encode?
+            if( encode )
+            {
+                buffer[ position++ ] = (byte)theByte;
+                if( position >= bufferLength )  // Enough to encode.
+                {
+                    out.write( encode3to4( b4, buffer, bufferLength ) );
+
+                    lineLength += 4;
+                    if( breakLines && lineLength >= MAX_LINE_LENGTH )
+                    {
+                        out.write( NEW_LINE );
+                        lineLength = 0;
+                    }   // end if: end of line
+
+                    position = 0;
+                }   // end if: enough to output
+            }   // end if: encoding
+
+            // Else, Decoding
+            else
+            {
+                // Meaningful Base64 character?
+                if( DECODABET[ theByte & 0x7f ] > WHITE_SPACE_ENC )
+                {
+                    buffer[ position++ ] = (byte)theByte;
+                    if( position >= bufferLength )  // Enough to output.
+                    {
+                        int len = Base64.decode4to3( buffer, 0, b4, 0 );
+                        out.write( b4, 0, len );
+                        //out.write( Base64.decode4to3( buffer ) );
+                        position = 0;
+                    }   // end if: enough to output
+                }   // end if: meaningful base64 character
+                else if( DECODABET[ theByte & 0x7f ] != WHITE_SPACE_ENC )
+                {
+                    throw new java.io.IOException( "Invalid character in Base64 data." );
+                }   // end else: not white space either
+            }   // end else: decoding
+        }   // end write
+        
+        
+        
+        /**
+         * Calls {@link #write} repeatedly until <var>len</var> 
+         * bytes are written.
+         *
+         * @param theBytes array from which to read bytes
+         * @param off offset for array
+         * @param len max number of bytes to read into array
+         * @since 1.3
+         */
+        public void write( byte[] theBytes, int off, int len ) throws java.io.IOException
+        {
+            // Encoding suspended?
+            if( suspendEncoding )
+            {
+                super.out.write( theBytes, off, len );
+                return;
+            }   // end if: supsended
+            
+            for( int i = 0; i < len; i++ )
+            {
+                write( theBytes[ off + i ] );
+            }   // end for: each byte written
+            
+        }   // end write
+        
+        
+        
+        /**
+         * Method added by PHIL. [Thanks, PHIL. -Rob]
+         * This pads the buffer without closing the stream.
+         */
+        public void flushBase64() throws java.io.IOException 
+        {
+            if( position > 0 )
+            {
+                if( encode )
+                {
+                    out.write( encode3to4( b4, buffer, position ) );
+                    position = 0;
+                }   // end if: encoding
+                else
+                {
+                    throw new java.io.IOException( "Base64 input not properly padded." );
+                }   // end else: decoding
+            }   // end if: buffer partially full
+
+        }   // end flush
+
+        
+        /** 
+         * Flushes and closes (I think, in the superclass) the stream. 
+         *
+         * @since 1.3
+         */
+        public void close() throws java.io.IOException
+        {
+            // 1. Ensure that pending characters are written
+            flushBase64();
+
+            // 2. Actually close the stream
+            // Base class both flushes and closes.
+            super.close();
+            
+            buffer = null;
+            out    = null;
+        }   // end close
+        
+        
+        
+        /**
+         * Suspends encoding of the stream.
+         * May be helpful if you need to embed a piece of
+         * base640-encoded data in a stream.
+         *
+         * @since 1.5.1
+         */
+        public void suspendEncoding() throws java.io.IOException 
+        {
+            flushBase64();
+            this.suspendEncoding = true;
+        }   // end suspendEncoding
+        
+        
+        /**
+         * Resumes encoding of the stream.
+         * May be helpful if you need to embed a piece of
+         * base640-encoded data in a stream.
+         *
+         * @since 1.5.1
+         */
+        public void resumeEncoding()
+        {
+            this.suspendEncoding = false;
+        }   // end resumeEncoding
+        
+        
+        
+    }   // end inner class OutputStream
+    
+    
+}   // end class Base64
Index: /gs3-extensions/vishnu/trunk/src/vishnu/util/Hgram.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/util/Hgram.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/util/Hgram.java	(revision 21054)
@@ -0,0 +1,378 @@
+package vishnu.util;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.imageio.*;
+import javax.imageio.stream.*;
+import java.io.*;
+import java.util.*;
+import java.awt.image.*;
+
+public class Hgram extends JFrame
+{
+    public static int TOP = 0;
+    public static int LARGEST = 1;
+
+    // data is an array with each entry being the frequency of a bin
+    public static int BIN_DATA = 0;
+
+    // data comes in raw format and needs to be allocated to bins
+    public static int RAW_DATA = 1;
+    
+    double[] data;
+    
+    // default size of frame
+    int width = 400;
+    int height = 400;
+
+    // axis limits of histogram
+    double xmin;
+    double xmax;
+
+    // number of bins, bin frequencies and width
+    int noBins;
+    double bins[];
+    double binWidth;
+    
+    BufferedImage offScreen=null;
+    Dimension offScreenSize;
+    Graphics2D offGraphics;
+    
+    String label = null;
+    
+    double median = -1;
+    int med_bin = -1;
+    int mode = -1;
+    
+    
+    public Hgram()
+    {
+	init();
+    }
+
+    
+    public void setData(double[] d, int m){
+	data = d; 
+	mode = m;
+	xmin = getMinimum(data);
+	xmax = getMaximum(data);
+	createBins(data,noBins,xmin, xmax);
+    }
+
+      
+    public void setBins(double[] b){ bins = b; }
+
+
+    public void setBinNumber(int i){ noBins = i; }
+
+    
+    public void setMaximum(double i){ xmax = i; }
+    
+    
+    public void setMinimum(double i){ xmin = i; }
+
+
+    public void setBinWidth(double i){ binWidth = i; }
+    
+    
+    public void setLabel(String l){ label = l; }
+  
+
+    public void init()
+    {
+	this.setDefaultLookAndFeelDecorated(true);
+        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+	this.setSize(width,height);
+	this.setLocation(0,0);
+	this.setVisible(true);
+	
+	this.addComponentListener( new ComponentAdapter(){
+		public void componentResized(ComponentEvent evt){
+		    update();
+		}
+	    });
+	
+	
+	this.addMouseListener(new MouseAdapter(){
+		public void mousePressed(MouseEvent evt)
+		{
+    		    int intX = evt.getX();
+	    	    int intY = evt.getY();
+		}
+		
+		public void mouseClicked(MouseEvent event)
+		{
+		    int x = event.getX();
+		    int y = event.getY();
+		    
+		    if( event.getClickCount() == 1 )
+			{
+			}
+		}
+	    });
+	
+	
+	this.addMouseMotionListener(new MouseMotionAdapter()
+	    {
+		public void mouseMoved(MouseEvent event)
+		{
+		    int x = event.getX();
+		    int y = event.getY();
+		    getBin(x,y);
+		}
+	    });
+    }
+
+    
+    public int getBin(int x, int y){ return -1; }
+    
+    
+    private int getMaxBin()
+    {
+	int maxIndex = -1;
+	double max = 0;
+	
+	for( int i = 0; i < bins.length; i++ ){
+	    if( bins[i] > max ){
+		max = bins[i];
+		maxIndex = i;
+	    }
+	}
+	
+	return maxIndex;
+    }
+    
+    
+    public void plot()
+    {
+	update();
+    }
+    
+      
+    public double getMaximum(double[] d)
+    {
+	double max = 0; 
+
+	for( int i = 0; i < d.length; i++ ){
+	    if( d[i] > max ) max = d[i];
+	}
+	return max;
+    }
+ 
+
+
+    public double getMinimum(double[] d)
+    {
+	double min = d[0]*d[0]*1000;
+
+	for( int i = 0; i < d.length; i++ )
+	    {
+		if( d[i] < min ) min = d[i];
+	    }
+	return min;
+    }
+
+
+    public double getFirstMaximum(double[] d)
+    {
+	double max = 0;
+	for( int i = 0; i < bins.length; i++ )
+	    {
+		if( bins[i] > max )
+		    {
+			max = bins[i];
+		    }
+	    }
+	return max;
+    }
+
+
+    public double getSecondMaximum(double[] d)
+    {
+	double max_1 = 0;
+	double max_2 = 0;
+	for( int i = 0; i < d.length; i++ ){
+	    if( d[i] > max_1 ){
+		max_2 = max_1;
+		max_1 = d[i];
+	    }
+	    else if( d[i] > max_2 ){
+		max_2 = d[i];
+	    }
+	}
+	return max_2;
+    }
+
+ 
+    public void createBins(double[] data, int noBins, double xmin, double xmax)
+    {
+	bins = new double[noBins];
+	binWidth = (double)(xmax-xmin)/noBins;
+	
+	for( int i = 0; i < data.length; i++ ){
+	    int bin = (int)Math.round((double)(data[i]-xmin)/binWidth);
+	    if( bin >= noBins ) bin = noBins-1;
+	    bins[bin]++;
+	}
+	
+	for( int i = 0; i < noBins; i++ ){
+	    bins[i] = (double)bins[i]/data.length;
+	}
+    }
+    
+    
+    public void update()
+    {
+	if( bins == null ) return;
+	
+	Dimension d = this.getSize();
+	width = d.width;
+	height = d.height;
+        offScreen = new BufferedImage( width, height, BufferedImage.TYPE_INT_ARGB ); 
+	offGraphics = (Graphics2D)offScreen.getGraphics();
+        offGraphics.setColor(Color.white);
+        offGraphics.fillRect(0,0,width,height);
+    	BasicStroke myStroke = new BasicStroke(2,BasicStroke.CAP_BUTT,BasicStroke.JOIN_MITER);
+        offGraphics.setStroke(myStroke);	
+	
+	int xaxis = (int)(0.8 * width); 
+	int xBuffer = (int)(0.1 * width);
+	
+	int yaxis = (int)(0.8 * d.height);
+	int yBuffer = (int)(0.1 * d.height);
+	
+	double maxBinLength = (double)xaxis/bins.length;
+	int effBinLength = (int)(0.8 * maxBinLength);
+	int binBuffer = (int)(0.1 * effBinLength);
+	
+	double max_1 = getFirstMaximum(bins);
+	double max_2 = getSecondMaximum(bins);
+	double max = max_1;
+	boolean cut = false;
+	
+	if( max_1 > 1.5 * max_2 ){
+	    cut = true;
+	    max = max_2;
+	}
+	
+	double pixelPerUnit = (double)yaxis/max; 
+	
+
+	/**** draw the bins ****/
+
+	for( int i = 0; i < bins.length; i++ ){
+	    int level = (int)(bins[i]* pixelPerUnit);
+	    double bound = (double)((int)(100 * (i * (double)(xmax-xmin)/bins.length + xmin)))/100;
+	    offGraphics.setColor(Color.blue);
+	    offGraphics.fillRect(xBuffer + (int)(i*maxBinLength)+binBuffer,height-yBuffer-level,effBinLength,level);
+	}
+
+	if( label != null ){
+	    FontMetrics fm   = offGraphics.getFontMetrics(offGraphics.getFont());
+	    int x = width/2 - fm.stringWidth(label)/2;		
+	    offGraphics.drawString(label,x,yBuffer);
+	}
+
+
+	/**** draw axes and ticks ****/
+
+	offGraphics.setColor(Color.blue);
+	
+	int x = xBuffer + binBuffer;
+	int y = height-yBuffer;
+	offGraphics.drawLine(x, y+5, x, y-5);
+	
+	String tickStr = String.valueOf(xmin);
+	offGraphics.drawString(tickStr,x-5,y+20);
+	
+	x = xBuffer + binBuffer+(int)(bins.length*maxBinLength)+binBuffer;
+	y = height-yBuffer;
+	offGraphics.drawLine(x, y+5, x, y-5);
+	tickStr = String.valueOf(xmax);
+	offGraphics.drawString(tickStr,x-5,y+20);
+	
+        repaint();
+    }
+    
+    
+    public void paint(Graphics g)
+    {
+        Graphics2D g2 = (Graphics2D)g;
+	
+	if( offScreen!=null )
+	    g2.drawImage(offScreen, 0, 0, null);
+    }
+    
+    
+    public void printBins()
+    {
+	for( int i = 0; i < bins.length; i++ ){
+	    double low = i*binWidth;
+	    double up = (i+1)*binWidth;
+	    System.out.println("Bin.." + i + " " + low + " " + up + " " + bins[i]); 
+	}
+    }
+
+
+    public void save(String name, String type)
+    {
+	
+	try{
+	    Iterator imageWriters =
+		ImageIO.getImageWritersByFormatName(type);
+	    ImageWriter imageWriter = (ImageWriter)imageWriters.next();
+	    File file = new File( name + "." + type );
+	    ImageOutputStream ios =
+		ImageIO.createImageOutputStream( file );
+	    imageWriter.setOutput( ios );
+	    imageWriter.write( offScreen );
+	} catch( IOException ie ) {
+	    ie.printStackTrace();
+	}
+    }
+
+}
+
+ // accepts raw data
+    /*public Hgram(double[] data, int noBins, double min, double max)
+    {
+	super("JHistogram");
+	init();
+	createBins(data,noBins,min,max);
+	update();
+	}*/
+    
+    
+    // accepts both raw and binned data
+    /*public Hgram(double[] data, int noBins, int mode)
+    {
+	super("JHistogram");
+	this.mode = mode;
+	init();
+	createBins(data,noBins,getMinimum(data),getMaximum(data));
+	update();
+	}*/
+    
+
+
+/*
+	int level = (int)(bins[med_bin]* pixelPerUnit);
+	offGraphics.fillRect(xBuffer + (int)(med_bin*maxBinLength)+binBuffer,height-yBuffer-level,effBinLength,level);
+	String med_string = String.valueOf(median);
+	med_string = med_string.substring(0,med_string.indexOf('.')+3);
+	offGraphics.drawString(med_string, xBuffer + (int)(med_bin*maxBinLength)+binBuffer-10,height-yBuffer-level-10);
+	    
+	//saveHistogram(offScreen);
+	
+	repaint();
+	    }
+	    }*/
+
+  /*
+    public Hgram(double[] data, int noBins)
+    {
+	init();
+	createBins(data,noBins,getMinimum(data),getFirstMaximum(data));
+	update();
+	}*/
Index: /gs3-extensions/vishnu/trunk/src/vishnu/util/Misc.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/util/Misc.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/util/Misc.java	(revision 21054)
@@ -0,0 +1,240 @@
+package vishnu.util;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+
+public class Misc {
+
+    // quickSort
+    private static void qs(int l, int r, double v[][])
+    {
+	if( l < r )
+	{
+		int i = l;
+		int j = r;
+		double pivot = v[1][(l+r)/2];
+		do
+		{
+
+			while( v[1][i] < pivot )
+			{
+				i++;
+			} 
+			while( v[1][j] > pivot )
+			{
+				j--;
+			}
+
+			if( i <= j )
+			{
+				if( i < j )
+				{
+					double t1 = v[1][i];	
+					double t0 = v[0][i];
+					v[1][i] = v[1][j];
+					v[1][j] = t1;
+					v[0][i] = v[0][j];
+					v[0][j] = t0;
+				}
+				i++;
+				j--;
+			}
+		
+		}
+		while( i <= j );
+		qs(l,j,v);
+		qs(i,r,v);
+		
+	}
+    }
+
+    
+    
+    // quickSort
+    private static void qs(int l, int r, double v[])
+    {
+	if( l < r )
+	{
+		int i = l;
+		int j = r;
+		double pivot = v[(l+r)/2];
+		do
+		{
+
+			while( v[i] < pivot )
+			{
+				i++;
+			} 
+			while( v[j] > pivot )
+			{
+				j--;
+			}
+
+			if( i <= j )
+			{
+				if( i < j )
+				{
+					double t1 = v[i];	
+					v[i] = v[j];
+					v[j] = t1;
+				}
+				i++;
+				j--;
+			}
+		
+		}
+		while( i <= j );
+		qs(l,j,v);
+		qs(i,r,v);
+		
+	}
+    }
+
+
+ // quickSort
+    private static void qs(int l, int r, int v[])
+    {
+	if( l < r )
+	{
+		int i = l;
+		int j = r;
+		int pivot = v[(l+r)/2];
+		do
+		{
+
+			while( v[i] < pivot )
+			{
+				i++;
+			} 
+			while( v[j] > pivot )
+			{
+				j--;
+			}
+
+			if( i <= j )
+			{
+				if( i < j )
+				{
+					int t1 = v[i];	
+					v[i] = v[j];
+					v[j] = t1;
+				}
+				i++;
+				j--;
+			}
+		
+		}
+		while( i <= j );
+		qs(l,j,v);
+		qs(i,r,v);
+	}
+    }
+
+
+ // quickSort
+    private static void qs(int l, int r, int[] v, Object[] objArr)
+    {
+        if( l < r ){
+            int i = l;
+            int j = r;
+
+            int pivot = v[(l+r)/2];
+
+            do{
+                while( v[i] < pivot ){
+                    i++;
+                }
+                while( v[j] > pivot ){
+                    j--;
+                }
+
+                if( i <= j ){
+                    if( i < j ){
+                        int t1 = v[i];
+                        v[i] = v[j];
+                        v[j] = t1;
+
+                        Object tobj = objArr[i];
+                        objArr[i] = objArr[j];
+                        objArr[j] = tobj;
+ }
+                    i++;
+                    j--;
+                }
+
+            }
+            while( i <= j );
+            qs(l,j,v,objArr);
+            qs(i,r,v,objArr);
+        }
+    }
+
+
+    // quickSort
+    private static void qs(int l, int r, float[] v, Object[] objArr)
+    {
+	if( l < r ){
+	    int i = l;
+	    int j = r;
+	    
+	    float pivot = v[(l+r)/2];
+	    
+	    do{
+		while( v[i] < pivot ){
+		    i++;
+		} 
+		while( v[j] > pivot ){
+		    j--;
+		}
+		
+		if( i <= j ){
+		    if( i < j ){
+			float t1 = v[i];	
+			v[i] = v[j];
+			v[j] = t1;
+			
+			Object tobj = objArr[i];
+			objArr[i] = objArr[j];
+			objArr[j] = tobj;
+		    }
+		    i++;
+		    j--;
+		}
+		
+	    }
+	    while( i <= j );
+	    qs(l,j,v,objArr);
+	    qs(i,r,v,objArr);
+	}
+    }
+    
+
+    public static void quickSort(int[] v)
+    {
+	qs(0,v.length-1,v);
+    }
+
+    public static void quickSort(float[] v, Object[] objArr)
+    {
+	qs(0,v.length-1,v,objArr);
+    }
+
+    public static void quickSort(int[] v, Object[] objArr)
+    {
+	qs(0,v.length-1,v,objArr);
+    }
+
+    public static void quickSort(double v[])
+    {
+	qs(0,v.length-1,v);
+    }
+
+
+    public static void quickSort(double v[][])
+    {
+	qs(0,v[0].length-1,v);
+    }
+}
+    
+    
Index: /gs3-extensions/vishnu/trunk/src/vishnu/util/Sammon.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/util/Sammon.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/util/Sammon.java	(revision 21054)
@@ -0,0 +1,247 @@
+package vishnu.util;
+
+import java.io.*;
+import java.util.*;
+import vishnu.datablock.Point2D;
+
+public class Sammon
+{
+
+    public static final double MF =       0.3;		/* Magic Factor          */
+    public static final double _MAX_Err = 0.00005;	/* Max Mapping Error     */
+    public static final int _MAX_M =  1000;		/* Max Iteration Number  */
+    public static final int _MAX_N =  1000;		/* Max Number of Entries */
+    public static final int _MAX_L =  128;		/* Max Dimension for X   */
+
+
+    double[][] Xs;
+    double[][] Ys;
+    int number;
+    int dimension;
+    double total_Xs_dists;
+
+/* SS298: Added function for scaling values between 0 and 1
+* n : number of entries
+*/
+    void scale_values(int n)
+    {
+
+	// get the maximum and the minimum of Ys array
+
+    // get the maximum and the minimum of Ys array
+
+        double maxX = Ys[0][0];
+        double minX = Ys[0][0];
+        double maxY = Ys[0][1];
+        double minY = Ys[0][1];
+
+        int i;
+        for (i = 1 ; i < n ; i++)
+        {
+
+            if (maxX < Ys[i][0]) maxX = Ys[i][0];
+            if (minX > Ys[i][0]) minX = Ys[i][0];
+            if (maxY < Ys[i][1]) maxY = Ys[i][1];
+            if (minY > Ys[i][1]) minY = Ys[i][1];
+        }
+        maxY -= minY;
+        maxX -= minX;
+        for (i = 0 ; i < n ; i++)
+        {
+            if (maxX > 0)
+            {
+                Ys[i][0] -= minX;
+                Ys[i][0] /= maxX;
+                Ys[i][0] *=0.8;
+                Ys[i][0] +=0.1;
+            }
+            else Ys[i][0]=0.5;
+            if (maxY > 0)
+            {
+                Ys[i][1] -= minY;
+                Ys[i][1] /= maxY;
+                Ys[i][1] *= 0.8;
+                Ys[i][1] += 0.1;
+            }
+            else Ys[i][1]=0.5;
+        }
+
+    }
+
+
+
+/* return a Euclidean distance between two 'd'-dimensional vectors 'xs' and 'ys'
+*/
+    double eucl_dist(int d, double[] xs, double[] ys)
+    {
+        double ans=0.0;
+        double tmp;
+        int i;
+        for(i=0;i<d;i++)
+        {
+            tmp = xs[i]-ys[i];
+            ans += tmp * tmp;
+        }
+        return Math.sqrt(ans);
+    }
+
+
+/* n - number of entries
+* d - dimension
+* post:
+*   return an array of eucidean distances for X
+*   and assign its total distance (will be used in the mapping error) to 'total'
+*/
+    double[] compute_Xs_eucl_dists()
+    {
+        double tmp[];
+        int i,j,k;
+        tmp = new double[number*(number-1)/2];
+
+        total_Xs_dists=0.0;
+        k=0;
+        for(i=1;i<number;i++)
+            for(j=0;j<i;j++)
+            {
+                tmp[k] = eucl_dist(dimension, Xs[i], Xs[j]);
+                total_Xs_dists+=tmp[k];
+                k++;
+            }
+            return tmp;
+    }
+    
+    
+    public Point2D [] getMapping()
+    {
+        Point2D [] sam = new Point2D[Ys.length];
+        for (int c=0;c<Ys.length;c++)
+	    {
+            float fx = (float) Ys[c][0];
+            float fy = (float) Ys[c][1];
+            sam[c]=new Point2D(fx,fy);
+        }
+        return sam;
+    }
+
+
+
+/*----------------------------------------------------------------*/
+    
+    public Sammon(double [][] centroids)
+    {
+	
+        int i, j, m, p;
+        double[] Xs_dists;
+
+        double dist_star,dist, alpha, beta, gamma, zeta, err;
+        double uppDer0, uppDer1, lowDer0, lowDer1;
+        double maxX=0.0;
+        double minX=0.0;
+        double maxY=0.0;
+        double minY=0.0;
+
+        number = centroids.length;
+        dimension = centroids[0].length;
+
+        Xs = centroids;
+        Ys = new double[number][2];
+
+        Xs_dists = compute_Xs_eucl_dists();
+
+        p=0;
+        for (i=0;i<number;i++)
+        {
+            Ys[i][0] =  Xs[i][0];
+            double value = Xs_dists.length>0?Xs_dists[(i==(number-1))?i:p]:
+                          0.0;
+            Ys[i][1] =  Xs[i][0]+value;
+            p+=(number-(i+1));
+        }
+
+        m=0;  /* iteration counter */
+        do{
+
+            for(p=0;p<number;p++)
+            {
+                uppDer0=uppDer1=lowDer0=lowDer1=0.0;
+
+
+                /* calcuate partial derivatives */
+
+                for(j=0;j<number;j++)
+                {
+                    if (p==j) continue;
+                    dist_star = Xs_dists[(j>p) ? (j*(j-1)/2+p) : (p*(p-1)/2+j)];
+
+                    dist = eucl_dist(2,Ys[p],Ys[j]);
+                    if (dist == 0) dist = 0.00001;
+                    alpha = dist_star-dist;
+                    beta = dist_star*dist;
+                    if (beta == 0) beta = 0.00000000000001;
+                    gamma = alpha/beta;
+
+                    /* 1st dimension */
+                    zeta = Ys[p][0]-Ys[j][0];
+                    uppDer0 += gamma * zeta;
+                    lowDer0 += (alpha-(zeta*zeta/dist) * (1+alpha/dist))/beta;
+
+                    /* 2nd dimension */
+                    zeta = Ys[p][1]-Ys[j][1];
+                    uppDer1 += gamma * zeta;
+                    lowDer1 += (alpha-(zeta*zeta/dist) * (1+alpha/dist))/beta;
+                }  /* for */
+
+                Ys[p][0] = Ys[p][0]+MF*uppDer0/Math.abs(lowDer0);
+                Ys[p][1] = Ys[p][1]+MF*uppDer1/Math.abs(lowDer1);
+                if(p==0)
+                {
+                    maxX = Ys[0][0];
+                    minX = Ys[0][0];
+                    maxY = Ys[0][1];
+                    minY = Ys[0][1];
+                }
+                else
+                {
+                  if (maxX < Ys[p][0]) maxX = Ys[p][0];
+                  if (minX > Ys[p][0]) minX = Ys[p][0];
+                  if (maxY < Ys[p][1]) maxY = Ys[p][1];
+                  if (minY > Ys[p][1]) minY = Ys[p][1];
+                }
+
+
+            }  /* for */
+
+
+        /* calcuate the mapping error */
+
+            err=0.0;
+            p=0;
+            for(j=1;j<number;j++)
+                for(i=0;i<j;i++)
+                {
+                    dist_star = Xs_dists[p++];
+                    alpha = dist_star - eucl_dist(2,Ys[j],Ys[i]);
+                    err += (alpha*alpha/dist_star);
+                }
+            err=err / total_Xs_dists;
+
+            m++;
+        } while ((err>_MAX_Err) && (m!=_MAX_M));
+
+
+        if(maxY==minY)
+        {
+          System.out.println("Warning: simulated 'y' values in mapping");
+          for(p=0;p<number;p++)
+            Ys[p][1]=Math.random();
+        }
+        if(maxX==minX)
+        {
+          System.out.println("Warning: simulated 'x' values in mapping");
+          for(p=0;p<number;p++)
+            Ys[p][0]=Math.random();
+        }
+
+        scale_values(number);
+    }
+}
Index: /gs3-extensions/vishnu/trunk/src/vishnu/util/SpringUtilities.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/util/SpringUtilities.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/util/SpringUtilities.java	(revision 21054)
@@ -0,0 +1,194 @@
+package vishnu.util;
+
+import javax.swing.*;
+import javax.swing.SpringLayout;
+import java.awt.*;
+
+/**
+ * A 1.4 file that provides utility methods for
+ * creating form- or grid-style layouts with SpringLayout.
+ * These utilities are used by several programs, such as
+ * SpringBox and SpringCompactGrid.
+ */
+public class SpringUtilities {
+    /**
+     * A debugging utility that prints to stdout the component's
+     * minimum, preferred, and maximum sizes.
+     */
+    public static void printSizes(Component c) {
+        System.out.println("minimumSize = " + c.getMinimumSize());
+        System.out.println("preferredSize = " + c.getPreferredSize());
+        System.out.println("maximumSize = " + c.getMaximumSize());
+    }
+
+    /**
+     * Aligns the first <code>rows</code> * <code>cols</code>
+     * components of <code>parent</code> in
+     * a grid. Each component is as big as the maximum
+     * preferred width and height of the components.
+     * The parent is made just big enough to fit them all.
+     *
+     * @param rows number of rows
+     * @param cols number of columns
+     * @param initialX x location to start the grid at
+     * @param initialY y location to start the grid at
+     * @param xPad x padding between cells
+     * @param yPad y padding between cells
+     */
+    public static void makeGrid(Container parent,
+                                int rows, int cols,
+                                int initialX, int initialY,
+                                int xPad, int yPad) {
+        SpringLayout layout;
+        try {
+            layout = (SpringLayout)parent.getLayout();
+        } catch (ClassCastException exc) {
+            System.err.println("The first argument to makeGrid must use SpringLayout.");
+            return;
+        }
+
+        Spring xPadSpring = Spring.constant(xPad);
+        Spring yPadSpring = Spring.constant(yPad);
+        Spring initialXSpring = Spring.constant(initialX);
+        Spring initialYSpring = Spring.constant(initialY);
+        int max = rows * cols;
+
+        //Calculate Springs that are the max of the width/height so that all
+        //cells have the same size.
+        Spring maxWidthSpring = layout.getConstraints(parent.getComponent(0)).
+                                    getWidth();
+        Spring maxHeightSpring = layout.getConstraints(parent.getComponent(0)).
+                                    getWidth();
+        for (int i = 1; i < max; i++) {
+            SpringLayout.Constraints cons = layout.getConstraints(
+                                            parent.getComponent(i));
+
+            maxWidthSpring = Spring.max(maxWidthSpring, cons.getWidth());
+            maxHeightSpring = Spring.max(maxHeightSpring, cons.getHeight());
+        }
+
+        //Apply the new width/height Spring. This forces all the
+        //components to have the same size.
+        for (int i = 0; i < max; i++) {
+            SpringLayout.Constraints cons = layout.getConstraints(
+                                            parent.getComponent(i));
+
+            cons.setWidth(maxWidthSpring);
+            cons.setHeight(maxHeightSpring);
+        }
+
+        //Then adjust the x/y constraints of all the cells so that they
+        //are aligned in a grid.
+        SpringLayout.Constraints lastCons = null;
+        SpringLayout.Constraints lastRowCons = null;
+        for (int i = 0; i < max; i++) {
+            SpringLayout.Constraints cons = layout.getConstraints(
+                                                 parent.getComponent(i));
+            if (i % cols == 0) { //start of new row
+                lastRowCons = lastCons;
+                cons.setX(initialXSpring);
+            } else { //x position depends on previous component
+                cons.setX(Spring.sum(lastCons.getConstraint(SpringLayout.EAST), xPadSpring));
+            }
+
+            if (i / cols == 0) { //first row
+                cons.setY(initialYSpring);
+            } else { //y position depends on previous row
+                cons.setY(Spring.sum(lastRowCons.getConstraint(SpringLayout.SOUTH),
+                                     yPadSpring));
+            }
+            lastCons = cons;
+        }
+
+        //Set the parent's size.
+        SpringLayout.Constraints pCons = layout.getConstraints(parent);
+        pCons.setConstraint(SpringLayout.SOUTH,
+                            Spring.sum(
+                                Spring.constant(yPad),
+                                lastCons.getConstraint(SpringLayout.SOUTH)));
+        pCons.setConstraint(SpringLayout.EAST,
+                            Spring.sum(
+                                Spring.constant(xPad),
+                                lastCons.getConstraint(SpringLayout.EAST)));
+    }
+
+    /* Used by makeCompactGrid. */
+    private static SpringLayout.Constraints getConstraintsForCell(
+                                                int row, int col,
+                                                Container parent,
+                                                int cols) {
+        SpringLayout layout = (SpringLayout) parent.getLayout();
+        Component c = parent.getComponent(row * cols + col);
+        return layout.getConstraints(c);
+    }
+
+    /**
+     * Aligns the first <code>rows</code> * <code>cols</code>
+     * components of <code>parent</code> in
+     * a grid. Each component in a column is as wide as the maximum
+     * preferred width of the components in that column;
+     * height is similarly determined for each row.
+     * The parent is made just big enough to fit them all.
+     *
+     * @param rows number of rows
+     * @param cols number of columns
+     * @param initialX x location to start the grid at
+     * @param initialY y location to start the grid at
+     * @param xPad x padding between cells
+     * @param yPad y padding between cells
+     */
+    public static void makeCompactGrid(Container parent,
+                                       int rows, int cols,
+                                       int initialX, int initialY,
+                                       int xPad, int yPad) {
+        SpringLayout layout;
+        try {
+            layout = (SpringLayout)parent.getLayout();
+        } catch (ClassCastException exc) {
+            System.err.println("The first argument to makeCompactGrid must use SpringLayout.");
+            return;
+        }
+
+        //Align all cells in each column and make them the same width.
+        Spring x = Spring.constant(initialX);
+        for (int c = 0; c < cols; c++) {
+            Spring width = Spring.constant(0);
+            for (int r = 0; r < rows; r++) {
+                width = Spring.max(width,
+                                   getConstraintsForCell(r, c, parent, cols).
+                                       getWidth());
+            }
+            for (int r = 0; r < rows; r++) {
+                SpringLayout.Constraints constraints =
+                        getConstraintsForCell(r, c, parent, cols);
+                constraints.setX(x);
+                constraints.setWidth(width);
+            }
+            x = Spring.sum(x, Spring.sum(width, Spring.constant(xPad)));
+        }
+
+        //Align all cells in each row and make them the same height.
+        Spring y = Spring.constant(initialY);
+        for (int r = 0; r < rows; r++) {
+            Spring height = Spring.constant(0);
+            for (int c = 0; c < cols; c++) {
+                height = Spring.max(height,
+                                    getConstraintsForCell(r, c, parent, cols).
+                                        getHeight());
+            }
+            for (int c = 0; c < cols; c++) {
+                SpringLayout.Constraints constraints =
+                        getConstraintsForCell(r, c, parent, cols);
+                constraints.setY(y);
+                constraints.setHeight(height);
+            }
+            y = Spring.sum(y, Spring.sum(height, Spring.constant(yPad)));
+        }
+
+        //Set the parent's size.
+        SpringLayout.Constraints pCons = layout.getConstraints(parent);
+        pCons.setConstraint(SpringLayout.SOUTH, y);
+        pCons.setConstraint(SpringLayout.EAST, x);
+    }
+}
+
Index: /gs3-extensions/vishnu/trunk/src/vishnu/util/XMLUtil.java
===================================================================
--- /gs3-extensions/vishnu/trunk/src/vishnu/util/XMLUtil.java	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/src/vishnu/util/XMLUtil.java	(revision 21054)
@@ -0,0 +1,71 @@
+/*********************************************************************
+ Copyright (C) 2004 New Zealand Digital Library, http://www.nzdl.org
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+***********************************************************************/
+
+package vishnu.util;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+public class XMLUtil {
+    
+    /** returns the (first) child element with the given name */
+    public static Node getChildByTagName(Node n, String name) {
+
+	Node child = n.getFirstChild();
+	while (child!=null) {
+	    if (child.getNodeName().equals(name)) {
+		return child;
+	    }
+	    child = child.getNextSibling();
+	}
+	return null; //not found
+    }
+
+    /** takes a list of elements, and returns an array of strings 
+     * of the values of attribute att_name */
+    public static String [] getAttributeValuesFromList(Element list,
+						       String att_name) {
+	
+	NodeList children = list.getChildNodes();
+	
+	int num_nodes = children.getLength();
+	String []ids = new String[num_nodes];
+	for (int i=0; i<num_nodes; i++) {
+	    Element e = (Element)children.item(i);
+	    String id = e.getAttribute(att_name);	    
+	    ids[i] = id;	    
+	}
+	
+	return ids;
+    }
+
+    /** extracts the text out of a node */
+    public static String getNodeText(Element param) {
+	param.normalize();
+	Node n = param.getFirstChild();
+	while (n!=null && n.getNodeType() !=Node.TEXT_NODE) {
+	    n=n.getNextSibling();
+	}
+	if (n == null) {
+	    return "";
+	}
+	return n.getNodeValue();
+    }
+
+}
Index: /gs3-extensions/vishnu/trunk/web/WEB-INF/web.xml
===================================================================
--- /gs3-extensions/vishnu/trunk/web/WEB-INF/web.xml	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/web/WEB-INF/web.xml	(revision 21054)
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!DOCTYPE web-app 
+    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
+    "http://java.sun.com/dtd/web-app_2_3.dtd">
+
+<!-- this is based on a sample web.xml file provided by 
+http://jakarta.apache.org/tomcat/tomcat-4.0-doc/appdev/web.xml.txt
+-->
+<web-app>
+
+  
+  <!-- General description of your web application -->
+  
+  <display-name>VISHNU</display-name>
+  <description>
+    Collection server for Imperial College's new Vishnu Visualisation tool.
+  </description>
+  
+  <context-param>
+    <param-name>webmaster</param-name>
+    <param-value>enter your email address here</param-value>
+    <description>
+      The EMAIL address of the administrator to whom questions
+      and comments about this application should be addressed.
+    </description>
+  </context-param>
+  
+
+  <!-- Servlet definitions for the servlets that make up
+  your web application, including initialization
+  parameters.  
+  -->
+  <servlet>
+    <servlet-name>visualiser</servlet-name>
+    <description>imperial college vishnu servlet</description>
+    <servlet-class>vishnu.server.VisServlet</servlet-class>
+    <init-param>
+      <param-name>collectionsHome</param-name>
+      <!-- edit this to provide the full path to the collections directory-->
+      <!-- make sure the path separators are correct for your platform -->
+      <param-value>path-to-vishnu/collections</param-value>
+    </init-param>
+  </servlet>
+
+  <!-- Define mappings that are used by the servlet container to
+  translate a particular request URI (context-relative) to a
+  particular servlet.  The examples below correspond to the
+  servlet descriptions above.  -->
+  <servlet-mapping>
+    <servlet-name>visualiser</servlet-name>
+    <url-pattern>/server</url-pattern>
+  </servlet-mapping>
+  <!-- Define the default session timeout for your application,
+  in minutes.  From a servlet or JSP page, you can modify
+  the timeout for a particular session dynamically by using
+  HttpSession.getMaxInactiveInterval(). -->
+  
+  <session-config>
+    <session-timeout>30</session-timeout>    <!-- 30 minutes -->
+  </session-config>
+  
+  
+</web-app>
+
+
Index: /gs3-extensions/vishnu/trunk/web/index.html
===================================================================
--- /gs3-extensions/vishnu/trunk/web/index.html	(revision 21054)
+++ /gs3-extensions/vishnu/trunk/web/index.html	(revision 21054)
@@ -0,0 +1,64 @@
+<html>
+  <head>
+    <title>Imperial College Visualizer</title>
+  </head>
+  <body>
+<h1>VISHNU:</h1>
+
+<h2>Overview</h2>
+This is a document search engine with several new visualization front
+ends that aid navigation through the set of documents returned by a
+query. Keywords from the document set are identified and selected on
+the fly. These keywords are then used to compute document clusters. The
+clusters can be displayed and explored using three visualizations:
+Sammon cluster view, Dendro map or radial interactive visualization.<br>      
+
+<h2>Quick start</h2>
+<p>Choose a collection from the colection menu. Type a query into the search box and click the Find button. Each pane will then display the query
+results in a different manner.<br>
+</p>
+<p style="margin-left: 40px;"><span style="font-weight:bold;">PlainVis</span> ---&nbsp; This shows a ranked list of documents
+retrieved. Clicking on the link for a document (in the left most
+column) will bring up a new window containing the document.<br>
+</p>
+
+<p style="margin-left: 40px;"><span style="font-weight: bold;">RadialVis</span> --- This shows the
+key words from the retrieved document set around a circle with the
+documents represented by dots within the circle. The dots are arranged
+closest to the words that contain them. Clicking on a word will
+highlight the documents containing that word. Correspondingly, clicking
+on a dot will show the words that it contains. Words can be dragged
+which will move the dots, thus changing the influence of that particular
+word on the arrangement.<br>
+At the bottom part of the pane key words can be selected and de-selected.<br>
+</p>
+<p style="margin-left: 40px;"><span style="font-weight: bold;">SammonVis</span> --- This displays
+documents clustered by key words. As you mouse over a cluster a menu is
+displayed. This shows the number of documents in the cluster, and, at
+the bottom of the menu, the top ranked key words used to form the
+cluster. Clicking on the number of documents brings up the key words
+and their percentages in the left hand frame and a summary of the
+documents in the right hand frame. It is possible to drill down two
+levels through this menu. To return to a higher level use the button at
+the top of the frame.<br>
+</p>
+<p style="margin-left: 40px;"><span style="font-weight: bold;">DendroVis</span> ---&nbsp; This
+displays the document clusters arranged in a hierarchical format. As
+you mouse over nodes in the display a menu similar to the SammonVis
+menu is displayed. You can drill down through the hierarchy to examine
+deeper nodes. The key words are displayed at each node. The menu
+indicates if these are from the outer twigs (bottom) of the hierarchy.
+Clicking on the number of documents in the node displays the keywords
+and document summaries.<br>
+</p>
+
+      
+      <p>
+	<center>
+	<applet codebase='lib' code='vishnu.testvis.visual.Vishnu.class' archive='vishnu.jar' width='900' height='700'>
+	  <param name='library' value='http://localhost:8080/vishnu/server'/>
+	</applet>
+      </p>
+    </center>
+  </body>
+</html>
