[med-svn] [grabix] 14/14: New upstream version 4.1

Andreas Tille tille at debian.org
Fri Dec 8 20:09:53 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to annotated tag upstream/4.1
in repository grabix.

commit 61dca66b172f2fad480743a383e438fe5f641a12
Author: Andreas Tille <tille at debian.org>
Date:   Fri Dec 8 21:04:32 2017 +0100

    New upstream version 4.1
---
 README                                             |   47 +
 build.xml                                          |  150 ++
 debian/README.Debian                               |   14 -
 debian/bin/haploview                               |    2 -
 debian/changelog                                   |    9 -
 debian/compat                                      |    1 -
 debian/control                                     |   34 -
 debian/copyright                                   |   32 -
 debian/docs                                        |    1 -
 debian/haploview.dirs                              |    2 -
 debian/install                                     |    1 -
 debian/patches/build.xml                           |   84 -
 debian/patches/series                              |    1 -
 debian/rules                                       |   30 -
 debian/source/format                               |    1 -
 debian/watch                                       |    4 -
 docs/HaploHelp.jar                                 |  Bin 0 -> 60552 bytes
 docs/README                                        |   18 +
 docs/add-web-headers.pl                            |   30 +
 docs/build.xml                                     |  111 +
 docs/haploview.xml                                 | 1948 +++++++++++++++++
 edu/mit/wi/haploview/BasicTableModel.java          |   42 +
 edu/mit/wi/haploview/CheckDataController.java      |  105 +
 edu/mit/wi/haploview/CheckDataPanel.java           |  470 ++++
 edu/mit/wi/haploview/CheckDataTableSorter.java     |   19 +
 edu/mit/wi/haploview/Chromosome.java               |  187 ++
 edu/mit/wi/haploview/Configuration.java            |   74 +
 edu/mit/wi/haploview/Constants.java                |  243 +++
 edu/mit/wi/haploview/DPrimeDisplay.java            | 1831 ++++++++++++++++
 edu/mit/wi/haploview/DPrimeTable.java              |   45 +
 edu/mit/wi/haploview/EM.java                       | 1290 +++++++++++
 edu/mit/wi/haploview/ExportDialog.java             |  278 +++
 .../wi/haploview/FilteredIndividualsDialog.java    |  136 ++
 edu/mit/wi/haploview/FindBlocks.java               |  335 +++
 edu/mit/wi/haploview/Functions.java                |   75 +
 edu/mit/wi/haploview/GBrowseDialog.java            |  123 ++
 edu/mit/wi/haploview/GBrowseOptionDialog.java      |   87 +
 edu/mit/wi/haploview/GeneCruiser.java              |  190 ++
 edu/mit/wi/haploview/HVWrap.java                   |  187 ++
 edu/mit/wi/haploview/HaploData.java                | 2273 ++++++++++++++++++++
 edu/mit/wi/haploview/HaploText.java                | 1879 ++++++++++++++++
 edu/mit/wi/haploview/HaploView.java                | 1924 +++++++++++++++++
 edu/mit/wi/haploview/HaploViewException.java       |    8 +
 edu/mit/wi/haploview/Haplotype.java                |  160 ++
 edu/mit/wi/haploview/HaplotypeDisplay.java         |  491 +++++
 .../wi/haploview/HaplotypeDisplayController.java   |  123 ++
 edu/mit/wi/haploview/HaploviewTab.java             |   24 +
 edu/mit/wi/haploview/HetsDialog.java               |  166 ++
 edu/mit/wi/haploview/IndividualDialog.java         |  228 ++
 edu/mit/wi/haploview/MendelDialog.java             |  183 ++
 edu/mit/wi/haploview/NumberTextField.java          |   59 +
 edu/mit/wi/haploview/Options.java                  |  210 ++
 edu/mit/wi/haploview/PairwiseLinkage.java          |   57 +
 edu/mit/wi/haploview/PlinkResultsPanel.java        |  740 +++++++
 .../wi/haploview/ProportionalSpacingDialog.java    |   73 +
 edu/mit/wi/haploview/ProxyDialog.java              |   59 +
 edu/mit/wi/haploview/ReadDataDialog.java           | 1101 ++++++++++
 edu/mit/wi/haploview/RegionDialog.java             |  134 ++
 edu/mit/wi/haploview/SNP.java                      |  124 ++
 edu/mit/wi/haploview/StatFunctions.java            |  295 +++
 edu/mit/wi/haploview/SwingWorker.java              |  132 ++
 edu/mit/wi/haploview/TableSorter.java              |  488 +++++
 .../wi/haploview/TreeTable/AbstractCellEditor.java |  113 +
 .../TreeTable/AbstractTreeTableModel.java          |  227 ++
 .../TreeTable/HaplotypeAssociationModel.java       |   84 +
 .../TreeTable/HaplotypeAssociationNode.java        |   69 +
 edu/mit/wi/haploview/TreeTable/JTreeTable.java     |  154 ++
 edu/mit/wi/haploview/TreeTable/MergeSort.java      |  109 +
 edu/mit/wi/haploview/TreeTable/TreeTableModel.java |   94 +
 .../haploview/TreeTable/TreeTableModelAdapter.java |  121 ++
 edu/mit/wi/haploview/TweakBlockDefsDialog.java     |  177 ++
 edu/mit/wi/haploview/UpdateChecker.java            |  119 +
 edu/mit/wi/haploview/UpdateDisplayDialog.java      |   84 +
 edu/mit/wi/haploview/Util.java                     |   28 +
 .../haploview/association/AssociationResult.java   |  262 +++
 .../haploview/association/AssociationTestSet.java  |  934 ++++++++
 .../wi/haploview/association/CustomAssocPanel.java |   83 +
 .../wi/haploview/association/HaploAssocPanel.java  |  182 ++
 .../association/HaplotypeAssociationResult.java    |  108 +
 .../association/MarkerAssociationResult.java       |  129 ++
 .../association/PermutationTestPanel.java          |  291 +++
 .../haploview/association/PermutationTestSet.java  |  386 ++++
 edu/mit/wi/haploview/association/TDTPanel.java     |  148 ++
 .../tagger/HaploviewAlleleCorrelator.java          |  241 +++
 edu/mit/wi/haploview/tagger/TaggerConfigPanel.java |  604 ++++++
 edu/mit/wi/haploview/tagger/TaggerController.java  |  177 ++
 .../wi/haploview/tagger/TaggerResultsPanel.java    |  239 ++
 edu/mit/wi/pedfile/CheckData.java                  |  617 ++++++
 edu/mit/wi/pedfile/CheckDataException.java         |   33 +
 edu/mit/wi/pedfile/Family.java                     |  146 ++
 edu/mit/wi/pedfile/Individual.java                 |  233 ++
 edu/mit/wi/pedfile/MarkerResult.java               |  229 ++
 edu/mit/wi/pedfile/MathUtil.java                   |  114 +
 edu/mit/wi/pedfile/MendelError.java                |   21 +
 edu/mit/wi/pedfile/PedFile.java                    | 1789 +++++++++++++++
 edu/mit/wi/pedfile/PedFileException.java           |    8 +
 edu/mit/wi/pedparser/Individual.java               |   49 +
 edu/mit/wi/pedparser/PedEdge.java                  |   26 +
 edu/mit/wi/pedparser/PedParser.java                |  864 ++++++++
 edu/mit/wi/pedparser/PedTreeNode.java              |   79 +
 edu/mit/wi/pedparser/PedigreeException.java        |    9 +
 edu/mit/wi/plink/AssociationResult.java            |   39 +
 edu/mit/wi/plink/Marker.java                       |   33 +
 edu/mit/wi/plink/Plink.java                        |  695 ++++++
 edu/mit/wi/plink/PlinkException.java               |    9 +
 edu/mit/wi/plink/PlinkGraph.java                   |  806 +++++++
 edu/mit/wi/plink/PlinkTableModel.java              |  314 +++
 edu/mit/wi/plink/PlotOptionDialog.java             |  355 +++
 edu/mit/wi/tagger/Allele.java                      |   50 +
 edu/mit/wi/tagger/AlleleCorrelator.java            |   11 +
 edu/mit/wi/tagger/Block.java                       |   65 +
 edu/mit/wi/tagger/LocusCorrelation.java            |   21 +
 edu/mit/wi/tagger/SNP.java                         |   59 +
 edu/mit/wi/tagger/Tag.java                         |   17 +
 edu/mit/wi/tagger/TagSequence.java                 |   73 +
 edu/mit/wi/tagger/Taggable.java                    |   32 +
 edu/mit/wi/tagger/Tagger.java                      |  843 ++++++++
 edu/mit/wi/tagger/TaggerException.java             |    8 +
 edu/mit/wi/tagger/VariantSequence.java             |   62 +
 examples/README                                    |   27 +
 examples/sample.hmp                                |   35 +
 examples/sample.info                               |   20 +
 examples/sample.ped                                |  120 ++
 resources/JimiLicense                              |   52 +
 resources/JimiProClasses.zip                       |  Bin 0 -> 455489 bytes
 resources/axiom/axiom-api-1.2.5.jar                |  Bin 0 -> 140953 bytes
 resources/axiom/axiom-dom-1.2.5.jar                |  Bin 0 -> 149481 bytes
 resources/axiom/axiom-impl-1.2.5.jar               |  Bin 0 -> 116719 bytes
 resources/axiom/axiom-license.txt                  |  203 ++
 resources/axiom/commons-logging-1.0.4.jar          |  Bin 0 -> 38015 bytes
 resources/axiom/stax-api-1.0.1.jar                 |  Bin 0 -> 26514 bytes
 resources/axiom/wstx-asl-3.0.0.jar                 |  Bin 0 -> 473187 bytes
 resources/jfreechart/batik-awt-util.jar            |  Bin 0 -> 369001 bytes
 resources/jfreechart/batik-dom.jar                 |  Bin 0 -> 87407 bytes
 resources/jfreechart/batik-ext.jar                 |  Bin 0 -> 76687 bytes
 resources/jfreechart/batik-license.txt             |  201 ++
 resources/jfreechart/batik-svggen.jar              |  Bin 0 -> 164659 bytes
 resources/jfreechart/batik-util.jar                |  Bin 0 -> 81508 bytes
 resources/jfreechart/batik-xml.jar                 |  Bin 0 -> 20305 bytes
 resources/jfreechart/jcommon-1.0.10.jar            |  Bin 0 -> 311481 bytes
 resources/jfreechart/jfreechart-1.0.6.jar          |  Bin 0 -> 1212442 bytes
 resources/jfreechart/license-LGPL.txt              |  504 +++++
 resources/jfreechart/xml-apis.jar                  |  Bin 0 -> 107431 bytes
 resources/jgrapht-0.5.3.jar                        |  Bin 0 -> 80943 bytes
 resources/jgrapht-license.txt                      |  504 +++++
 resources/jh.jar                                   |  Bin 0 -> 473527 bytes
 resources/log4j-1.2.14.jar                         |  Bin 0 -> 367444 bytes
 resources/orator.raw                               |  Bin 0 -> 11880 bytes
 148 files changed, 33490 insertions(+), 216 deletions(-)

diff --git a/README b/README
new file mode 100755
index 0000000..b8a4161
--- /dev/null
+++ b/README
@@ -0,0 +1,47 @@
+Welcome to Haploview!!
+
+Introduction:
+
+Haploview is designed to simplify and expedite the process of
+haplotype analysis by providing a common interface to several tasks
+relating to such analyses.
+
+
+What's here:
+
+A clean, pruned checkout of the code should have the following:
+
+edu/ -- the source tree
+examples/ -- some sample input files 
+resources/ -- some helper files
+docs/ -- files related to program documentation
+build.xml -- the ant build instruction file
+README -- this file
+
+
+Dependencies:
+
+HaploView can be built using the jakarta-ant build tool. Please
+download this tool at:
+
+http://ant.apache.org
+
+
+Building:
+
+After installing ant and putting the ant executable script in your path:
+
+BASH export PATH=$PATH:<path_to_ant_bin_directory>
+
+CSH setenv PATH $PATH:<path_to_ant_bin_directory>
+
+simply type:
+
+	ant haploview 
+
+Questions??
+
+e-mail:
+
+Jeffrey Barrett
+jcbarret at broad.mit.edu
diff --git a/build.xml b/build.xml
new file mode 100755
index 0000000..e774a7d
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Initial Set-Up -->
+
+<project name="HaploView" default="main" basedir=".">
+
+   <path id="haploview.root">
+     <pathelement location="${basedir}"/>
+   </path>
+
+
+
+<!-- Get all the files in the /resources directory and add them to the -->
+<!-- Classpath -->
+
+ <path id="library.classpath">
+   <fileset dir="resources/">
+       <include name="*.jar"/>
+       <include name="*.zip"/>
+       <include name="jfreechart/*.jar"/>
+       <include name="axiom/*.jar"/>
+     </fileset>
+   <fileset dir="docs/">
+       <include name="HaploHelp.jar"/>
+   </fileset>
+</path>
+
+ <!-- =========================================================== -->
+ <!-- Initialization                                              -->
+ <!-- =========================================================== -->
+<!-- Setup project roots -->
+ <target name="init">
+    <property name="haploview.root" value="${basedir}"/>
+    <property name="haploview.build" value="${haploview.root}/build"/>
+    <property name="haploview.dist" value="${haploview.root}/dist"/>
+    <property name="haploview.classes" value="${haploview.build}/classes"/>
+    <property name="haploview.src" value="${haploview.root}"/>
+    <property name="haploview.lib" value="${haploview.root}/resources"/>
+    <property name="haploview.package" value="edu/mit/wi/"/>
+    <tstamp>
+      <format property="build.number" pattern="yyyyMMddHHmm"/>
+    </tstamp>
+</target>
+
+<!-- Prepare src directory for compilation step -->
+
+<target name="prepare-compile" depends="init">
+    <mkdir dir="${haploview.classes}"/>
+</target>
+
+<!-- Compile the Haploview application -->
+
+<target name="compile-haploview" depends="prepare-compile">
+   <javac srcdir="${haploview.src}" destdir="${haploview.classes}"
+   includes="${haploview.package}/**" debug="on" source="1.4" target="1.4">
+   <classpath refid="library.classpath"/>
+   </javac>
+
+   <copy file="resources/orator.raw" todir="build/classes/edu/mit/wi/haploview/"/>
+</target>
+
+
+
+<!-- this target makes the jar with HVWrap as the main class, which -->
+<!-- uses automagic -Xmx stuff, and suppresses error output -->
+<target name = "haploview-release" depends="compile-haploview">
+   <jar jarfile="Haploview.jar">
+     <fileset dir="${haploview.classes}">
+       <include name="edu/mit/wi/**/**"/>
+     </fileset>
+     <manifest>
+       <attribute name="Built-By" value="${user.name}"/>
+       <attribute name="Main-Class" value="edu.mit.wi.haploview.HVWrap"/>
+     </manifest>
+     <zipfileset src="resources/JimiProClasses.zip"/>
+     <zipfileset src="resources/jh.jar"/>
+     <zipfileset src="docs/HaploHelp.jar"/>
+     <zipfileset src="resources/jgrapht-0.5.3.jar"/>
+     <zipfileset src="resources/log4j-1.2.14.jar"/>
+     <zipfileset src="resources/jfreechart/jfreechart-1.0.6.jar"/>
+     <zipfileset src="resources/jfreechart/jcommon-1.0.10.jar"/>
+     <zipfileset src="resources/jfreechart/batik-xml.jar"/>
+     <zipfileset src="resources/jfreechart/xml-apis.jar"/>
+     <zipfileset src="resources/jfreechart/batik-awt-util.jar"/>
+     <zipfileset src="resources/jfreechart/batik-dom.jar"/>
+     <zipfileset src="resources/jfreechart/batik-svggen.jar"/>
+     <zipfileset src="resources/jfreechart/batik-util.jar"/>
+     <zipfileset src="resources/jfreechart/batik-ext.jar"/>
+     <zipfileset src="resources/axiom/axiom-api-1.2.5.jar"/>
+     <zipfileset src="resources/axiom/axiom-dom-1.2.5.jar"/>
+     <zipfileset src="resources/axiom/axiom-impl-1.2.5.jar"/>
+     <zipfileset src="resources/axiom/commons-logging-1.0.4.jar"/>
+     <zipfileset src="resources/axiom/stax-api-1.0.1.jar"/>
+     <zipfileset src="resources/axiom/wstx-asl-3.0.0.jar"/>
+   </jar>
+</target>
+
+
+<!-- Jar the application up -->
+<target name="haploview" depends="compile-haploview">
+   <jar jarfile="Haploview.jar">
+     <fileset dir="${haploview.classes}">
+       <include name="edu/mit/wi/**/**"/>
+     </fileset>
+     <manifest>
+       <attribute name="Built-By" value="${user.name}"/>
+       <attribute name="Main-Class" value="edu.mit.wi.haploview.HaploView"/>
+     </manifest>
+     <zipfileset src="resources/JimiProClasses.zip"/>
+     <zipfileset src="resources/jh.jar"/>
+     <zipfileset src="docs/HaploHelp.jar"/>
+     <zipfileset src="resources/jgrapht-0.5.3.jar"/>
+     <zipfileset src="resources/log4j-1.2.14.jar"/>
+     <zipfileset src="resources/jfreechart/jfreechart-1.0.6.jar"/>
+     <zipfileset src="resources/jfreechart/jcommon-1.0.10.jar"/>
+     <zipfileset src="resources/jfreechart/batik-xml.jar"/>
+     <zipfileset src="resources/jfreechart/xml-apis.jar"/>
+     <zipfileset src="resources/jfreechart/batik-awt-util.jar"/>
+     <zipfileset src="resources/jfreechart/batik-dom.jar"/>
+     <zipfileset src="resources/jfreechart/batik-svggen.jar"/>
+     <zipfileset src="resources/jfreechart/batik-util.jar"/>
+     <zipfileset src="resources/jfreechart/batik-ext.jar"/>
+     <zipfileset src="resources/axiom/axiom-api-1.2.5.jar"/>
+     <zipfileset src="resources/axiom/axiom-dom-1.2.5.jar"/>
+     <zipfileset src="resources/axiom/axiom-impl-1.2.5.jar"/>
+     <zipfileset src="resources/axiom/commons-logging-1.0.4.jar"/>
+     <zipfileset src="resources/axiom/stax-api-1.0.1.jar"/>
+     <zipfileset src="resources/axiom/wstx-asl-3.0.0.jar"/>
+   </jar>
+</target>
+
+<target name="clean" depends="init">
+  <delete dir="${haploview.build}"/>
+</target>
+
+<target name="main" depends="init">
+  <echo message="------------------------------------------------------------"/>
+  <echo message="welcome to haploview build file"/>
+  <echo message="Usage: ant [target-name]"/>
+  <echo message="where [target-name] is one of the following targets:"/>
+  <echo message="compile-haploview --> compiles the haploview classes
+  only"/>
+  <echo message="haploview-release --> makes 'wrapped' jar with auto Xmx"/>
+  <echo message="haploview --> makes the executable jar file"/>
+  <echo message="clean --> cleans up after itself removing the build directory"/>
+  <echo message="------------------------------------------------------------"/>
+</target>
+
+
+</project>
diff --git a/debian/README.Debian b/debian/README.Debian
deleted file mode 100644
index ff1bf21..0000000
--- a/debian/README.Debian
+++ /dev/null
@@ -1,14 +0,0 @@
-haploview for Debian
---------------------
-
-javahelp2 has just appeared on Debian and thus the packaging of Haploview
-can continue. Many thanks for that!
-
-I found the Jimi classes to be problematic, though. These seem to be
-available as binaries only http://java.sun.com/products/jimi/. All other
-tar files could be removed from the source they these were already
-available in Debian.
-
-Many thanks also to the Debian-Med community for their ongoing support.
-
- -- Steffen Moeller <moeller at debian.org>  Sun, 14 Jan 2007 15:28:10 +0100
diff --git a/debian/bin/haploview b/debian/bin/haploview
deleted file mode 100644
index adf8976..0000000
--- a/debian/bin/haploview
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-export CLASSPATH=/usr/share/java/jgrapht.jar java -jar /usr/share/haploview/Haploview.jar
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 033e99e..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,9 +0,0 @@
-haploview (4.1-1) UNRELEASED; urgency=low
-
-  [ Steffen Moeller ]
-  * Initial release (Closes: #311421)
-
-  [ Andreas Tille ]
-  * Standards-Version: 4.1.1
-
- -- Steffen Moeller <moeller at debian.org>  Sun, 14 Jan 2007 15:28:10 +0100
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index f599e28..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-10
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 39368d3..0000000
--- a/debian/control
+++ /dev/null
@@ -1,34 +0,0 @@
-Source: haploview
-Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Steffen Moeller <steffen_moeller at gmx.de>
-Section: contrib/science
-XS-Autobuild: no
-Priority: optional
-Build-Depends-Indep: cdbs,
-                     ant,
-                     debhelper (>= 10),
-                     libjfreechart-java,
-                     libjgrapht-java,
-                     liblog4j1.2-java,
-                     javahelp2
-Standards-Version: 4.1.1
-Vcs-Browser: http://anonscm.debian.org/viewvc/debian-med/trunk/packages/haploview/trunk/
-Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/haploview/trunk/
-Homepage: https://www.broadinstitute.org/scientific-community/science/programs/medical-and-population-genetics/haploview/haploview
-
-Package: haploview
-Architecture: all
-Depends: java-virtual-machine,
-         libjfreechart-java,
-         libjgrapht-java,
-         libjcommon-java,
-         liblog4j1.2-java,
-         javahelp2,
-         libbatik-java,
-         ${shlibs:Depends},
-         ${misc:Depends}
-Description: Analysis and visualization of LD and haplotype maps
- This tools assists in the analysis of the nucleotide
- variation in a population. Such investigations are performed
- to determine genes and genetic pathways that are associated
- with diseases. This is an early stage in the quest for new drugs.
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index a86c933..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,32 +0,0 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: HaploView
-Upstream-Contact: Jeffrey Barrett, Julian Maller, David Bender <haploview at broad.mit.edu>
-Source: http://sourceforge.net/projects/haploview/files/haploview-source/
-
-Files: *
-Copyright: © 2003-2006 Broad Institute of MIT and Harvard
-License: MIT
-
-Files: debian/*
-Copyright: © 2007 Steffen Moeller <steffen_moeller at gmx.de>
-License: MIT
-
-License: MIT
- Permission is hereby granted, free of charge, to any person obtaining a
- copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restr iction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Soft ware is furnished to do so, subject to
- the following conditions:
- .
- The above copyright notice and this permission notice shall be included
- in all copies or substantial portions of the Software.
- .
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
- TORT OR OTHERWISE, ARISING F ROM, OUT OF OR IN CONNECTION WITH THE
- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/debian/docs b/debian/docs
deleted file mode 100644
index e845566..0000000
--- a/debian/docs
+++ /dev/null
@@ -1 +0,0 @@
-README
diff --git a/debian/haploview.dirs b/debian/haploview.dirs
deleted file mode 100644
index 8cfd4cc..0000000
--- a/debian/haploview.dirs
+++ /dev/null
@@ -1,2 +0,0 @@
-usr/share/haploview
-usr/bin
diff --git a/debian/install b/debian/install
deleted file mode 100644
index 8e71dae..0000000
--- a/debian/install
+++ /dev/null
@@ -1 +0,0 @@
-debian/bin	usr
diff --git a/debian/patches/build.xml b/debian/patches/build.xml
deleted file mode 100644
index 18f4901..0000000
--- a/debian/patches/build.xml
+++ /dev/null
@@ -1,84 +0,0 @@
---- haploview-4.0.orig/build.xml
-+++ haploview-4.0/build.xml
-@@ -14,10 +14,18 @@
- <!-- Classpath -->
- 
-  <path id="library.classpath">
-+   <fileset dir="/usr/share/java/">
-+       <include name="jfreechart.jar" />
-+       <include name="log4j-1.2.jar" />
-+       <include name="jgrapht.jar" />
-+       <include name="jh.jar" />
-+       <include name="jcommon.jar" />
-+       <include name="batik-all.jar" />
-+   </fileset>
-    <fileset dir="resources/">
-        <include name="*.jar"/>
-        <include name="*.zip"/>
--       <include name="jfreechart/*.jar"/>
-+<!--       <include name="jfreechart/*.jar"/> -->
-      </fileset>
-    <fileset dir="docs/">
-        <include name="HaploHelp.jar"/>
-@@ -51,7 +59,9 @@
- 
- <target name="compile-haploview" depends="prepare-compile">
-    <javac srcdir="${haploview.src}" destdir="${haploview.classes}"
--   includes="${haploview.package}/**" debug="on" target="1.4">
-+   includes="${haploview.package}/**" debug="on"
-+   >
-+   <!-- target="1.4" -->
-    <classpath refid="library.classpath"/>
-    </javac>
- 
-@@ -72,12 +82,17 @@
-        <attribute name="Main-Class" value="edu.mit.wi.haploview.HVWrap"/>
-      </manifest>
-      <zipfileset src="resources/JimiProClasses.zip"/>
-+<!--
-      <zipfileset src="resources/jh.jar"/>
-+-->
-      <zipfileset src="docs/HaploHelp.jar"/>
-+<!--
-      <zipfileset src="resources/jgrapht-0.5.3.jar"/>
-      <zipfileset src="resources/log4j-1.2.14.jar"/>
-      <zipfileset src="resources/jfreechart/jfreechart-1.0.5.jar"/>
-      <zipfileset src="resources/jfreechart/jcommon-1.0.9.jar"/>
-+-->
-+<!--
-      <zipfileset src="resources/jfreechart/batik-xml.jar"/>
-      <zipfileset src="resources/jfreechart/xml-apis.jar"/>
-      <zipfileset src="resources/jfreechart/batik-awt-util.jar"/>
-@@ -85,6 +100,7 @@
-      <zipfileset src="resources/jfreechart/batik-svggen.jar"/>
-      <zipfileset src="resources/jfreechart/batik-util.jar"/>
-      <zipfileset src="resources/jfreechart/batik-ext.jar"/>
-+-->
-    </jar>
- </target>
- 
-@@ -100,12 +116,15 @@
-        <attribute name="Main-Class" value="edu.mit.wi.haploview.HaploView"/>
-      </manifest>
-      <zipfileset src="resources/JimiProClasses.zip"/>
--     <zipfileset src="resources/jh.jar"/>
-+     <!-- <zipfileset src="resources/jh.jar"/> -->
-      <zipfileset src="docs/HaploHelp.jar"/>
-+<!--
-      <zipfileset src="resources/jgrapht-0.5.3.jar"/>
-      <zipfileset src="resources/log4j-1.2.14.jar"/>
-      <zipfileset src="resources/jfreechart/jfreechart-1.0.5.jar"/>
-      <zipfileset src="resources/jfreechart/jcommon-1.0.9.jar"/>
-+-->
-+<!--
-      <zipfileset src="resources/jfreechart/batik-xml.jar"/>
-      <zipfileset src="resources/jfreechart/xml-apis.jar"/>
-      <zipfileset src="resources/jfreechart/batik-awt-util.jar"/>
-@@ -113,6 +132,7 @@
-      <zipfileset src="resources/jfreechart/batik-svggen.jar"/>
-      <zipfileset src="resources/jfreechart/batik-util.jar"/>
-      <zipfileset src="resources/jfreechart/batik-ext.jar"/>
-+-->
-    </jar>
- </target>
- 
diff --git a/debian/patches/series b/debian/patches/series
deleted file mode 100644
index c14487c..0000000
--- a/debian/patches/series
+++ /dev/null
@@ -1 +0,0 @@
-build.xml
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 8254a13..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/make -f
-  
-include /usr/share/quilt/quilt.make
-
-include /usr/share/cdbs/1/rules/debhelper.mk
-#include /usr/share/cdbs/1/class/makefile.mk
-include /usr/share/cdbs/1/class/ant.mk
-include /usr/share/cdbs/1/rules/patchsys-quilt.mk
-
-export JAVA_HOME=/usr/lib/jvm/java-6-sun/
-export JAVACMD=/usr/bin/java
-DEB_ANT_INSTALL_TARGET=haploview
-
-# Add here any variable or target overrides you need.
-
-DEB_JARS = jcommon,jgrapht,jfreechart,jh
-
-DEB_INSTALL_EXAMPLES_haploview=$(shell ls examples/*)
-
-common-install-impl:: install/haploview
-#install/libjline-java-doc
-
-install/haploview:: DEB_FINALDIR=$(CURDIR)/debian/haploview
-install/haploview::
-	install -p -m 644 -D Haploview.jar $(DEB_FINALDIR)/usr/share/haploview/Haploview.jar
-
-clean:: unpatch
-	dh_clean resources/jgrapht* Haploview.jar
-	rm -rf build resources/jfreechart
-	touch debian/files
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index 4616eb8..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,4 +0,0 @@
-version=3
-http://sf.net/haploview/Haploview([0-9].*)\.tar\.gz
-
-
diff --git a/docs/HaploHelp.jar b/docs/HaploHelp.jar
new file mode 100755
index 0000000..4d88517
Binary files /dev/null and b/docs/HaploHelp.jar differ
diff --git a/docs/README b/docs/README
new file mode 100755
index 0000000..686bd5f
--- /dev/null
+++ b/docs/README
@@ -0,0 +1,18 @@
+A clean copy of this directory should contain several files used in
+compiling the Haploview javahelp classes from scratch. It also
+contains a precompiled version of the most recently released help
+jarfile.
+
+The ant build.xml file will almost certainly not work on your system
+as it is written specifically for the machine which builds the release
+versions of the code and help documentation.
+
+It's probably best if you just leave this directory alone. :)
+
+
+Jeffrey Barrett
+jcbarret at broad.mit.edu
+
+Julian Maller
+jules at broad.mit.edu
+
diff --git a/docs/add-web-headers.pl b/docs/add-web-headers.pl
new file mode 100755
index 0000000..3f3ac3f
--- /dev/null
+++ b/docs/add-web-headers.pl
@@ -0,0 +1,30 @@
+#opens all the files in the directory ending in .php and strips <html> and
+#<body> tags and replaces 'em with the php header and footer for the web
+#page table of contents.
+
+$dir = $ARGV[0];
+
+opendir (THISDIR, $dir);
+ at files = readdir(THISDIR);
+closedir(THISDIR);
+
+foreach $file (@files){
+    if ($file =~ /php$/){
+	print "adding web headers: $file\n";
+	my $lines;
+	open (FILE, "$dir/$file");
+	while (<FILE>){
+	    $lines = $lines . $_;
+	}
+	close FILE;
+
+	open (OUT, ">$dir/$file");
+	print OUT "<?php \$documentation=true;\nrequire(\"header.php\"); ?>\n";
+	$lines =~ s/(<html[.\n]*<body.*?>)//;
+	$lines =~ s/<\/body><\/html>//;
+	print OUT $lines;
+	print OUT "<? require(\"footer.html\"); ?>\n";
+	close OUT;
+    }
+}
+
diff --git a/docs/build.xml b/docs/build.xml
new file mode 100755
index 0000000..c88d0b2
--- /dev/null
+++ b/docs/build.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Initial Set-Up -->
+
+<project name="HaploView" default="main" basedir=".">
+
+   <path id="haploview.root">
+     <pathelement location="${basedir}"/>
+   </path>
+
+
+ <!-- =========================================================== -->
+ <!-- Initialization                                              -->
+ <!-- =========================================================== -->
+<!-- Setup project roots -->
+ <target name="init">
+    <property name="haploview.root" value="${basedir}"/>
+    <property name="xslPath" value="/usr/share/sgml/docbook/stylesheet/xsl/nwalsh"/>
+    <property name="stylesheet.fo" value="${xslPath}/fo/docbook.xsl" />
+    <property name="stylesheet.html" value="${xslPath}/html/chunk.xsl" />
+    <property name="stylesheet.javahelp" value="${xslPath}/javahelp/javahelp.xsl" />
+    <property name="stylesheet.params" value="toc.section.depth=1 generate.toc="book toc" shade.verbatim=1 chunk.section.depth=0 suppress.navigation=1" />
+    <property name="xslt.processor.class" value="com.icl.saxon.StyleSheet" />
+    <property name="out.doc.fo" value="pdf/haploview.fo" />
+    <property name="out.doc.pdf" value="pdf/" />
+    <property name="out.doc.html" value="html/" />
+    <property name="out.doc.javahelp" value="javahelp/" />
+    <property name="stylesheet.params.html" value="${stylesheet.params} base.dir=${out.doc.html} root.filename="documentation" use.id.as.filename=1 html.ext=".php"" />
+    <property name="stylesheet.params.javahelp" value="${stylesheet.params} base.dir=${out.doc.javahelp}" />
+    <property name="stylesheet.params.pdf" value="${stylesheet.params} ulink.show=0"/>
+    <property name="out.doc.javahelp" value="javahelp/" />
+    <property name="doc.xml" value="haploview.xml" />
+    <property name="jhindexer" value="/opt/javahelp/javahelp/bin/jhindexer" />
+    <property name="doc.javahelp.jar" value="HaploHelp.jar" />
+
+    <path id="xslt.processor.classpath">
+      <pathelement path="/opt/saxon/saxon.jar" />
+      <pathelement path="${xslPath}/extensions/saxon653.jar"/>
+    </path>
+    <tstamp>
+      <format property="build.number" pattern="yyyyMMddHHmm"/>
+    </tstamp>
+</target>
+
+ <target name="doc-pdf" depends="init">
+    <delete dir="${out.doc.pdf}"/>
+    <java classname="${xslt.processor.class}"
+	      fork="yes"
+              failonerror="true">
+      <classpath refid="xslt.processor.classpath" />
+      <arg line="-o ${out.doc.fo}"/>
+      <arg line="${doc.xml} ${stylesheet.fo} ${stylesheet.params.pdf}" />
+    </java>
+
+    <exec executable="/opt/fop/fop" dir="${out.doc.pdf}" output="/dev/null">
+      <arg line="-fo haploview.fo -pdf haploview.pdf"/>
+    </exec>
+</target>
+
+<target name="doc-html" depends="init">
+    <delete dir="${out.doc.html}"/>
+
+    <java classname="${xslt.processor.class}"
+	      fork="yes"
+	      failonerror="true">
+      <classpath refid="xslt.processor.classpath" />
+	  <arg line="${doc.xml} ${stylesheet.html} ${stylesheet.params.html}" />
+    </java>
+
+    <exec executable="perl">
+         <arg line="add-web-headers.pl ${out.doc.html}"/>
+    </exec>
+
+</target>
+
+<target name="doc-javahelp" depends="init">
+    <delete dir="${out.doc.javahelp}"/>
+    <delete file="${doc.javahelp.jar}"/>
+    <java classname="${xslt.processor.class}"
+	      fork="yes"
+	      failonerror="true">
+      <classpath refid="xslt.processor.classpath" />
+	  <arg line="${doc.xml} ${stylesheet.javahelp} ${stylesheet.params.javahelp}" />
+    </java>
+
+    <exec executable="${jhindexer}" dir="${out.doc.javahelp}">
+      <arg line="."/>
+    </exec>
+
+    <jar destfile="${doc.javahelp.jar}" basedir="${out.doc.javahelp}" />
+
+</target>
+
+
+<target name="docs" depends="doc-javahelp,doc-html,doc-pdf">
+</target>
+
+<target name="main" depends="init">
+  <echo message="------------------------------------------------------------"/>
+  <echo message="welcome to haploview documentation build file"/>
+  <echo message="Usage: ant [target-name]"/>
+  <echo message="where [target-name] is one of the following targets:"/>
+  <echo message="doc-pdf --> renders docs in pdf"/>
+  <echo message="doc-javahelp --> renders docs in javahelp jar"/>
+  <echo message="doc-html --> renders docs in html"/>
+  <echo message="docs --> generates all 3 formats above"/>
+  <echo message="------------------------------------------------------------"/>
+</target>
+
+
+</project>
diff --git a/docs/haploview.xml b/docs/haploview.xml
new file mode 100755
index 0000000..e180e44
--- /dev/null
+++ b/docs/haploview.xml
@@ -0,0 +1,1948 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
+        "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
+        <!ENTITY geq "&#x2265;">
+        ]>
+<book>
+  
+    <bookinfo>
+        <title>Haploview Documentation</title>
+    </bookinfo>
+  
+    <chapter id="using">
+        <title>Using Haploview</title>
+    
+        <section id="load"> <title>Loading a Dataset</title>
+
+            <para>
+                Data can be loaded in six <link linkend="formats">formats</link>.
+                Ped and Haps files can also load an optional marker info file
+                and PLINK files normally require an accompanying map or binary map file.
+                Further options are presented on the load screen:
+	
+                <itemizedlist>
+
+                    <listitem>
+                        Haploview saves time by only computing pairwise LD
+                        statistics for markers within a certain distance of each
+                        other. The default is 500KB. Enter a value of zero to
+                        force all pairwise computations.
+                    </listitem>
+
+                    <listitem>
+                        Haploview excludes individuals with less than 50% complete
+                        genotypes. This threshold can be adjusted in the load
+                        dialog. Additional details about excluded individuals are
+                        available from the <link linkend="checks">marker check tab</link>.
+                    </listitem>
+
+                    <listitem>
+                        When loading a file dumped from the HapMap project
+                        website, it is possible to automatically display SNP and
+                        gene tracks from the HapMap above the data by checking the
+                        "Download and show HapMap info track" box. More information
+                        is available with the <link linkend="tracks">LD Display</link> help. [hapmap file only]
+                    </listitem>
+
+                    <listitem>
+                        If you wish to perform association tests, you must inform
+                        the program now and select either family trios or
+                        case/controls. For family datasets a standard TDT or
+                        parenTDT are available.  More details are available under
+                        <link linkend="association">association</link>. [pedfile
+                        only]
+                    </listitem>
+
+                    <listitem>
+                        If your data is from the X chromosome in the linkage formats,
+                        tick the box so that Haploview will correctly process your data.
+                        In other formats, select the X chromosome in the dropdown menu.
+                        X chromosome data is not supported by the phased haplotype format.
+                        All functionality now works with the X chromosome.
+                    </listitem>
+
+                    <listitem>
+                        Haploview will maximize the information available from a
+                        pedigree for both LD analyses and association tests. For the
+                        former it creates a maximal set of unrelated individuals,
+                        using trio data only for obligate parent/offspring
+                        phasing. For TDT association testing, all available
+                        transmissions from parent-offspring will be used. More
+                        detailed information about specific situations is available
+                        in the <ulink
+                            url="http://www.broad.mit.edu/mpg/haploview/faq.php">FAQ</ulink>.
+                    </listitem>
+
+                    <listitem>
+                        Haploview can be configured to support proxy host settings using
+                        the "Proxy Settings" button on the load screen.
+                    </listitem>
+
+                </itemizedlist>
+            </para>
+
+            <para>
+                Haploview allocates 512MB of memory by default. This is usually
+                sufficient to handle datasets with several thousand
+                markers. If you are running the program on very large datasets
+                (>20,000 markers) you may need to force more memory (presuming
+                your computer has sufficient resources available). This can be
+                accomplished using the following command:
+            </para>
+            <screen>java -jar Haploview.jar -memory 2000</screen>
+
+            <para>
+                Where "2000" in this case specifies 2000 megabytes of memory and
+                can be adjusted as necessary. Previous versions of Haploview
+                required a slightly different command to adjust available
+                memory, which still works: </para>
+
+            <screen>java -Xmx2000M -cp Haploview.jar edu/mit/wi/haploview/Haploview</screen>
+
+        </section>
+        <section id="checks">
+            <title>Data Quality Checks</title>
+
+            <section>
+                <title>Marker Checks</title>
+
+                <para>
+                    After loading a file, Haploview shows some basic data
+                    quality checks for the markers. Markers are filtered out
+                    based on some default criteria which can be adjusted as
+                    necessary. Markers can be added or removed from analyses by
+                    hand via the checkboxes. The data in this table can be
+                    sorted by clicking on any of the column headers. Compound
+                    sorts can be done by clicking on the first column header
+                    then CTRL clicking on the next one.</para>
+
+                <itemizedlist>
+                    <listitem><emphasis role="bold">#</emphasis> is the marker number.</listitem>
+                    <listitem><emphasis role="bold">Name</emphasis> is the marker ID specified (only if an info file is loaded).</listitem>
+                    <listitem><emphasis role="bold">Position</emphasis> is the marker position specified (only if an info file is loaded).</listitem>
+                    <listitem><emphasis role="bold">ObsHET</emphasis> is the marker's observed heterozygosity.</listitem>
+                    <listitem><emphasis role="bold">PredHET</emphasis> is the marker's predicted heterozygosity (i.e. 2*MAF*(1-MAF)).</listitem>
+                    <listitem><emphasis role="bold">HWpval</emphasis> is the Hardy-Weinberg equilibrium p value, which is the probability that its deviation from H-W equilibrium could be explained by chance.</listitem>
+                    <listitem><emphasis role="bold">%Geno</emphasis> is the percentage of non-missing genotypes for this marker.</listitem>
+                    <listitem><emphasis role="bold">FamTrio</emphasis> is the number of fully genotyped family trios for this marker (0 for datasets with unrelated individuals).</listitem>
+                    <listitem><emphasis role="bold">MendErr</emphasis> is the number of observed Mendelian inheritance errors (0 for datasets with unrelated individuals).</listitem>
+                    <listitem><emphasis role="bold">MAF</emphasis> is the minor allele frequency (using founders only) for this marker.</listitem>
+                    <listitem><emphasis role="bold">Alleles</emphasis> are the major and minor alleles for this marker.</listitem>
+                    <listitem><emphasis role="bold">Rating</emphasis> is checked if the marker passes all the tests and unchecked if it fails one or more tests (highlighted in red).</listitem>
+                </itemizedlist>
+
+                <para>
+                    You can adjust the filtering thresholds and click "Rescore"
+                    to refilter the markers using the new values. These thresholds
+                    can be reset to values by clicking "Reset Values". Markers can
+                    also be selected/unselected by hand by clicking the "Rating"
+                    checkbox or using the "Select All" and "Deselect All" buttons.
+                    Any marker which fails one of the quality tests will have the
+                    relevant field(s) highlighted in red.
+                </para>
+            </section>
+      
+            <section >
+                <title>Duplicate Markers </title>
+                <para>
+                    If two markers in an input file have the same chromosomal
+                    position, Haploview will ignore the less completely
+                    genotyped marker by default and highlight both in yellow on
+                    the check markers panel. When running in nogui mode
+                    Haploview always ignores the less completely genotyped
+                    version of two markers with the same position. If you want
+                    to use both from the command line, you'll need to adjust one
+                    of the positions.
+                </para>
+
+                <para>
+                    If two markers in an input file have the same name, Haploview renames the second one in the file by appending ".X" to the filename, where "X" is a running integer count starting with 1. So if you have marker1, marker1 and marker2, Haploview would adjust this to: marker1, marker1.1 and marker2. Note that if the markers with the same name have different positions, Haploview won't deselect any of them; if they do have identical positions, it will filter all but one out a [...]
+            </section>
+      
+            <section>
+                <title>Filtered Individuals</title>
+                <para>
+                    The top of the tab contains information about individuals
+                    filtered during the loading of the file. It will show
+                    overview information about the number of singletons and
+                    trios used and the number of independent families
+                    loaded. Further information can be shown by clicking the
+                    "Advanced Views" button.  This will present a list of up to
+                    four buttons depending on the nature of the loaded dataset.
+                    The "Individual Summary" button will show genotyping percentage
+                    by family and individual.  If individuals have been excluded,
+                    the "Excluded Individuals" button will present a list of
+                    excluded individuals as well as the reason for exclusion.
+                    If Mendel errors are present, view detailed Mendel error information
+                    by clicking the "Mendel Errors" button.  If male heterozygotes
+                    are present in x chromosome data, information about them can be viewed
+                    by clicking "Male Heterozygotes".  All of these advanced views
+                    can also be exported using the "Export to File" button.  Details
+                    about individual filtering can be found in the
+                    <ulink url="http://www.broad.mit.edu/mpg/haploview/faq.php#filter">FAQ</ulink>.
+                </para>
+            </section>
+        </section>
+    
+        <section id="lddisplay"> <title>LD Display</title>
+      
+            <section>
+                <title>Perusing the LD Display</title>
+
+                <itemizedlist>
+                    <listitem>
+                        <para> The color scheme option (Display menu) allows you
+                            to choose among several LD color schemes. The following
+                            tables provide details on the color schemes, and a key to
+                            the meaning of the currently selected scheme can be
+                            dropped down from the "Key" menu in the upper right corner
+                            of the screen.</para>
+
+                        <table frame="all"><title>Standard Color Scheme</title>
+                            <tgroup cols="3" align="center">
+                                <tbody>
+                                    <row>
+                                        <entry></entry>
+                                        <entry>D' < 1</entry>
+                                        <entry>D' = 1</entry>
+                                    </row>
+                                    <row>
+                                        <entry>LOD < 2</entry>
+                                        <entry>white</entry>
+                                        <entry>blue</entry>
+                                    </row>
+		  
+                                    <row>
+                                        <entry>LOD ≥ 2</entry>
+                                        <entry>shades of pink/red</entry>
+                                        <entry>bright red</entry>
+                                    </row>
+                                </tbody>
+                            </tgroup>
+                        </table>
+	    
+                        <table frame="all"><title>Confidence Bounds Color Scheme</title>
+                            <tgroup cols="2" align="center">
+                                <tbody>
+                                    <row>
+                                        <entry>Strong Evidence of LD</entry>
+                                        <entry>dark grey</entry>
+                                    </row>
+                                    <row>
+                                        <entry>Uninformative</entry>
+                                        <entry>light grey</entry>
+                                    </row>
+                                    <row>
+                                        <entry>Strong Evidence of Recombination</entry>
+                                        <entry>white</entry>
+                                    </row>
+                                </tbody>
+                            </tgroup>
+                        </table>
+	    
+                        <table><title>r<superscript>2</superscript> Color Scheme</title>
+                            <tgroup cols='2' align="center">
+                                <tbody>
+                                    <row>
+                                        <entry>r<superscript>2</superscript> = 0</entry>
+                                        <entry>white</entry>
+                                    </row>
+                                    <row>
+                                        <entry>0 < r<superscript>2</superscript> < 1 </entry>
+                                        <entry>shades of grey</entry>
+                                    </row>
+                                    <row>
+                                        <entry>r<superscript>2</superscript> = 1 </entry>
+                                        <entry>black</entry>
+                                    </row>
+                                </tbody>
+                            </tgroup>
+                        </table>
+	    
+                        <table> <title>Alternate D'/LOD Color Scheme</title>
+                            <tgroup cols='3' align="center">
+                                <tbody>
+                                    <row>
+                                        <entry></entry>
+                                        <entry>Low D'</entry>
+                                        <entry>High D'</entry>
+                                    </row>
+                                    <row>
+                                        <entry>Low LOD</entry>
+                                        <entry>white</entry>
+                                        <entry>shades of pink</entry>
+                                    </row>
+                                    <row>
+                                        <entry>High LOD</entry>
+                                        <entry>white</entry>
+                                        <entry>black </entry>
+                                    </row>
+                                </tbody>
+                            </tgroup>
+                        </table>
+	    
+                        <para><emphasis>(r<superscript>2</superscript> and Alt D'/LOD courtesy of Will Fitzhugh)</emphasis></para>
+
+                        <table> <title>4 Gamete Color Scheme</title>
+                            <tgroup cols='2' align="center">
+                                <tbody>
+                                    <row>
+                                        <entry>4 distinct 2-marker haplotypes</entry>
+                                        <entry>white</entry>
+                                    </row>
+                                    <row>
+                                        <entry>< 4 distinct 2-marker haplotypes</entry>
+                                        <entry>black</entry>
+                                    </row>
+                                </tbody>
+                            </tgroup>
+                        </table>
+                    </listitem>
+
+
+                    <listitem>
+                        In order to help keep the display uncluttered, D prime
+                        values of 1.0 are never shown (the box is empty). These
+                        values can be switched on or off with the "Show LD values"
+                        option in the Display menu.  </listitem>
+
+                    <listitem>
+                        The zoom option (Display menu) allows you to select one of
+                        three zoom modes. The two zoomed out versions can be
+                        useful for browsing large datasets.</listitem>
+                    <listitem>
+                        Large datasets also show a "map" in the lower left corner
+                        which gives an overview of the D prime display and allows
+                        you to navigate quickly. Clicking on an area of the map
+                        will cause the main display to jump to that area. This map
+                        also shows the currently defined blocks as small black
+                        lines across the top.</listitem>
+                    <listitem>
+                        Markers with additional notes (as loaded from the <link linkend="info">info</link>
+                        file) are highlighted (the names are green in the
+                        zoomed-in view and the lines from the SNP position to the
+                        LD chart are green in the zoomed-out view. Details can be
+                        viewed by right clicking on the marker number (as
+                        mentioned below).</listitem>
+
+                    <listitem>
+                        Right clicking on the marker number (or the equivalent
+                        space in the zoomed out views) shows the marker name,
+                        minor allele frequency and any additional notes specified
+                        in the <link linkend="info">info</link> file. This can be
+                        especially helpful in the zoomed out views which do not
+                        display marker names. The last such piece of popup
+                        information clicked will be shown at the top of the LD
+                        plot. This reminder can be dismissed by left clicking
+                        anywhere on the LD plot.</listitem>
+
+                    <listitem>
+                        Right clicking on any pairwise LD comparison will show a
+                        more detailed summary of the LD between the two markers in
+                        question. This information is also shown at the top of the
+                        screen as described above and can be dismissed by left
+                        clicking anywhere on the LD plot.</listitem>
+	  
+                </itemizedlist>
+            </section>
+            <section id="tracks">
+                <title>Additional Data Tracks </title>
+                <section>
+                    <title>Analysis Track </title>
+
+                    <para>
+                        A graph of any variable versus chromosomal location can be
+                        added above the LD plot with the "Load Analysis Track"
+                        option. Simply create a file with two columns:
+                        <position> <value> . Haploview will plot the
+                        values in a continuous line along the top of the screen,
+                        along with a scale bar on the Y-axis. You can load several
+                        analysis tracks which will all be plotted in the same box
+                        at the top of the LD plot.  </para>
+
+                </section>
+                <section>
+                    <title> HapMap Gene/SNP Track</title>
+                    <para>
+                        The "Download HapMap info track" option (with an internet
+                        connection) allows you to connect to the HapMap Project
+                        server and download and display a track with HapMap
+                        genotyped SNPs and gene names. If an info file is
+                        specified, the default boundaries are the positions of the
+                        first and last markers (which is only valid if the info
+                        file is in genomic coordinates). You must specify the
+                        proper chromosome and genomic build in the dialog box. If you are using a
+                        file downloaded from the HapMap website the program will
+                        specify the correct default chromosome, build and start/end
+                        positions.
+	    
+                        This track display can be configured with the "HapMap Info
+                        Track Options" item in the "Display" menu. Available
+                        tracks include HapMap SNPs, Entrez genes, recombination rate,
+                        contigs, and GC content.
+	    
+                    </para>
+
+                </section>
+            </section>
+        </section>
+        <section>
+            <title>Blocks and Haplotypes</title>
+            <section id="blocks"> <title>Blocks</title>
+                <para>Haploview generates blocks whenever a file is opened, but
+                    these blocks can be edited and redefined in a number of
+                    ways. In the Analysis menu, you can clear all the blocks in
+                    order to start over, define blocks based on one of several
+                    automated methods or customize the parameters of those
+                    algorithms. Additionally, the blocks can be edited by hand.
+                </para>
+
+                <section>
+                    <title>Confidence Intervals [DEFAULT]</title>
+	  
+                    <para>The default algorithm is taken from Gabriel et al, Science,
+                        2002. 95% confidence bounds on D prime are generated and
+                        each comparison is called "strong LD", "inconclusive" or
+                        "strong recombination". A block is created if 95% of
+                        informative (i.e. non-inconclusive) comparisons are "strong
+                        LD". This method by default ignores markers with MAF <
+                        0.05. The MAF cutoff and the confidence bound cutoffs can be
+                        edited by choosing "Customize Block Definitions" (Analysis
+                        menu). This definition allows for many overlapping blocks to
+                        be valid. The default behavior is to sort the list of all
+                        possible blocks and start with the largest and keep adding
+                        blocks as long as they don't overlap with an already
+                        declared block.
+                    </para>
+	  
+                </section>
+                <section>
+                    <title>Four Gamete Rule </title>
+	  
+                    <para>This is a variant on
+                        the algorithm described in Wang et al, Am. J. Hum. Genet.,
+                        2002. For each marker pair, the population frequencies of
+                        the 4 possible two-marker haplotypes are computed. If all 4
+                        are observed with at least frequency 0.01, a recombination
+                        is deemed to have taken place. Blocks are formed by
+                        consecutive markers where only 3 gametes are observed. The
+                        1% cutoff can be edited to make the definition more or less
+                        stringent.
+                    </para>
+	  
+                </section>
+                <section>
+                    <title>Solid Spine of LD </title>
+	  
+                    <para>This internally developed method
+                        searches for a "spine" of strong LD running from one marker
+                        to another along the legs of the triangle in the LD chart
+                        (this would mean that the first and last markers in a block
+                        are in strong LD with all intermediate markers but that the
+                        intermediate markers are not necessarily in LD with each other).
+                    </para>
+	  
+                </section>
+	
+                <para>Markers can be removed from blocks by clicking on the marker
+                    number (along the top of the D prime graph). Blocks can be
+                    defined by hand by clicking and dragging along the marker
+                    number row. Any block which overlaps with an existing block
+                    will take precedence and delete the existing block.
+                </para>
+
+            </section>
+            <section id="haplotypes"> <title>Haplotypes</title>
+                <section>
+                    <title>Display</title>
+
+                    <para> View haplotypes for selected blocks by clicking on
+                        the "Haplotypes" tab or selecting "Haplotypes" from the
+                        Display menu. Haplotypes are estimated using an accelerated
+                        EM algorithm similar to the partition/ligation method
+                        described in Qin et al, 2002, Am J Hum Genet. This creates
+                        highly accurate population frequency estimates of the phased
+                        haplotypes based on the maximum likelihood as determined
+                        from the unphased input.
+                    </para>
+
+                    <para>The haplotype display shows each haplotype in a block with
+                        its population frequency and connections from one block to the
+                        next. In the crossing areas, a value of multiallelic D' is
+                        shown. This represents the level of recombination between the two
+                        blocks. Note that the value of multiallelic D' is computed for
+                        only the haplotypes ("alleles") currently displayed. This usually
+                        does not have a strong effect, as the rare haplotypes contribute
+                        only slightly to the overall value. Above the haplotypes are
+                        marker numbers along with a tick beneath haplotype tag SNPs
+                        (htSNPs).
+                    </para>
+	  
+                </section>
+                <section>
+                    <title>Display Controls </title>
+
+                    <para>The display can be edited using the controls at the
+                        bottom of the screen to display only more common haplotypes
+                        or to adjust the connecting lines. By default, alleles are
+                        displayed using A,C,G,T along with the special symbol 'X'
+                        which represents a fairly rare situation in which only one
+                        allele is unambiguously observed in phased data. The 'X'
+                        represents the allele of unknown identity. The display can
+                        also be changed to show the alleles numerically from 1-4
+                        with 8 being the equivalent of 'X', or as blue and red
+                        boxes, with blue being the major allele and red the minor.
+                    </para>
+	  
+                </section>
+                <section>
+                    <title>Tag SNPs </title>
+
+                    <para>Haplotype tag SNPs are no longer displayed by default
+                        in the Haplotypes tab. It is recommended that all tagging be
+                        done via the Tagger tab. The block-by-block tags can be
+                        displayed by ticking the "Show tags in blocks" option in the
+                        Display menu.  </para>
+	  
+                </section>
+	
+            </section>
+      
+        </section>
+
+        <section id="tagger">
+            <title>Tagger</title>
+            <section><title>Introduction</title>
+
+                <para>
+                    We have developed a tagging strategy that combines the simplicity of
+                    pairwise methods with the potential efficiency of multimarker
+                    approaches. We avoid overfitting and unbounded haplotype tests in the
+                    association phase by (<emphasis>a</emphasis>) using only those multiallelic combinations in
+                    which the alleles are themselves in strong LD, and (<emphasis>b</emphasis>) explicitly
+                    recording the allelic hypotheses that are to be tested in the
+                    subsequent association analysis. Attractive practical features include
+                    the ability to force in or exclude sets of tags.</para>
+
+                <para>
+                    Haploview is based on Paul de Bakker's <emphasis>Tagger</emphasis>. It and more information are available at the <ulink url="http://www.broad.mit.edu/mpg/tagger/">Tagger website</ulink>. There are a number of differences between the implementations, although they are constructed around the same concept. Tagger currently searches a much broader space of available multi-marker tests (up to 6-mers) whereas Haploview allows only 2- or 3-marker tests in the interest of com [...]
+            </section>
+
+            <section><title>Features</title>
+                <para>Haploview's Tagger operates in either pairwise or
+                    aggressive mode. In either case it begins by selecting a
+                    minimal set of markers such that all alleles to be captured
+                    are correlated at an r<superscript>2</superscript> greater
+                    than a user-editable threshold with a marker in that
+                    set. Certain markers can be forced into the tag list or
+                    explicity prohibited from being chosen as tags. You can also
+                    specify which markers in the dataset you want to be captured.</para>
+
+                <para>Aggressive tagging introduces two additional steps. The
+                    first is to try to capture SNPs which could not be captured in
+                    the pairwise step (N.B. these must have been "excluded" since
+                    otherwise they would simply be chosen to capture themselves)
+                    using multi-marker tests constructed from the set of markers
+                    chosen as pairwise tags. After this, it tries to "peel back"
+                    the tag list by replacing certain tags with multi-marker
+                    tests. Tagger avoids overfitting by only constructing
+                    multi-marker tests from SNPs which are in strong LD with each
+                    other, as measured by a pairwise LOD score. This LOD cutoff
+                    can be adjusted to loosen or tighten this requirement; in
+                    general, the default cutoff of 3.0 is appropriate for
+                    selecting tags from a HapMap-sized reference panel of 120
+                    chromosomes.</para>
+
+                <para>Much more information about the development of this
+                    algorithm is available at the <ulink
+                        url="http://www.broad.mit.edu/mpg/tagger/">Tagger
+                    website</ulink>.</para>
+
+            </section>
+	
+            <section><title>Tagger Configuration Panel</title>
+
+                <para><emphasis role="bold">N.B.</emphasis> Haploview's Tagger requires either an info file or a hapmap style input file, because it references the marker names specified in those files. If you load a pedigree or phased haplotypes input file without an info file, the Tagger panels will not be available.</para>
+
+                <para>This panel shows all SNPs available for tag
+                    selection. SNPs which are deselected in the Check Markers tab will not
+                    be in this list. There are three checkboxes for each SNP:</para>
+
+                <glosslist>
+                    <glossentry><glossterm>Force Include</glossterm>
+                        <glossdef>
+                            <para>Checking this box will force this SNP to be chosen as a tag SNP.</para>
+                        </glossdef>
+                    </glossentry>
+
+                    <glossentry><glossterm>Force Exclude</glossterm>
+                        <glossdef>
+                            <para>Checking this box will prohibit this SNP from being chosen as a tag SNP.</para>
+                        </glossdef>
+                    </glossentry>
+
+                    <glossentry><glossterm>Capture this Allele?</glossterm>
+                        <glossdef>
+                            <para>If this box is checked, Haploview will include this SNP in the list
+                                of alleles to be captured by the chosen tag set.</para>
+                        </glossdef>
+                    </glossentry>
+                </glosslist>
+
+                <para><emphasis role="bold">N.B.</emphasis> The include and exclude checkboxes are mutually exclusive, and
+                    "Capture this Allele" must be checked in order to either include or
+                    exclude a marker.</para>
+
+                <para>Directly below the marker list are buttons to quickly manipulate the table above.  Use "Include All"
+                    to check all of the "Force Include" boxes, and "Exclude All" to check all of the "Force Exclude" boxes.
+                    "Uncapture All" will uncheck the "Capture this Allele?" column for all markers, "Exclude A/T and C/G SNPs"
+                    will exclude check the "Force Exclude" boxes for SNPs with strand issues, and "Reset Table"
+                    will return the table to its initial state. Beneath these buttons are several additional tagging options.
+                    You can choose from among pairwise and two aggressive tagging strategies discussed above.
+                    You can also set the r<superscript>2</superscript> and LOD thresholds as previously mentioned.
+                    Additionally, you can specify the maximum number of tags to pick, as well as the minumum distance
+                    (in base pairs) between picked tags. You can load a set of SNPs to include or exclude using the
+                    "Load Includes" and "Load Excludes" buttons. These buttons take in a file with a single column
+                    of SNPs to include or exclude.  The "Alleles to Capture" button also takes in a file with a single column of SNPs to be captured.
+                    Design scores can also be loaded in using the "Design Scores" button. Design score files should contain
+                    two columns containing the SNP and the design score to assign to that SNP. A minimum design score threshold
+                    can also be specified. All of the Tagger thresholds can be reset to their default values using the
+                    "Reset Thresholds" button. Clicking "Run Tagger" will run the tagging algorithm. When finished it will
+                    switch from the Configuration to the Results Panel.</para>
+            </section>
+
+            <section><title>Tagger Results Panel</title>
+                <para>This panel is split into a "Tests" section on the left and a
+                    marker-by-marker report on the right. The marker report lists all
+                    SNPs, the test which best captures them, and their r<superscript>2</superscript> with that test.
+                    SNPs which were unchecked from the "Capture this allele?" list on the
+                    Configuration panel are greyed out. SNPs which could not be
+                    successfully tagged are shown in red.</para>
+
+                <para>The first list in the "Tests" section shows all the tests (both single
+                    marker and multi-marker alleles) chosen by Haploview. Selecting tests
+                    in this list will show which alleles are captured by those tests in
+                    the second list in the panel. Beneath these lists is a summary of the
+                    tagging results.</para>
+
+                <glosslist>
+                    <glossentry><glossterm>Captured N alleles with mean r<superscript>2</superscript> of X.</glossterm>
+                        <glossdef>
+                            <para>This shows how many of the SNPs in the dataset have been
+                                successfully tagged by the set of chosen tests. The mean r<superscript>2</superscript>
+                                represents the mean for only those SNPs successfully captured.</para>
+                        </glossdef>
+                    </glossentry>
+
+                    <glossentry><glossterm>
+                        Captured N percent of alleles with r<superscript>2</superscript> >0.8</glossterm>
+                        <glossdef>
+                            <para>This shows what fraction of the alleles captured by the tests have
+                                an r<superscript>2</superscript> >= 0.8. Of course, if your tagging r<superscript>2</superscript> threshold is >= 0.8 this value will always be 100%.</para>
+                        </glossdef>
+                    </glossentry>
+
+                    <glossentry><glossterm>Using N SNPs in M tests.</glossterm>
+                        <glossdef>
+                            <para>This shows that N unique SNPs have been chosen to create M tests,
+                                which can either be one of the set of N SNPs or some combination of
+                                those SNPs.</para>
+                        </glossdef>
+                    </glossentry>
+                </glosslist>
+
+                <para>The "Dump Tests File" button exports a <link
+                        linkend="taggertests">file with the list of tests</link> in
+                    the format used by Haploview's custom association test file
+                    and Tagger's export. This file contains the list of all tests
+                    (single SNPs and multi-marker tests) selected by Tagger for
+                    subsequent association analysis. In pairwise-only tagging this
+                    file will be identical to the "Tags" file, below.</para>
+
+                <para>The "Dump Tags File" button exports a <link
+                        linkend="taggertags">file with the list of Tag SNPs</link> in the
+                    format used by Haploview's custom association test file and
+                    Tagger's export. It is the concise list of SNPs selected by Tagger
+                    for genotyping. In pairwise-only tagging this file will be
+                    identical to the "Tests" file, above.</para>
+
+                <para>The "Export Tab to Text" option in the File menu will export a <link linkend="taggerout">summary
+                    file</link> showing the best tag for each marker and the list of tests along
+                    with the alleles tagged by each test.</para>
+            </section>
+        </section>
+	
+
+        <section id="association">
+            <title>Association Tests</title>
+            <para>
+                If selected when <link linkend="load">loading</link> the data,
+                Haploview computes single locus and multi-marker haplotype
+                association tests. For case/control data, the chi square and
+                p-value for the allele frequencies in cases vs. control are
+                shown. For family trios, all probands (affected individual
+                with genotyped parents) are used to compute TDT values. If the
+                parenTDT option is selected, additional information is gained
+                from parental phenotypes. More information about this method
+                can be found in the Citations list in the <link
+                    linkend="about">About Haploview</link> section.  </para>
+            <para>
+                The haplotype association test is performed on the set of blocks
+                selected on the LD and haplotype tabs. Results are shown only for
+                those haplotypes above the display threshold on the haplotype
+                tab. Counts for both TDT and case control association tests are
+                obtained by summing the fractional likelihoods of each individual for
+                each haplotype. In other words, if a particular individual has been
+                determined by the EM to have a 40% likelihood of haplotype A and 60%
+                likelihood of haplotype B, 0.4 and 0.6 would be added to the counts
+                for A and B respectively.
+            </para>
+            <para>
+                Additional information about the way in which pedigrees are filtered
+                for TDT purposes can be found in the <ulink url="http://www.broad.mit.edu/mpg/haploview/faq.php#peds">FAQ</ulink>.
+            </para>
+      
+            <para>
+                Haploview is not intended to be the only way of testing association
+                results, but to provide a straightforward way to do simple association
+                tests. It's always a good idea to try out multiple approaches to
+                analyzing your data.
+            </para>
+
+            <para>You can load a set of custom association tests in the format exported by Haploview and Tagger. This format is discussed <link linkend="custassocfile">below</link>.</para>
+
+
+        </section>
+
+        <section>
+            <title>Permutation Testing</title>
+            <para>Haploview provides a framework for permuting your association results in order to obtain a measure of significance corrected for multiple testing bias. You can choose to permute one of several test sets:</para>
+
+            <glosslist>
+                <glossentry><glossterm>Single Markers Only</glossterm>
+                    <glossdef>
+                        <para>Permute just association tests to the individual SNPs in your dataset.</para>
+                    </glossdef>
+                </glossentry>
+
+                <glossentry><glossterm>Single Markers and Haplotypes in Blocks</glossterm>
+                    <glossdef>
+                        <para>Permute the individual SNPs as above, along with all the haplotypes shown in the Haplotypes tab.</para>
+                    </glossdef>
+                </glossentry>
+
+                <glossentry><glossterm>Haplotypes in blocks only</glossterm>
+                    <glossdef>
+                        <para>Permute only the haplotypes in the Haplotypes tab, ignoring the single marker results.</para>
+                    </glossdef>
+                </glossentry>
+
+                <glossentry><glossterm>Custom Tests from File</glossterm>
+                    <glossdef>
+                        <para>Permute the set of tests loaded from an external file. Note that this choice is only available if you provided a <link linkend="custassocfile">tests file</link> when you loaded your dataset.</para>
+                    </glossdef>
+                </glossentry>
+            </glosslist>
+            <para>Specify how many permutations to do and press the "Do Permutations" button to start the permutations. While the permutations are running, Haploview shows the following:</para>
+            <itemizedlist>
+                <listitem>A progress bar which tracks the progress of the permutations.</listitem>
+                <listitem>The highest permuted chi square so far.</listitem>
+                <listitem>The fraction of permutations whose strongest association exceeds the best observed chi square.</listitem>
+            </itemizedlist>
+
+            <para>You can stop the permutations at any time with the "Stop" button. Once the permutations are complete, Haploview displays:</para>
+
+            <itemizedlist>
+                <listitem>A table listing all tests (single SNP and haplotype) along with their association chi squares and permuted p-values.</listitem>
+                <listitem>A histogram of the highest chi square from each of the permutations.</listitem>
+            </itemizedlist>
+
+            <para>You can save the <link linkend="permout">permutation summary</link> by using the "Export Tab to Text" option in the File menu.</para>
+        </section>
+
+        <section id="plink">
+            <title>PLINK</title>
+            <para>
+                Haploview can now take in PLINK outputs.  These files require a separate map
+                file or binary map file corresponding to each marker in the output file in order to load.
+                Any output file from PLINK can be loaded provided that it contains a SNP column
+                corresponding to the map file.  The map file can contain SNPs that are not present in
+                the associated output file and the SNPs need not be in the same order in the two files.
+                PLINK output is displayed in a single tab containing a sortable table of results
+                and a variety of filtering options below the table.  In SNP-based files, you can also
+                load in additional columns using the "Load Additional Results" button.</para>
+
+            <para>
+                Filtering options include chromosome and position.  The "Filter" dropdown box can be used to
+                further filter on any unrecognized columns in the table.  Multiple filters can be applied consecutively
+                by changing the filter options and pressing the "Filter" button.  Currently active filters can be viewed
+                using the "View X Active Filters" button where X is the number of currently active filters.  This will open
+                a small popup window where filters can be viewed, added, or removed as needed.  All filters are jointly applied
+                (logically equivalent to an "AND" operator as opposed to an "OR").  You can also go directly to a specified
+                marker name in the table using the "Go" button on the second row, and remove columns from the table by selecting
+                a column from the "Remove Columns:" dropdown menu and using the adjacent "Remove" button.  The "Reset" button can
+                be used to revert the table and filters back to their original state (though the sorting state is retained).
+                Please note that non-SNP based files which can be loaded in without a map file do not have the chromosomal or
+                marker filters.
+            </para>
+
+            <para>
+                You can use the Fisher method for combining p-values in your PLINK results using the "Combine P-Values" button
+                under the fitering options.  This will bring up a dialog that allows you to choose between 2 and 5 p-value columns
+                for use in the Fisher-combined algorithm.  Once you click "Go", a new column designated as "P_COMBINED" will appear
+                as the last column in the table.
+            </para>
+
+            <para>
+                You can create graphical plots from your results table using the "Plot" button under the filtering options.
+                Use the Plot Options dialog to specify a title for your plot and various plotting options.  At the top of the dialog,
+                select an optional title for your plot.  Then choose which columns to use on the X and Y axes of the plot as well as the scale
+                for each using the appropriate drop-down boxes.  If you have loaded a SNP based file with an accompanying map file,
+                selecting "Chromosomes" as the X-Axis will plot your results across the chromosomes and will color code them separately.
+                You can also select "Index" for either axis which will simply plot sequential numbers for each result shown in the table for
+                that axis.  Additionally, you can specify up to two thresholds for use in the plot, along with which axis to place them on and
+                which direction they should be.  Threshold 1 or the "Suggestive" threshold for the -log10 scale will create a blue line and
+                Threshold 2 or the "Significant" threshold for the -log10 scale will create a red line.  Datapoints which pass the thresholds
+                will be larger in size then the standard datapoints.  Directly beneath the thresholds, you can choose the base datapoint size
+                for results in your plot using the dropdown box.  To the right of that, you can use the optional "Color Key" dropdown menu to
+                select a column to be used as a coloring key in the plot.  Please note that this functionality will only work when the chosen
+                color key column has 50 or fewer unique values.  On the next line, you can use the "Show Gridlines?" checkbox to select whether
+                to show or hide the gridlines in the plot.  To the right of that, you can specify the initial width and height of the plot (in pixels).
+                Finally, on the next line you can use the "Export to SVG" checkbox and browse to a location to save your plot to a high quality SVG file.
+                Please note that the SVG option generally takes a great deal of processing power and memory and should only be used when very high quality
+                images are required.  In most cases, you can save the plot images as PNG files using the right click context menu described below.
+                
+                
+            </para>
+
+            <para>
+                Once the plot has loaded, you can hover over individual data points to see
+                information about that point in a tooltip popup.  SNP based data will display the corresponding marker name, chromosome, position,
+                and the value that is being plotted.  Non SNP-Based data will either display the corresponding FID and IID values if they are
+                available, or simply the X and Y values for that datapoint.  You can also click a datapoint to be taken back to that result
+                in the results table.  For many more plotting options including export options, right click anywhere on the plot.
+            </para>
+
+            <para>
+                By highlighting a specific result in the table and clicking "Go to Selected Region", you can bring
+                up a dialog to automatically fetch that region from the HapMap website and load it into Haploview.
+                The dialog allows you to specify the size of the region and the HapMap analysis panel that you wish to
+                download.  You can also optionally choose to annotate the columns from the PLINK tab to annotate in the LD Plot.
+                Once the region has been successfully loaded into Haploview, the initially selected marker will be highlighted in blue
+                on the Check Marker and PLINK tabs.  SNPs that appear in the PLINK tab are now marked in green on the LD Plot,
+                and the specific result that you specified is further highlighted in white.  You can view the annotated data from the PLINK
+                table by right clicking on the marker number in the LD Plot.  You can use the "Force in PLINK SNPs" button in the
+                Tagger panel to force include all the SNPs contained in the PLINK results tab.  Please note that using "Go to Selected Region"
+                requires an active internet connection.
+            </para>
+	 
+            <para>
+                The "Export Tab to Text" option in the File menu will export a text file containing the current view of the
+                results table.  This file will preserve any sorting and filtering that you've enabled in the table.
+            </para>
+
+        </section>
+
+    </chapter>
+
+
+    <chapter id="files">
+        <title>Files</title>
+        <section id="formats">
+            <title>Input File Formats</title>
+      
+            <para>Haploview currently accepts input data in five formats,
+                standard linkage format, completely or partially phased
+                haplotypes, <ulink url="http://www.hapmap.org/">HapMap Project</ulink> data dumps, PHASE format, and PLINK outputs.
+                The program can also automatically fetch phased HapMap data off the HapMap website.
+                It also takes in a separate file with marker position information, as well as several <link linkend="auxinput">auxiliary input files</link>, described below. The four formats
+                are explained in depth below.</para>
+      
+            <glosslist>
+                <glossentry id="ped"><glossterm>Linkage Format</glossterm>
+                    <glossdef>
+	    
+                        <para>
+                            Linkage data should be in the Linkage Pedigree (pre MAKEPED)
+                            format, with columns of family, individual, father, mother,
+                            gender, affected status and genotypes. The file should not
+                            have a header line (i.e. the first line should be for the
+                            first individual, not the names of the columns). Please note
+                            that Haploview can only interpret biallelic markers —
+                            markers with greater than two alleles (e.g. microsatellites)
+                            will not work correctly. A sample line from such a file
+                            might look something like:</para>
+
+<screen>3    12    8    9    1    2    1 2    3 3    0 0    4 2
+a     b    c    d    e    f    -----------g------------ </screen>
+
+                        <glosslist>
+                            <glossentry><glossterm>(a)  pedigree name</glossterm>
+                                <glossdef><para>A unique alphanumeric identifier for this individual's family. Unrelated individuals should not share a pedigree name.</para></glossdef></glossentry>
+                            <glossentry><glossterm>(b)  individual ID</glossterm>
+                                <glossdef><para>An alphanumeric identifier for this individual. Should be unique within his family (see above).</para></glossdef></glossentry>
+                            <glossentry><glossterm>(c)  father's ID</glossterm>
+                                <glossdef><para>Identifier corresponding to father's individual ID or "0" if unknown father. Note that if a father ID is specified, the father must also appear in the file.</para></glossdef></glossentry>
+                            <glossentry><glossterm>(d)  mother's ID</glossterm>
+                                <glossdef><para>Identifier corresponding to mother's individual ID or "0" if unknown mother Note that if a mother ID is specified, the mother must also appear in the file.</para></glossdef></glossentry>
+                            <glossentry><glossterm>(e)  sex</glossterm>
+                                <glossdef><para>Individual's gender (1=MALE, 2=FEMALE).</para></glossdef></glossentry>
+                            <glossentry><glossterm>(f)  affection status</glossterm>
+                                <glossdef><para>Affection status to be used for association tests (0=UNKNOWN, 1=UNAFFECTED, 2=AFFECTED).</para></glossdef></glossentry>
+                            <glossentry><glossterm>(g)  marker genotypes</glossterm>
+                                <glossdef><para>Each marker is represented by two columns (one for each allele, separated by a space) and coded either ACGT or 1-4 where: 1=A, 2=C, 3=G, T=4.
+                                    A 0 in any of the marker genotype position (as in the the genotypes for the third marker above) indicates missing data.
+                                </para></glossdef></glossentry>
+                        </glosslist>
+
+                        <para>It is also worth noting that this format can be used with
+                            non-family based data. Simply use a dummy value for the
+                            pedigree name (1, 2, 3...) and fill in zeroes for father and
+                            mother ID. It is important that the "dummy" value for the ped
+                            name be unique for each individual. Affection status can be
+                            used to designate cases vs. controls (2 and 1, respectively).</para>
+	    
+                        <para>Files should also follow the following guidelines:</para>
+                        <itemizedlist>
+                            <listitem>Families should be listed consecutively within the file
+                                (i.e. all the lines with the same pedigree ID should be adjacent)</listitem>
+                            <listitem>If an individual has a nonzero parent, the parent should
+                                be included in the file on his own line.</listitem>
+                        </itemizedlist>
+                    </glossdef></glossentry>
+
+                <glossentry id="haps"><glossterm>Phased Haplotypes</glossterm>
+                    <glossdef>
+                        <para>
+                            Haplotype data for Haploview's input must be formatted in
+                            columns of Family, Individual and Genotypes. There
+                            should be two lines (chromosomes) for each individual. This is
+                            the standard format of Genehunter's TDT output. See the sample
+                            below:
+                        </para>
+<screen>FAM1    FAM1M01    0    4    2    2
+FAM1    FAM1M01    0    4    2    2
+FAM1    FAM1F02    3    h    1    2
+FAM1    FAM1F02    3    h    1    2</screen>
+
+                        <para>
+                            The data format uses the numerals 1-4 to represent genotypes,
+                            the number zero to represent missing data, and the letter "h" to
+                            represent a heterozygous allele. That is, if an individual is
+                            heterozygous at a locus, both alleles should be "h" if the
+                            phasing (which allele falls on which chromosome) is uncertain.
+                        </para>
+                    </glossdef></glossentry>
+
+                <glossentry id="hapmap"><glossterm>HapMap Project Data Dumps</glossterm>
+                    <glossdef>
+                        <para>Data from the HapMap Project
+                            can be dumped by region using the GBrowse interface.
+                            The saved data file is in a marker-per-line format which
+                            can be loaded in Haploview.</para>
+	    
+                        <para>GBrowse dumps only one file, which has one marker per line
+                            and which includes familial relationships among the HapMap
+                            samples as well as marker position information. The file format
+                            has several header lines (beginning with "#") which Haploview
+                            parses. Open the file by selecting "Browse HapMap Data" option
+                            and selecting the downloaded file.</para>
+
+                        <para>If you wish to load data from another source in
+                            HapMap style format, you will need to specify pedigree
+                            information in the header of the file you've
+                            created. This can be done by creating lines of the
+                            following format at the top of your file:</para>
+
+                        <screen>#@ FAM01 NA0001 0 0 1 1</screen>
+
+                        <para>This data is the same as the pedfile format
+                            discussed above. That is, the fields are
+                            family,individual,father,mother,gender,affected
+                            status. You would then replace the NAXXXX identifiers in
+                            the header row of the HapMap file with your identifiers,
+                            subject to two important constraints: they must be
+                            unique across the entire dataset, not just within a
+                            family and they must begin with the characters
+                            NA.</para>
+
+                    </glossdef></glossentry>
+
+                <glossentry id="phase"><glossterm>HapMap PHASE Format</glossterm>
+                    <glossdef>
+                        <para>Data in the <emphasis>HapMap PHASE</emphasis> format can be loaded into
+                            Haploview using three separate files.  The first is the data file containing
+                            binary allele information.  The second is a sample file containing a single
+                            column of the individual IDs used in the dataset.  The third is a legend
+                            file containing four columns: marker, position, 0, and 1.  Only the legend
+                            file requires a header and is used to decode the information in
+                            the data file.  These files can be loaded in as GZIP compressed files using the
+                            "Files are GZIP compressed" checkbox on the initial loading screen.  For more
+                            information on the HapMap PHASE format, please see the
+                            <ulink url="http://www.hapmap.org/downloads/phasing/2007-08_rel22/phased/00README.txt">HapMap PHASE readme</ulink>.</para>
+                    </glossdef></glossentry>
+
+                 <glossentry id="hapmapdownload"><glossterm>HapMap Download</glossterm>
+                    <glossdef>
+                        <para>Data in the <emphasis>HapMap PHASE</emphasis> format can also be automatically
+                            downloaded into Haploview using the "HapMap Download" tab in the load screen by
+                            specifying the HapMap Release, chromosome, analysis panel, and start and end positions
+                            (in kb).  These options can also be automatically filled in by querying the GeneCruiser
+                            database with a gene or SNP ID.  More information about the GeneCruiser database can be
+                            found at the <ulink url="http://genecruiser.broad.mit.edu/">GeneCruiser website</ulink>.</para>
+                    </glossdef></glossentry>
+
+                <glossentry id="info"><glossterm>Marker Information File</glossterm>
+                    <glossdef>
+                        <para>The marker info file is two columns, marker name and
+                            position. The positions can be either absolute chromosomal
+                            coordinates or relative positions. It might look something
+                            like this:</para>
+<screen>marker01 190299
+marker02 190950
+marker03 191287</screen>
+	    
+                        <para>An optional third column can be included in the info file to
+                            make additional notes for specific SNPs. SNPs with additional
+                            information are highlighted in green on the LD display. For instance,
+                            you could make note that the first SNP is a coding variant as follows:</para>
+	    
+<screen>marker01 190299 CODING_SNP
+marker02 190950
+marker03 191287</screen>
+                    </glossdef></glossentry>
+
+                <glossentry id="plink2"><glossterm>PLINK Format</glossterm>
+                    <glossdef>
+                        <para>Output files from <emphasis>PLINK</emphasis> can be loaded into Haploview using the PLINK tab
+                            on the initial loading screen.  PLINK files must contain a header and at least
+                            one column header must be titled "SNP" and contain the marker IDs for the results
+                            in the file.  PLINK loading also requires a standard PLINK map or binary map file corresponding
+                            to the markers in the output file.  The map file can be either three or four headerless columns
+                            (the Morgan distance column is optional).  The map file can also be embedded in the results file as the
+                            first few columns of the file using the "Integrated Map Info" checkbox.  You can load in non-SNP
+                            based files as well by checking the "Non-SNP" box.  These files do not require a map file.  You can
+                            choose to only load in one chromosome from your results file using the "Only load results from Chromosome"
+                            checkbox and selecting a chromosome from the dropdown list.  You can also select which columns to load
+                            from your results file by checking the "Select Columns" checkbox.  For a great deal more information
+                            on PLINK outputs, please see Shaun Purcell's <ulink url="http://pngu.mgh.harvard.edu/~purcell/plink/index.shtml">PLINK website</ulink>.</para>
+                    </glossdef></glossentry>
+
+                <glossentry id="batch"><glossterm>Batch Load File</glossterm>
+	  
+                    <glossdef><para>The "-batch" flag on the command line allows you to run
+                        Haploview automatically (in nogui mode) on several files. Batch input
+                        files should have one genotype file per line, along with an info file
+                        (if desired) separated by a space. Filenames must conform to the
+                        following rules:</para>
+
+                        <itemizedlist>
+                            <listitem>Pedfile names must end in ".ped"</listitem>
+                            <listitem>Phased haplotype file names must end in ".haps"</listitem>
+                            <listitem>HapMap file names must end in ".hmp"</listitem>
+                            <listitem>Info file names must end in ".info"</listitem>
+                        </itemizedlist>
+
+                        <para>The following example shows 2 pedfiles (with info files) and a
+                            hapmap file:</para>
+
+<screen>sample1.ped   sample1.info
+sample2.ped   sample2.info
+sample3.hmp</screen>
+                    </glossdef></glossentry>
+            </glosslist>
+        </section>
+
+        <section>
+            <title>Output Files</title>
+            <para>For any given tab the information in the display can be
+                saved. For the data check and association test tabs, a simple
+                tab-delimited text file is generated from the tables. For the LD
+                and Haplotype tabs, data can either be dumped to text files or
+                the image can be saved to a PNG.</para>
+      
+            <glosslist>
+                <glossentry><glossterm>LD Text Output File</glossterm>
+                    <glossdef>
+                        <para>LD text output is a tab delimited set of columns
+                            containing the various measures of LD used by the
+                            program. Details for each column are shown below:</para>
+                        <itemizedlist>
+                            <listitem><literal>L1</literal> and <literal>L2</literal> are the two loci in question,
+                                referenced by their number or name (if marker info file is
+                                provided)</listitem>
+                            <listitem><literal>D'</literal> is the value of D prime between the
+                                two loci.</listitem>
+                            <listitem><literal>LOD</literal> is the log of the likelihood odds
+                                ratio, a measure of confidence in the value
+                                of <literal>D'</literal></listitem>
+                            <listitem><literal>r<superscript>2</superscript></literal> is the correlation coefficient between
+                                the two loci</listitem>
+                            <listitem><literal>CIlow</literal> is 95% confidence lower bound on
+                                <literal>D'</literal></listitem>
+                            <listitem><literal>CIhi</literal> is the 95% confidence upper bound on
+                                <literal>D'</literal></listitem>
+                            <listitem><literal>Dist</literal> is the distance (in bases) between the
+                                loci, and is only displayed if a marker info file has been
+                                loaded</listitem>
+                            <listitem><literal>T-int</literal> is a statistic used by the HapMap
+                                Project to measure the completeness of information represented
+                                by a set of markers in a region</listitem>
+                        </itemizedlist>
+
+
+                        <para>Details about additional options for this output type can be
+                            found below in the <link linkend="export">Export Options</link>
+                            section.</para>
+                    </glossdef></glossentry>
+
+                <glossentry><glossterm>LD PNG Output</glossterm>
+                    <glossdef>
+                        <para>When saving the LD table to a PNG, Haploview saves an image
+                            using the current <link linkend="lddisplay">display settings</link>.
+                            This includes color scheme, zoom and proportional
+                            spacing. Thus, in order to save a less detailed image to a PNG,
+                            first zoom out, then export the tab. Note that Haploview cannot
+                            save large datasets at the higher zoom levels. For more
+                            information see the <link linkend="export">Export Options</link> section
+                            below.</para>
+                    </glossdef></glossentry>
+	
+                <glossentry><glossterm>Haplotype Text Output File</glossterm>
+                    <glossdef>
+                        <para>Haplotype output shows a block, its markers, the haplotypes
+                            and their population frequencies, the crossover percentages to
+                            the next block and the multiallelic D prime. Crossover percentages
+                            are shown as a matrix with this block's haplotypes as the rows
+                            and the next block'shaplotypes as the columns. An example might look like:</para>
+	    
+<para>
+<screen>BLOCK 1.  MARKERS: 1 2 3 4
+3312 (0.825)    |0.800  0.025   0.000|
+1144 (0.163)    |0.031  0.125   0.007|
+3342 (0.013)    |0.006  0.000   0.006|
+Multiallelic Dprime: 0.802
+BLOCK 2.  MARKERS: 10 11 12
+441 (0.837)
+222 (0.150)
+242 (0.013)</screen>
+</para>
+	    
+                        <para>In this example, the first block has 4 markers with 3
+                            haplotypes displayed and the second block has 3 markers and 3
+                            haplotypes. The tag SNPs for each block are (3,4) and (10,11)
+                            respectively. The crossover percentage matrix can be read as follows:
+                            80% of all samples have the pattern 3312-441, 3.1% have the
+                            pattern 1144-441 and so forth.</para>
+	    
+                    </glossdef></glossentry>
+	
+                <glossentry><glossterm>Haplotype PNG Output</glossterm>
+                    <glossdef>
+                        <para>Saving the haplotype tab to a PNG produces an image using the
+                            current <link linkend="haplotypes">display settings</link> (such as haplotype frequency cutoff).</para>
+                    </glossdef></glossentry>
+	
+                <glossentry><glossterm>Single Marker Association Text Output File</glossterm>
+                    <glossdef>
+                        <para>Single marker association results are saved in a
+                            tab-delimited text file with the following columns:</para>
+	    
+                        <itemizedlist>
+                            <listitem><literal>#</literal> is the marker number.</listitem>
+                            <listitem><literal>Name</literal> is the marker ID specified if an info file is loaded.</listitem>
+                            <listitem><literal>Chi Square</literal> is the chi square value for the marker.</listitem>
+                            <listitem><literal>p value</literal> is the significance level for the above chi square.</listitem>
+                        </itemizedlist>
+	    
+                        <para>Trio (TDT) data only:</para>
+                        <itemizedlist>
+                            <listitem><literal>Overtransmitted</literal> is the allele overtransmitted to affected offspring.</listitem>
+                            <listitem><literal>T:U</literal> is the ratio of transmissions to non transmissions of the overtransmitted allele (see above).</listitem>
+                        </itemizedlist>
+	    
+                        <para>Case-Control data only:</para>
+                        <itemizedlist>
+                            <listitem><literal>Major Alleles</literal> are the major alleles in the case and control populations respectively.</listitem>
+                            <listitem><literal>Case Control Ratios</literal> are the ratios (shown as either counts or quotients, depending on selected options) for the case and control populations, respectively.</listitem>
+                        </itemizedlist>
+                    </glossdef></glossentry>
+	
+                <glossentry><glossterm>Haplotype Association Text Output</glossterm>
+                    <glossdef>
+                        <para>Haplotype association text output is a tab-delimited file,
+                            broken into sections by block. The columns are:</para>
+                        <itemizedlist>
+                            <listitem><literal>Haplotype</literal> is the sequence of alleles for this haplotype in this block.</listitem>
+                            <listitem><literal>Frequency</literal> is the population frequency for this haplotype.</listitem>
+                            <listitem><literal>Chi Square</literal> is the chi square value for the haplotype.</listitem>
+                            <listitem><literal>p value</literal> is the significance level for the above chi square.</listitem>
+                        </itemizedlist>
+	    
+                        <para>Trio (TDT) data only:</para>
+                        <itemizedlist>
+                            <listitem><literal>T:U</literal> is the ratio of transmissions to non transmissions of the haplotype to affected offspring.</listitem>
+                        </itemizedlist>
+	    
+                        <para>Case-Control data only:</para>
+                        <itemizedlist>
+                            <listitem><literal>Case Control Ratios</literal> are the ratios (shown as either counts or quotients, depending on selected options) for the case and control populations, respectively.</listitem>
+                        </itemizedlist>
+                    </glossdef></glossentry>
+
+                <glossentry id="permout"><glossterm>Permutation Text Output File</glossterm>
+                    <glossdef><para>The output from the permutations tab shwos the number of permutations performed and then a tab-delimited table with one row per permuted test and the following columns:</para>
+                        <itemizedlist>
+                            <listitem><literal>Name</literal> is the test name, which is either a marker name or a comma separated list of marker names then a tab then a comma separated set of alleles for those markers.</listitem>
+                            <listitem><literal>Chi Square</literal> is the observed association chi square for that test.</listitem>
+                            <listitem><literal>Permutation p-value</literal> shows the significance of the test among the permutation tests.</listitem>
+                        </itemizedlist>
+                    </glossdef>
+                </glossentry>
+
+                <glossentry id="taggerout"><glossterm>Tagger Text Output File</glossterm>
+                    <glossdef>
+                        <para>The Tagger text output begins with several pieces of summary information. More details on this can be found in the <link linkend="tagger">Tagger</link> section. The rest of the output is divided into two sections. The first lists each marker, with the following rows:</para>
+                        <itemizedlist>
+                            <listitem><literal>Marker</literal> is the marker name.</listitem>
+                            <listitem><literal>Best Test</literal> is the test with the highest r<superscript>2</superscript> to this marker.</listitem>
+                            <listitem><literal>r^2 w/test</literal> is the r<superscript>2</superscript> between this marker and its test.</listitem>
+                        </itemizedlist>
+                        <para>The second part consists of a list of the tests and the alleles they capture best.</para>
+                    </glossdef>
+                </glossentry>
+
+                <glossentry id="taggertests"><glossterm>Tagger Tests Dump</glossterm>
+                    <glossdef>
+                        <para>This file is the same format used by Haploview for custom association tests and exported by <ulink url="http://www.broad.mit.edu/mpg/tagger/">Tagger</ulink>. It is <link linkend="custassocfile">discussed below</link> in the auxiliary files section.</para>
+                    </glossdef>
+                </glossentry>
+
+                <glossentry id="taggertags"><glossterm>Tagger Tags Dump</glossterm>
+                    <glossdef>
+                        <para>This file is the same format used by Haploview for custom association tests and exported by <ulink url="http://www.broad.mit.edu/mpg/tagger/">Tagger</ulink>. It is <link linkend="custassocfile">discussed below</link> in the auxiliary files section.</para>
+                    </glossdef>
+                </glossentry>
+
+                <glossentry><glossterm>Marker Check Text Output File</glossterm>
+                    <glossdef>
+                        <para>The marker check data is a tab-delimited file with the
+                            following columns:</para>
+                        <itemizedlist>
+                            <listitem><literal>#</literal> is the marker number.</listitem>
+                            <listitem><literal>Name</literal> is the marker ID specified (only if an info file is loaded).</listitem>
+                            <listitem><literal>Position</literal> is the marker position specified (only if an info file is loaded).</listitem>
+                            <listitem><literal>ObsHET</literal> is the marker's observed heterozygosity.</listitem>
+                            <listitem><literal>PredHET</literal> is the marker's predicted heterozygosity (i.e. 2*MAF*(1-MAF)).</listitem>
+                            <listitem><literal>HWpval</literal> is the Hardy-Weinberg equilibrium p value, which is the probability that its deviation from H-W equilibrium could be explained by chance.</listitem>
+                            <listitem><literal>%Geno</literal> is the percentage of non-missing genotypes for this marker.</listitem>
+                            <listitem><literal>FamTrio</literal> is the number of fully genotyped family trios for this marker (0 for datasets with unrelated individuals).</listitem>
+                            <listitem><literal>MendErr</literal> is the number of observed Mendelian inheritance errors (0 for datasets with unrelated individuals).</listitem>
+                            <listitem><literal>MAF</literal> is the minor allele frequency (using founders only) for this marker.</listitem>
+                            <listitem><literal>Alleles</literal> are the major and minor alleles for this marker.</listitem>
+                            <listitem><literal>Rating</literal> is "BAD" if the marker failed any of the above tests and blank otherwise.</listitem>
+                        </itemizedlist>
+                    </glossdef></glossentry>
+
+                <glossentry><glossterm>PLINK Table Text Output File</glossterm>
+                    <glossdef>
+                        <para>The PLINK text output is a tab-delimited file of the current view
+                            of the data in the PLINK tab.  Please note that while the filtering state
+                            is preserved in this output, the sorting state is not.</para>
+                    </glossdef></glossentry>
+      
+
+            </glosslist>
+        </section>
+    
+        <section id="export">
+            <title>Export Options</title>
+            <para>The "Export Options" item in the File Menu allows adjustment of
+                several parameters and allows the user to save any tab without
+                having to switch to it. Specifically, the LD tab allow the markers
+                to be filtered to output only some of the markers:</para>
+            <glosslist>
+                <glossentry><glossterm>All</glossterm>
+                    <glossdef><para>The default setting (and only one available for most tabs) is to use all the markers.</para></glossdef></glossentry>
+                <glossentry><glossterm>Marker Range</glossterm>
+                    <glossdef><para>Generates the LD text or PNG file for only a specific range of markers.</para></glossdef></glossentry>
+                <glossentry><glossterm>Adjacent Markers</glossterm>
+                    <glossdef><para>Generates the LD text file for only adjacent markers. This can be useful to view the T-int stat, which measures LD information content in the gaps between markers.</para></glossdef></glossentry>
+            </glosslist>
+      
+            <para>There is also an option to generate a "compressed" LD PNG,
+                which is useful for very large datasets. The image is shrunk
+                to an arbitrary zoom level which allows Haploview to save the PNG
+                with minimal memory usage. Images can also be exported as high
+                quality SVG files for use in publication.  Please note that SVG
+                images are quite large and may require a large amount of memory.</para>
+        </section>
+
+        <section id = "auxinput">
+            <title>Auxiliary Input Files</title>
+            <glosslist>
+                <glossentry id="blocksfile"><glossterm>Blocks File</glossterm>
+	  
+                    <glossdef><para>You can specify a set of blocks by loading a blocks
+                        file. Each line is a space separated list of markers with one
+                        block per line. For example:</para>
+	    
+<screen>1 2 3 4
+9 10 11 12 13 14 15</screen>
+	    
+                        <para>Would create one block from markers 1-4 and another from
+                            9-15. The first marker in the file is number 1 (not 0).</para>
+                    </glossdef></glossentry>
+	
+                <glossentry id="trackfile"><glossterm>Analysis Track File</glossterm>
+	  
+                    <glossdef><para>You can add an analysis track along the top of the LD
+                        display by loading a file with two columns, <position>
+                        <value>. Haploview will plot the values continuously with
+                        respect to the positions of the markers, so the positions should
+                        use the same coordinates as the marker info file. For
+                        example:</para>
+	    
+<screen>1000 0.3
+2000 1.7
+3000 11.0
+4000 2.3
+5000 4.6</screen>
+	    
+                        <para>Would plot a line from position 1000 to 5000. The values
+                            can be of any units or magnitude, as the Haploview scales the
+                            analysis track to the bounds of the values.</para>
+                    </glossdef></glossentry>
+
+                <glossentry id="custassocfile"><glossterm>Custom Association Tests File</glossterm>
+                    <glossdef><para>You can specify a set of custom association tests for Haploview to perform. The format takes both single marker tests and multi-marker tests (which require you to specify alleles for those markers). The format is one test per line with each line containing one of the following: a single marker name or several comma separated names, then a tab, then comma separated alleles for each marker. This format is exported by Haploview using the "Dump Tests" butt [...]
+
+
+                        <para>For instance, the following example would create 5 tests: markers 1, 2 and 3 individually, all the alleles (haplotypes) of the block 4,5,6 and the CAA haplotype of the block 12,13,14:</para>
+<screen>marker1
+marker2
+marker3
+marker12,marker13,marker14     2,1,1</screen>
+
+                        <para><emphasis role="bold">N.B.</emphasis> Using a Custom Association Tests File requires a <link linkend="info">marker info file</link>, since the tests file reads the marker names as specified in the info file.</para>
+                    </glossdef>
+                </glossentry>
+
+                <glossentry id="taggerincludeexclude"><glossterm>Tagger Marker Include/Exclude File</glossterm>
+                    <glossdef><para>You can specifiy a list of markers for Tagger to include or exclude from those markers available for selection as tag SNPs. In either case the format is the same: one marker name per line. The following file could be used to either include or exclude markers 1,7 and 9:</para>
+
+<screen>marker1
+marker7
+marker9</screen>
+
+                        <para><emphasis role="bold">N.B.</emphasis> Using a Tagger Include/Exclude File requires a <link linkend="info">marker info file</link>, since it reads the marker names as specified in the info file.</para>
+
+                    </glossdef>
+                </glossentry>
+            </glosslist>
+        </section>
+    </chapter>
+
+
+    <chapter id="commandline">
+        <title>Command Line Options</title>
+
+        <para>Haploview can be run from the command line without the
+            display in order to do processing of multiple datasets or quick
+            computation on very large datasets. In order to run Haploview
+            without the display, add the "-nogui" flag. The "-help" flag
+            shows a condensed explanation of all the command line options
+            explained below. Haploview can be run from the command line
+            using:</para>
+
+        <screen>java -jar Haploview.jar</screen>
+
+        <section>
+            <title>General Options</title>
+            <glosslist>
+                <glossentry><glossterm>-h, -help</glossterm>
+                    <glossdef><para>Print help information.</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-n, -nogui</glossterm>
+                    <glossdef><para>Command line mode—does not launch display.</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-q, -quiet</glossterm>
+                    <glossdef><para>Quiet mode—minimizes output to command line.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-log <filename></glossterm>
+                    <glossdef><para>Outputs logfile information to specified filename (defaults to haploview.log if no name specified)</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-out <fileroot></glossterm>
+                    <glossdef><para>Specify a fileroot to be used for all output files</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-memory <memsize></glossterm>
+                    <glossdef><para>Allocate <memsize> megabytes of memory to the Haploview process (default is 512MB).</para></glossdef></glossentry>
+            </glosslist>
+        </section>
+    
+        <section>
+            <title>Input Options</title>
+            <glosslist>
+                <glossentry><glossterm>-pedfile <filename></glossterm>
+                    <glossdef><para>Specify a genotype input file (or http:// location) in <link linkend="ped">pedigree</link>
+                        format. This option works in GUI mode.</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-hapmap <filename></glossterm>
+                    <glossdef>
+                        <para>Specify a <link linkend="hapmap">HapMap</link>
+                            format input file (or http:// location). This option works in GUI mode. </para>
+                    </glossdef></glossentry>
+
+                <glossentry><glossterm>-phasedhmpdata <filename></glossterm>
+                    <glossdef>
+                        <para>Specify a <link linkend="phase">PHASE</link>
+                            format data input file (or http:// location). This option works in GUI mode. </para>
+                    </glossdef></glossentry>
+
+                <glossentry><glossterm>-phasedhmpsample <filename></glossterm>
+                    <glossdef>
+                        <para>Specify a <link linkend="phase">PHASE</link>
+                            format sample input file (or http:// location). This option works in GUI mode. </para>
+                    </glossdef></glossentry>
+
+                <glossentry><glossterm>-phasedhmplegend <filename></glossterm>
+                    <glossdef>
+                        <para>Specify a <link linkend="phase">PHASE</link>
+                            format legend input file (or http:// location). This option works in GUI mode. </para>
+                    </glossdef></glossentry>
+
+                <glossentry><glossterm>-hapmapDownload</glossterm>
+                    <glossdef><para>Specify a <link linkend="hapmapdownload">phased HapMap download</link>.  This option works in GUI mode. </para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-haps <filename></glossterm>
+                    <glossdef><para>Specify a <link linkend="haps">phased</link> input file (or http:// location). This option works in GUI mode.</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-info <filename></glossterm>
+                    <glossdef><para>Specify a marker <link linkend="info">information</link> file (or http:// location). This option works in GUI mode.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-plink <filename></glossterm>
+                    <glossdef><para>Specify a PLINK or other results file (or http:// location). This option only works in GUI mode.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-map <filename></glossterm>
+                    <glossdef><para>Specify a map or binary map file (or http:// location). This option only works in GUI mode.</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-batch <filename></glossterm>
+                    <glossdef><para>Specify a <link linkend="batch">batch</link> load file.</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-blocks <filename></glossterm>
+                    <glossdef><para>Specify a <link linkend="blocksfile">block</link> definition file (or http:// location). This will automatically use this block definition for haplotype output.</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-track <filename></glossterm>
+                    <glossdef><para>Specify an analysis <link linkend="trackfile">track</link> file (or http:// location)</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-chromosome <1-22,X,Y></glossterm>
+                    <glossdef><para>Specify which chromosome these data come from. This is especially critical when analyzing data from the X chromosome or direct HapMap downloads.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-panel <CEU,YRI,CHB+JPT></glossterm>
+                    <glossdef><para>Specify which analysis panel to use for phased HapMap downloads.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-startpos <integer></glossterm>
+                    <glossdef><para>Specify the start position in kb for phased HapMap downloads.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-endpos <integer></glossterm>
+                    <glossdef><para>Specify the end position in kb for phased HapMap downloads.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-release <16a,21,22></glossterm>
+                    <glossdef><para>Specify the HapMap release version for phased HapMap downloads (defaults to 21).</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-gzip</glossterm>
+                    <glossdef><para>Specify PHASE format inputs using GZIP compression</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-nonSNP</glossterm>
+                    <glossdef><para>Specify that the accompanying PLINK file is non-SNP based output. This option only works in GUI mode.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-selectCols</glossterm>
+                    <glossdef><para>Activate the preloading column filter for PLINK loads. This option only works in GUI mode.</para></glossdef></glossentry>
+
+
+            </glosslist>
+        </section>
+
+        <section>
+            <title>Data Check Options</title>
+            <glosslist>
+                <glossentry><glossterm>-skipcheck</glossterm>
+                    <glossdef><para>Skip all the genotype data quality checks and uses all markers for all analyses.</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-minMAF <threshold></glossterm>
+                    <glossdef><para>Exclude all markers with minor allele frequency below <threshold>, which must be between 0 and 0.5. Default of 0. This option works in GUI mode.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-maxMendel <integer></glossterm>
+                    <glossdef><para>Exclude markers with greater than <integer> Mendelian inheritance errors. Default of 1. This option works in GUI mode.</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-minGeno <threshold></glossterm>
+                    <glossdef><para>Exclude markers with less than <threshold> fraction of nonzero genotypes. <threshold> must be between 0 and 1 with a default of 0.5. This option works in GUI mode.</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-hwcutoff <threshold></glossterm>
+                    <glossdef><para>Exclude markers with a Hardy Weinberg p-value less than <threshold>, which ranges from 0 to 1 with a default of 0.001 This option works in GUI mode.</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-maxDistance <distance></glossterm>
+                    <glossdef><para>Maximum intermarker distance for LD comparisons (in kilobases). Default is 500. This option works in GUI mode.</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-missingCutoff <threshold></glossterm>
+                    <glossdef><para>Exclude individuals with more than <threshold> fraction missing data, where <threshold> is a value between 0 and 1 with a default of 0.5. This option works in GUI mode.</para></glossdef></glossentry>
+            </glosslist>
+        </section>
+
+        <section>
+            <title>Block Output Options</title>
+            <glosslist>
+                <glossentry><glossterm>-blockoutput <type></glossterm>
+                    <glossdef><para>Generate haplotypes and population frequencies for blocks of <type>, which can be GAB (Gabriel et al), GAM (4 gamete blocks), SPI (solid spine blocks) or ALL (each of the previous 3). The default block type is Gabriel. More information can be found with the <link linkend="blocks">blocks</link> help.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-blockCutHighCI <thresh></glossterm>
+                    <glossdef><para>Gabriel 'Strong LD' high confidence interval D' cutoff.</para></glossdef></glossentry>
+                <glossentry><glossterm>-blockCutLowCI <thresh></glossterm>
+                    <glossdef><para>Gabriel 'Strong LD' low confidence interval D' cutoff.</para></glossdef></glossentry>
+                <glossentry><glossterm>-blockMAFThresh <thresh></glossterm>
+                    <glossdef><para>Gabriel MAF threshold. Markers below this allele frequency will be skipped in building Gabriel blocks.</para></glossdef></glossentry>
+                <glossentry><glossterm>-blockRecHighCI <thresh></glossterm>
+                    <glossdef><para>Gabriel recombination high confidence interval D' cutoff.</para></glossdef></glossentry>
+                <glossentry><glossterm>-blockInformFrac <thresh></glossterm>
+                    <glossdef><para>Gabriel fraction of informative markers required to be in strong LD.</para></glossdef></glossentry>
+                <glossentry><glossterm>-block4GamCut <thresh></glossterm>
+                    <glossdef><para>4 Gamete block cutoff for frequency of 4th pairwise haplotype.</para></glossdef></glossentry>
+                <glossentry><glossterm>-blockSpineDP <thresh></glossterm>
+                    <glossdef><para>Solid Spine blocks D' cutoff for 'Strong LD'.</para></glossdef></glossentry>
+            </glosslist>
+        </section>
+        <section><title>Other Output Options</title>
+            <glosslist>
+                <glossentry><glossterm>-check</glossterm>
+                    <glossdef><para>Output marker quality checks to <inputfile>.CHECK</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-mendel</glossterm>
+                    <glossdef><para>Output Mendel error information to <inputfile>.MENDEL</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-malehets</glossterm>
+                    <glossdef><para>Output chromosome X male heterozygote information to <inputfile>.MALEHETS</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-dprime</glossterm>
+                    <glossdef><para>Output pairwise LD text table to <inputfile>.LD. Note that -dprime and -check default to no haplotype output unless the -blockoutput flag is also specified.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-png</glossterm>
+                    <glossdef><para>Output PNG image file of LD display to <inputfile>.LD.PNG</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-compressedpng</glossterm>
+                    <glossdef><para>Output low-resolution (smaller file) PNG image of LD display to <inputfile>.LD.PNG</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-svg</glossterm>
+                    <glossdef><para>Output svg format of LD display to <inputfile>.LD.SVG</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-infoTrack</glossterm>
+                    <glossdef><para>Include HapMap info track in PNG image outputs</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-spacing <threshold></glossterm>
+                    <glossdef><para>Use proportional spacing for dumped LD pngs. <threshold> ranges from 0 (no spacing) to 1 (max spacing) with a default of 0.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-ldcolorscheme <type></glossterm>
+                    <glossdef><para>Use a particular color scheme for dumped LD pngs. <type> can be DEFAULT, RSQ, DPALT, GAB or GAM. More information can be found with the <link linkend="lddisplay">LD</link> display help</para></glossdef></glossentry>
+	
+                <glossentry><glossterm>-hapthresh <threshold></glossterm>
+                    <glossdef><para>Only output haplotypes with frequency ≥ <threshold>. Note that multiallelic D' and htSNPs are computed using only displayed haplotypes.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-excludeMarkers <markers></glossterm>
+                    <glossdef><para>Exclude markers in a comma separated list with ranges specified as start..end. So, to exclude markers 3, 5 and 10 through 15 you'd use "-excludeMarkers 3,5,10..15"</para></glossdef></glossentry>
+            </glosslist>
+        </section>
+        <section><title>Association Output Options</title>
+            <glosslist>
+                <glossentry><glossterm>-assocCC</glossterm>
+                    <glossdef><para>Output case/control association results. Saves single marker results to <inputfile>.ASSOC and haplotype results to <inputfile>.HAPASSOC. Haplotype association results are not generated if block type is set to ALL.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-assocTDT</glossterm>
+                    <glossdef><para>Output TDT association results. Saves single marker results to <inputfile>.ASSOC and haplotype results to <inputfile>.HAPASSOC. Haplotype association results not generated if block type is set to ALL.</para></glossdef></glossentry>
+
+                <glossentry><glossterm>-customAssoc <file></glossterm>
+                    <glossdef><para>Loads a set of custom tests for association.</para></glossdef></glossentry>
+                <glossentry><glossterm>-permtests <numtests></glossterm>
+                    <glossdef><para>Performs <numtests> permutations on default association tests (or custom tests if a custom association file is specified) and writes to <inputfile>.PERMUT </para></glossdef></glossentry>
+            </glosslist>
+        </section>
+        <section><title>Tagging Output Options</title>
+            <glosslist>
+                <glossentry><glossterm>-pairwiseTagging</glossterm>
+                    <glossdef><para>Generates pairwise tagging information in <inputfile>.TAGS and .TESTS</para></glossdef></glossentry>
+                <glossentry><glossterm>-aggressiveTagging</glossterm>
+                    <glossdef><para>As above but generates 2-marker haplotype tags unless specified otherwise by -aggressiveNumMarkers</para></glossdef></glossentry>
+                <glossentry><glossterm>-tagrsqcounts</glossterm>
+                    <glossdef><para>Generates conditional haplotype probabilities from tagger in <inputfile>.CHAPS</para></glossdef></glossentry>
+                <glossentry><glossterm>-aggressiveNumMarkers <2,3></glossterm>
+                    <glossdef><para>Specifies whether to use 2-marker haplotype tags or 2 and 3-marker haplotype tags.</para></glossdef></glossentry>
+                <glossentry><glossterm>-maxNumTags <n></glossterm>
+                    <glossdef><para>Only selects <n> best tags.</para></glossdef></glossentry>
+                <glossentry><glossterm>-includeTags <markers></glossterm>
+                    <glossdef><para>Forces in a comma separated list of marker names as tags.</para></glossdef></glossentry>
+                <glossentry><glossterm>-includeTagsFile <file></glossterm>
+                    <glossdef><para>Forces in a file (or http:// location) of one marker name per line as tags.</para></glossdef></glossentry>
+                <glossentry><glossterm>-excludeTags <markers></glossterm>
+                    <glossdef><para>Excludes a comma separated list of marker names from being used as tags.</para></glossdef></glossentry>
+                <glossentry><glossterm>-excludeTagsFile <file></glossterm>
+                    <glossdef><para>Excludes a file (or http:// location) of one marker name per line from being used as tags.</para></glossdef></glossentry>
+                <glossentry><glossterm>-captureAlleles <file></glossterm>
+                    <glossdef><para>Capture only the alleles contained in a file (or http:// location) of one marker name per line.</para></glossdef></glossentry>
+                <glossentry><glossterm>-designScores <file></glossterm>
+                    <glossdef><para>Specify design scores in a file (or http:// location) of one marker name and one score per line.</para></glossdef></glossentry>
+                <glossentry><glossterm>-mindesignscore <threshold></glossterm>
+                    <glossdef><para>Specify a minimum design score threshold.</para></glossdef></glossentry>
+                <glossentry><glossterm>-mintagdistance <distance></glossterm>
+                    <glossdef><para>Specify a minimum distance in bases between picked tags.</para></glossdef></glossentry>
+                <glossentry><glossterm>-taglodcutoff <thresh></glossterm>
+                    <glossdef><para>Tagger LOD cutoff for creating multimarker tag haplotypes.</para></glossdef></glossentry>
+                <glossentry><glossterm>-tagrsqcutoff <thresh></glossterm>
+                    <glossdef><para>Tagger r^2 cutoff.</para></glossdef></glossentry>
+
+
+
+            </glosslist>
+        </section>
+    </chapter>
+
+    <chapter id="about">
+        <title>About Haploview</title>
+        <para>
+            Haploview was developed in and is maintained by Mark Daly's lab
+            at the <ulink url="http://www.broad.mit.edu">Broad Institute</ulink> by
+            Jeffrey Barrett, Julian Maller and David Bender. Questions and comments should be addressed to:
+            <ulink url="mailto:haploview at broad.mit.edu">haploview at broad.mit.edu</ulink>
+        </para>
+      
+        <itemizedlist>
+            <listitem>The design of the LD and haplotype interfaces is the work of <emphasis role="bold">Ben Fry</emphasis> at
+                the <ulink url="http://acg.media.mit.edu/people/fry">MIT Media
+                Lab</ulink>.</listitem>
+	
+            <listitem>Thanks to <emphasis role="bold">Andrew Kirby and Hin-Tak Leung</emphasis> for code contributions.</listitem>
+
+            <listitem>Thanks to <emphasis role="bold">Itsik Pe'er and Paul de Bakker</emphasis> for their extensive contributions to methods development and testing.</listitem>
+
+            <listitem>Hardy-Weinberg calculation code courtesy of <emphasis role="bold">Goncalo
+                Abecasis</emphasis> and <emphasis role="bold">Jan Wigginton</emphasis> at the
+                <ulink url="http://www.sph.umich.edu/csg/index.html">University of Michigan
+                    Center for Statistical Genetics</ulink></listitem>
+	
+	
+            <listitem>The r<superscript>2</superscript> and alternative D' color schemes are the work
+                of <emphasis role="bold">Will Fitzhugh</emphasis>. </listitem>
+	
+            <listitem>The interface to the HapMap GBrowse track is courtesy of
+                <emphasis role="bold">Simon Twigger</emphasis>. </listitem>
+
+            <listitem><ulink url="http://pngu.mgh.harvard.edu/~purcell/plink/">PLINK</ulink> is the work of <emphasis role="bold">Shaun
+                Purcell</emphasis> at the Center for Human Genetic Research
+                of Massachusetts General Hospital</listitem>
+	
+        </itemizedlist>
+      
+        <para><emphasis role="bold">Source Code</emphasis></para>
+      
+        <para>Haploview is an open source project hosted by
+            <ulink url="http://sf.net/">SourceForge</ulink>. The source can be downloaded at
+            the SourceForge <ulink url="http://sf.net/projects/haploview/">project
+            site</ulink>.</para>
+      
+        <para><emphasis role="bold">Citations</emphasis></para>
+      
+        <para>Haploview can be cited with the following
+            <ulink url="http://bioinformatics.oupjournals.org/cgi/content/full/bth457?ijkey=54SiAdNbKzbNg">paper</ulink>:</para>
+      
+        <para>Barrett JC, Fry B, Maller J, Daly MJ. Haploview: analysis and
+            visualization of LD and haplotype maps. <emphasis>Bioinformatics</emphasis>. 2005
+            Jan 15 [PubMed ID: 15297300]</para>
+
+        <para>Information about the exact test for HW can be found in the following paper:</para>
+
+        <para>Wigginton JE, Cutler DJ, Abecasis GR. A note on exact tests of Hardy-Weinberg equilibrium. <emphasis>Am J Hum Genet.</emphasis> 2005 May;76(5):887-93.</para>
+
+        <para>Information about parenTDT can be found in the following paper:</para>
+
+        <para>Purcell S, Sham P, Daly MJ. Parental phenotypes in family-based association analysis. <emphasis>Am J Hum Genet.</emphasis> 2005 Feb;76(2):249-59.</para>
+      
+        <para>Questions, complaints and suggestion should be directed to
+            <ulink url="mailto:haploview at broad.mit.edu">haploview at broad.mit.edu</ulink>.</para>
+      
+    
+        <section>
+            <title>System Requirements</title>
+            <para>It is recommended that Haploview be run on a machine with at least
+                128M of memory. The Haploview jarfile should now automatically
+                allocate extra memory when starting up, so the -Xmx flag is no longer
+                required when running the program from the command line.</para>
+      
+            <para>Haploview requires Java JRE 1.3 or later, but 1.4 is strongly
+                recommended. It is worthwhile in any case to <ulink url="http://www.java.com/">download the most recent Java
+                release</ulink>.</para>
+        </section>
+
+        <section>
+            <title>Known Issues</title>
+
+            <para>We are working to correct a rare issue with Spineblock outputs for
+                certain datasets.</para>
+
+            <para>Saving PNG files from the command line (the -png and -compressedpng
+                flags) requires Java version 1.4 or later.</para>
+
+            <para>There appears to be an issue with the Windows JDK version
+                1.4.1_03. Note that all other versions (both earlier and later
+                versions of 1.4.1 included) seem to work fine. If you find this
+                to be your current Java version (type java -version at the
+                command prompt), please install the latest version.</para>
+        </section>
+
+        <section>
+            <title>Updates</title>
+            <para>If you have an internet connection, Haploview will automatically
+                check for an update upon startup. If a new version is available, it
+                will show a message in the lower right corner of the screen for a few
+                seconds. Details can be found by using the "Check for Updates" button
+                in the File menu.</para>
+        </section>
+
+        <section>
+            <title>Change Log</title>
+
+            <section>
+                <title>Version 4.1, 29 April 2008</title>
+                <itemizedlist>
+                    <listitem>added support for GeneCruiser lookup</listitem>
+                    <listitem>added support for HapMap release 22</listitem>
+                    <listitem>added -out fileroot specification to the command line</listitem>
+                    <listitem>added consecutive filtering to PLINK with view/add/remove filters</listitem>
+                    <listitem>added SVG output for LD plot and haplotype blocks display</listitem>
+                    <listitem>added min design scores to Tagger in GUI and command line</listitem>
+                    <listitem>added reset thresholds button to Tagger</listitem>
+                    <listitem>added exclude A/T and C/G SNPs button to Tagger</listitem>
+                    <listitem>upgraded graphing packages to latest version</listitem>
+                    <listitem>changed load screen dialog tab orientation for Mac OSX</listitem>
+                    <listitem>fixed bug with LD display pop-up on certain systems</listitem>
+                    <listitem>fixed bug with HapMap info track caching</listitem>
+                    <listitem>fixed bug on LD Display with MAF of .5</listitem>
+                    <listitem>fixed bug where setting tagger cutoff fields to blank would generate exceptions</listitem>
+                    <listitem>fixed bug with PLINK View Filters dialog</listitem>
+                    <listitem>fixed bug with PLINK file column number mismatches</listitem>
+                </itemizedlist>
+            </section>
+
+            <section>
+                <title>Version 4.0, 21 August 2007</title>
+                <itemizedlist>
+                    <listitem>added support for HapMap PHASE format data</listitem>
+                    <listitem>added support for phased HapMap downloads</listitem>
+                    <listitem>added support for PLINK output files including plotting functionality</listitem>
+                    <listitem>added a number of Tagger features to coincide with the Tagger website functionality</listitem>
+                    <listitem>added 2-marker aggressive tagging from the command line</listitem>
+                    <listitem>added log file functionality to command line mode</listitem>
+                    <listitem>added proxy support</listitem>
+                    <listitem>added table sorting to Tagger configuration</listitem>
+                    <listitem>added numerous minor display and interface tweaks</listitem>
+                    <listitem>added export options for Mendel errors and male heterozygotes on x chromosome</listitem>
+                    <listitem>added command line HapMap info track download</listitem>
+                    <listitem>added case control frequencies to command line association output</listitem>
+                    <listitem>added ld values selection to command line image export</listitem>
+                    <listitem>added citation information to the "About" dialog</listitem>
+                    <listitem>added "Uncapture All" button to Tagger</listitem>
+                    <listitem>added build 36 option to HapMap info track downloads</listitem>
+                    <listitem>added http:// loading for files on the command line and initial load screen</listitem>
+                    <listitem>changed some labelling in Tagger and made pairwise the default option</listitem>
+                    <listitem>changed obsHet calculation to use founders only</listitem>
+                    <listitem>changed HW and het calculations to show as "NA" in X data with too few individuals</listitem>
+                    <listitem>enhanced GUI design with tabbed pane for initial loading dialog</listitem>
+                    <listitem>fixed bug with haplotype associations for extended trios and x chromosome</listitem>
+                    <listitem>fixed bug with case control counts for certain zeroed out markers</listitem>
+                    <listitem>fixed Mendel error reporting bug on x chromosome</listitem>
+                    <listitem>fixed Tagger bug affecting maximum number of tags input</listitem>
+                    <listitem>fixed bug when left-clicking on the LD plot</listitem>
+                    <listitem>fixed bug when changing tabs after multiple file loads</listitem>
+                    <listitem>fixed bug when changing tabs after running tagger and changing check marker thresholds</listitem>
+                    <listitem>fixed bug when loading custom association test file with multi-marker tests but no alleles</listitem>
+                    <listitem>fixed bug with long windows filenames in command line mode</listitem>
+                    <listitem>fixed bug with display when resizing the window to a very small size</listitem>
+                    <listitem>fixed bug with 'h' alleles in haps files not being properly calculated</listitem>
+                    <listitem>fixed bug with check panel export not accounting for user threshold adjustments</listitem>
+                    <listitem>fixed bug with LD display where some r^2 values were invisible against the background</listitem>
+                    <listitem>fixed bug with integer overflow when loading files with tens of thousands of individuals</listitem>
+                    <listitem>fixed bug where HapMap info track would turn black if it was too wide</listitem>
+                    <listitem>fixed bug with null pointer exception on failed hapmap info track downloads</listitem>
+                    <listitem>fixed bug with null pointer exception when using Export Options Dialog</listitem>
+                    <listitem>fixed bug with LD Plot now showing the correct markers when changing marker ratings and using table sorting in the check panel</listitem>
+                    <listitem>fixed tagger results reporting of % captured when specifying alleles to capture</listitem>
+                    <listitem>fixed bug with marriage loops in pedigree files causing StackOverflow errors</listitem>
+                    <listitem>fixed bug with exception being thrown for certain input files with no markers</listitem>
+                    <listitem>fixed bug with multimarker tags showing in the tagger results even though they don't capture anything</listitem>
+                    <listitem>fixed bug with command line error handling</listitem>
+                    <listitem>fixed bug with loading improperly formatted info files from "Load marker data"</listitem>
+                </itemizedlist>
+            </section>
+
+            <section>
+                <title>Version 3.32, 21 June 2006</title>
+                <itemizedlist>
+                    <listitem>fixed bugs in check markers display when using table sorting</listitem>
+                    <listitem>fixed line number output on error messages</listitem>
+                    <listitem>grammar corrections</listitem>
+                </itemizedlist>
+            </section>
+
+            <section>
+                <title>Version 3.31, 2 June 2006</title>
+                <itemizedlist>
+                    <listitem>fixed bug where chromosomes were created in reverse</listitem>
+                </itemizedlist>
+            </section>
+
+            <section>
+                <title>Version 3.3, 26 May 2006</title>
+                <itemizedlist>
+                    <listitem>support for X chromosome</listitem>
+                    <listitem>major memory usage improvements</listitem>
+                    <listitem>allow multiple analysis tracks and label Y axis</listitem>
+                    <listitem>added user defined hapmap sample option</listitem>
+                    <listitem>added haplotypes only permutation option</listitem>
+                    <listitem>added option to show different LD measures or hide them</listitem>
+                    <listitem>added -memory switch and changed default to 512M</listitem>
+                    <listitem>added minor allele to check panel</listitem>
+                    <listitem>added progress bar for data loading</listitem>
+                    <listitem>support for alphabetical (ACGT) input format</listitem>
+                    <listitem>added individual information dialog</listitem>
+                    <listitem>added check panel for phased haps files</listitem>
+                    <listitem>added dump tags button</listitem>
+                    <listitem>last popup dialog is shown at top of display</listitem>
+                    <listitem>added GOLD style color scheme</listitem>
+                    <listitem>can sort tables by clicking on headers</listitem>
+                    <listitem>changed case control allele order</listitem>
+                    <listitem>fixed grey screen with no text bug</listitem>
+                    <listitem>fixed tagger bugs for maxnumtags and forceinclude</listitem>
+                    <listitem>fixed batch mode for hapmap files</listitem>
+                    <listitem>fixed Tagger bug where some SNPs were tagged by more than one tag.</listitem>
+                    <listitem>fixed export options bug with association tab</listitem>
+                    <listitem>fixed generateHaplotyeps bug for matching alleles</listitem>
+                </itemizedlist>
+            </section>
+
+            <section>
+                <title>Version 3.2, 13 April 2005</title>
+                <itemizedlist>
+                    <listitem>added Tagger interface</listitem>
+                    <listitem>added custom association test input</listitem>
+                    <listitem>added permutation testing of association results</listitem>
+                    <listitem>improved memory efficiency for EM haplotype reconstruction</listitem>
+                    <listitem>added Perlegen sample IDs</listitem>
+                    <listitem>fixed bug in PNG export cutting off final marker</listitem>
+                </itemizedlist>
+            </section>
+
+            <section>
+                <title>Version 3.11, 04 February 2005</title>
+                <itemizedlist>
+                    <listitem>improved parsing of families with unrelated members</listitem>
+                    <listitem>fixed bug with singletons with large amounts of missing data</listitem>
+                    <listitem>fixed bug with java path differences from cmd line</listitem>
+                    <listitem>fixed export issues for LD display</listitem>
+                    <listitem>fixed bug with block output for files without blocks</listitem>
+                </itemizedlist>
+            </section>
+      
+            <section>
+                <title>Version 3.1, 27 January 2005</title>
+                <itemizedlist>
+                    <listitem>Additional HapMap info track display options.</listitem>
+                    <listitem>Improved parsing of complex pedigrees.</listitem>
+                    <listitem>Fixed bug with loading of haps style input files.</listitem>
+                    <listitem>Fixed bug with correctly parsing out-of-order info files.</listitem>
+                    <listitem>Fixed problem with association tests on datasets with no blocks.</listitem>
+                    <listitem>Fixed bug with exporting range of markers to LD PNG.</listitem>
+                </itemizedlist>
+            </section>
+      
+            <section>
+                <title>Version 3.0, 7 October 2004</title>
+                <itemizedlist>
+                    <listitem>bugfixes for block size display, checkdata tab, allele sorting, EM missing data.</listitem>
+                    <listitem>Added compressed PNG output mode.</listitem>
+                    <listitem>Lots of new command line options.</listitem>
+                    <listitem>Changed batch mode input format.</listitem>
+                    <listitem>Re-sort out of order info files.</listitem>
+                    <listitem>Substantial improvement in speed and memory usage in EM.</listitem>
+                    <listitem>New color schemes.</listitem>
+                    <listitem>Added additional sample information for 2nd and 3rd HapMap plate.</listitem>
+                    <listitem>Fixed handling of complex pedigrees.</listitem>
+                    <listitem>Added proportional spacing to LD display.</listitem>
+                    <listitem>Added HapMap GBrowse track.</listitem>
+                    <listitem>Filter individuals with lots of missing genotypes.</listitem>
+                    <listitem>Haplotype association tests.</listitem>
+                    <listitem>Update checker.</listitem>
+                </itemizedlist>
+            </section>
+      
+            <section>
+                <title>Version 2.05, 27 April 2004</title>
+                <itemizedlist>
+                    <listitem>Fixed problem with EM for long blocks</listitem>
+                    <listitem>Added "Export options" to allow exporting subsets of data and LD values for only adjacent marker pairs.</listitem>
+                    <listitem>Numerous minor bugfixes</listitem>
+                    <listitem>Added block labels to haplotype display</listitem>
+                    <listitem>Added block size to LD display</listitem>
+                    <listitem>Added saved block definition input file</listitem>
+                    <listitem>Added analysis track input option</listitem>
+                    <listitem>Enabled direct click from HapMap webpage to Haploview</listitem>
+                </itemizedlist>
+            </section>
+      
+            <section>
+                <title>Version 2.04, 21 January 2004</title>
+                <itemizedlist>
+                    <listitem>All color schemes now allowed with all block definitions,
+                        including hand-defined blocks.</listitem>
+                    <listitem>Enabled loading input file from command line while still
+                        opening GUI.</listitem>
+                    <listitem>Added command line HapMap Project input option.</listitem>
+                    <listitem>Added colored box haplotype display.</listitem>
+                    <listitem>Tweaked command line flags.</listitem>
+                    <listitem>Added command line checkdata only output.</listitem>
+                    <listitem>More accurate Hardy-Weinberg code implemented (courtesy of
+                        G. Abecasis & J. Wigginton).</listitem>
+                    <listitem>Correctly deals with genotype inputs with poorly genotyped markers.</listitem>
+                </itemizedlist>
+            </section>
+
+            <section>
+                <title>Version 2.03, 18 December 2003</title>
+                <itemizedlist>
+                    <listitem>Correctly handles new HapMap Project dump format.</listitem>
+                    <listitem>Added minor allele frequency filter to data check tab.</listitem>
+                </itemizedlist>
+            </section>
+      
+            <section>
+                <title>Version 2.02, 05 December 2003</title>
+                <itemizedlist>
+                    <listitem>fixed confidence bounds coloring scheme bug</listitem>
+                    <listitem>fixed text output bug where marker numbers were
+                        incorrect</listitem>
+                    <listitem>added marker spacing map to top of HapMap datasets</listitem>
+                    <listitem>added T-int statistic to text dump of LD chart</listitem>
+                </itemizedlist>
+            </section>
+      
+            <section>
+                <title>Version 2.01, 31 October 2003</title>
+                <itemizedlist>
+                    <listitem>fixed bug involving European style decimal format (e.g. 0,45
+                        vs. 0.45)</listitem>
+                    <listitem>when exporting data, default file name is now blank</listitem>
+                    <listitem>marker info file now works in either forward or reverse
+                        direction</listitem>
+                    <listitem>activated loading of HapMap Project dumped data</listitem>
+                </itemizedlist>
+            </section>
+        </section>
+    </chapter>
+
+</book>
+
diff --git a/edu/mit/wi/haploview/BasicTableModel.java b/edu/mit/wi/haploview/BasicTableModel.java
new file mode 100755
index 0000000..1dd63b6
--- /dev/null
+++ b/edu/mit/wi/haploview/BasicTableModel.java
@@ -0,0 +1,42 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.table.AbstractTableModel;
+import java.util.Vector;
+
+public class BasicTableModel extends AbstractTableModel{
+    static final long serialVersionUID = -5805808329328199L;
+
+    Vector columnNames; Vector data;
+
+    public BasicTableModel(Vector c, Vector d){
+        columnNames=c;
+        data=d;
+    }
+
+    public String getColumnName(int i){
+        return (String)columnNames.elementAt(i);
+    }
+
+    public Class getColumnClass(int c){
+        //things look nicer if we use the String renderer to left align all the cols.
+        return String.class;
+    }
+
+    public int getColumnCount(){
+        return columnNames.size();
+    }
+
+    public int getRowCount(){
+        return data.size();
+    }
+
+    public Object getValueAt(int row, int column){
+        return ((Vector)data.elementAt(row)).elementAt(column);
+    }
+
+    public void setValueAt(Object o, int row, int column){
+        ((Vector)data.elementAt(row)).set(column,o);
+        fireTableCellUpdated(row, column);
+    }
+
+}
diff --git a/edu/mit/wi/haploview/CheckDataController.java b/edu/mit/wi/haploview/CheckDataController.java
new file mode 100755
index 0000000..d803071
--- /dev/null
+++ b/edu/mit/wi/haploview/CheckDataController.java
@@ -0,0 +1,105 @@
+package edu.mit.wi.haploview;
+
+import edu.mit.wi.pedfile.CheckData;
+
+import javax.swing.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+
+
+public class CheckDataController extends JPanel implements ActionListener {
+    static final long serialVersionUID = 3169384768782728106L;
+
+    private NumberTextField hwcut, genocut, mendcut, mafcut;
+    CheckDataPanel cdp;
+
+    public CheckDataController(CheckDataPanel cdp){
+        this.cdp = cdp;
+        JPanel failPanel = new JPanel();
+        failPanel.setLayout(new BoxLayout(failPanel,BoxLayout.Y_AXIS));
+        JPanel holdPanel = new JPanel();
+        holdPanel.add(new JLabel("HW p-value cutoff: "));
+        hwcut = new NumberTextField(String.valueOf(CheckData.hwCut),8,true,false);
+        holdPanel.add(hwcut);
+        failPanel.add(holdPanel);
+        holdPanel = new JPanel();
+        holdPanel.add(new JLabel("Min genotype %: "));
+        genocut = new NumberTextField(String.valueOf(CheckData.failedGenoCut),3, false,false);
+        holdPanel.add(genocut);
+        failPanel.add(holdPanel);
+        holdPanel = new JPanel();
+        holdPanel.add(new JLabel("Max # mendel errors: "));
+        mendcut = new NumberTextField(String.valueOf(CheckData.numMendErrCut),4,false,false);
+        holdPanel.add(mendcut);
+        failPanel.add(holdPanel);
+        holdPanel = new JPanel();
+        holdPanel.add(new JLabel("Minimum minor allele freq."));
+        mafcut = new NumberTextField(String.valueOf(CheckData.mafCut),8,true,false);
+        holdPanel.add(mafcut);
+        failPanel.add(holdPanel);
+        JPanel newPanel = new JPanel();
+        newPanel.setLayout(new BoxLayout(newPanel,BoxLayout.X_AXIS));
+
+        JButton selAll = new JButton("Select All");
+        selAll.addActionListener(this);
+        newPanel.add(selAll);
+        JButton deSelAll = new JButton("Deselect All");
+        deSelAll.addActionListener(this);
+        newPanel.add(deSelAll);
+        JButton setDefault = new JButton("Reset Values");
+        setDefault.addActionListener(this);
+        newPanel.add(setDefault);
+        JButton rescore = new JButton("Rescore Markers");
+        rescore.addActionListener(this);
+        newPanel.add(rescore);
+        if (cdp.isPlink()){
+            JButton plinkOnly = new JButton("Select only PLINK Markers");
+            plinkOnly.addActionListener(this);
+            newPanel.add(plinkOnly);
+        }
+
+        this.add(failPanel);
+        failPanel.add(newPanel);
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if (command.equals("Select All")){
+            cdp.selectAll();
+        }else if (command.equals("Deselect All")){
+            cdp.deSelectAll();
+        }else if (command.equals("Select only PLINK Markers")){
+            cdp.plinkOnly();
+        }else if (command.equals("Rescore Markers")){
+            String cut = hwcut.getText();
+            if (cut.equals("")){
+                cut = "0";
+            }
+            CheckData.hwCut = Double.parseDouble(cut);
+
+            cut = genocut.getText();
+            if (cut.equals("")){
+                cut="0";
+            }
+            CheckData.failedGenoCut = Integer.parseInt(cut);
+            cut = mendcut.getText();
+            if (cut.equals("")){
+                cut="0";
+            }
+            CheckData.numMendErrCut = Integer.parseInt(cut);
+            cut = mafcut.getText();
+            if (cut.equals("")){
+                cut="0";
+            }
+            CheckData.mafCut = Double.parseDouble(cut);
+
+            cdp.redoRatings();
+            cdp.getTable().repaint();
+        }else if (command.equals("Reset Values")){
+            hwcut.setText(String.valueOf(CheckData.defaultHwCut));
+            genocut.setText(String.valueOf(CheckData.defaultFailedGenoCut));
+            mendcut.setText(String.valueOf(CheckData.defaultNumMendErrCut));
+            mafcut.setText(String.valueOf(CheckData.defaultMafCut));
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/CheckDataPanel.java b/edu/mit/wi/haploview/CheckDataPanel.java
new file mode 100755
index 0000000..185deeb
--- /dev/null
+++ b/edu/mit/wi/haploview/CheckDataPanel.java
@@ -0,0 +1,470 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import javax.swing.event.TableModelListener;
+import javax.swing.event.TableModelEvent;
+import javax.swing.table.*;
+import java.awt.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.util.Vector;
+import java.io.File;
+import java.io.IOException;
+import java.io.FileWriter;
+
+import edu.mit.wi.pedfile.MarkerResult;
+import edu.mit.wi.pedfile.PedFile;
+import edu.mit.wi.pedfile.CheckData;
+
+public class CheckDataPanel extends JPanel
+        implements TableModelListener, ActionListener {
+    static final long serialVersionUID = 4043744314283837703L;
+    private JTable table;
+    private CheckDataTableModel tableModel;
+    private CheckDataTableSorter sorter;
+    private HaploData theData;
+
+    boolean changed;
+    static int STATUS_COL = 8;
+    private HaploView hv;
+
+    public CheckDataPanel(HaploView hv){
+        this(hv.theData);
+        this.hv = hv;
+
+        setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
+
+        JPanel missingPanel = new JPanel();
+        JLabel countsLabel;
+        countsLabel = new JLabel("Using " + theData.numSingletons + " singletons and "
+                + theData.numTrios + " trios from "
+                + theData.numPeds + " families.");
+        if (theData.numTrios + theData.numSingletons == 0){
+            countsLabel.setForeground(Color.red);
+        }
+        countsLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
+        missingPanel.add(countsLabel);
+        JButton advancedButton = new JButton("Advanced Views");
+        advancedButton.addActionListener(this);
+        missingPanel.add(advancedButton);
+        missingPanel.setBorder(BorderFactory.createLineBorder(Color.black));
+        JPanel extraPanel = new JPanel();
+        extraPanel.add(missingPanel);
+
+        sorter = new CheckDataTableSorter(tableModel);
+        table = new JTable(sorter);
+        sorter.setTableHeader(table.getTableHeader());
+        table.getTableHeader().setReorderingAllowed(false);
+
+        final CheckDataCellRenderer renderer = new CheckDataCellRenderer();
+        try{
+            table.setDefaultRenderer(Class.forName("java.lang.Double"), renderer);
+            table.setDefaultRenderer(Class.forName("java.lang.Integer"), renderer);
+            table.setDefaultRenderer(Class.forName("java.lang.Long"), renderer);
+            table.setDefaultRenderer(Class.forName("java.lang.String"),renderer);
+        }catch (Exception e){
+        }
+
+        table.getColumnModel().getColumn(0).setPreferredWidth(30);
+        table.getColumnModel().getColumn(0).setMinWidth(30);
+        if (theData.infoKnown){
+            table.getColumnModel().getColumn(1).setMinWidth(100);
+            table.getColumnModel().getColumn(2).setMinWidth(60);
+        }
+        JScrollPane tableScroller = new JScrollPane(table);
+        //changed 600 to 800 and tableScroller.getPreferredSize().height to Integer.MAX_VALUE.
+        tableScroller.setMaximumSize(new Dimension(800, Integer.MAX_VALUE));
+
+        add(extraPanel);
+        add(tableScroller);
+
+        if (theData.dupsToBeFlagged){
+            JOptionPane.showMessageDialog(hv,
+                    "Two or more SNPs have identical position. They have been flagged in yellow\n"+
+                            "and the less completely genotyped duplicate has been deselected.",
+                    "Duplicate SNPs",
+                    JOptionPane.INFORMATION_MESSAGE);
+        }
+
+        if (theData.dupNames){
+            JOptionPane.showMessageDialog(hv,
+                    "Two or more SNPs have identical names. They have been renamed with\n"+
+                            ".X extensions where X is an integer unique to each duplicate.",
+                    "Duplicate SNPs",
+                    JOptionPane.INFORMATION_MESSAGE);
+        }
+    }
+
+    public CheckDataPanel(HaploData hd){
+        STATUS_COL = 9;
+
+        theData = hd;
+        PedFile pf = theData.getPedFile();
+
+        Vector tableColumnNames = pf.getColumnNames();
+        if (theData.infoKnown){
+            STATUS_COL += 2;
+        }
+
+        tableModel = new CheckDataTableModel(tableColumnNames, pf.getTableData(), pf.getMarkerRatings(), pf.getDups());
+        tableModel.addTableModelListener(this);
+    }
+
+    public JTable getTable(){
+        return table;
+    }
+
+    public void tableChanged(TableModelEvent e) {
+        if (e.getColumn() == STATUS_COL){
+            changed = true;
+        }
+    }
+
+    public void selectAll(){
+        for (int i = 0; i < table.getRowCount(); i++){
+            table.setValueAt(new Boolean(true), i, STATUS_COL);
+        }
+        changed = true;
+    }
+
+    public void deSelectAll(){
+        for (int i = 0; i < table.getRowCount(); i++){
+            table.setValueAt(new Boolean(false), i, STATUS_COL);
+        }
+        changed = true;
+    }
+
+    public void plinkOnly(){
+         try{
+	     //Vector result = new CheckData(theData.getPedFile()).check();
+
+            for (int j = 0; j < table.getColumnCount(); j++){
+                sorter.setSortingStatus(j,TableSorter.NOT_SORTED);
+            }
+
+            for (int i = 0; i < table.getRowCount(); i++){
+                //MarkerResult cur = (MarkerResult)result.get(i);
+
+                //use this marker if it has "extra info", a sign of PLINK status
+                if (Chromosome.getUnfilteredMarker(i).getExtra() != null){
+                    table.setValueAt(new Boolean(true),i,STATUS_COL);
+                }else{
+                    table.setValueAt(new Boolean(false),i,STATUS_COL);
+                }
+            }
+            changed = true;
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    public void redoRatings(){
+        try{
+            Vector result = new CheckData(theData.getPedFile()).check();
+
+            for (int j = 0; j < table.getColumnCount(); j++){
+                sorter.setSortingStatus(j,TableSorter.NOT_SORTED);
+            }
+
+            for (int i = 0; i < table.getRowCount(); i++){
+                MarkerResult cur = (MarkerResult)result.get(i);
+
+                //use this marker as long as it has a good (i.e. positive) rating and is not an "unused" dup (==2)
+                int curRating = cur.getRating();
+                int dupStatus = Chromosome.getUnfilteredMarker(i).getDupStatus();
+                if ((curRating > 0 && dupStatus != 2) ||
+                        theData.getPedFile().isWhiteListed(Chromosome.getUnfilteredMarker(i))){
+                    table.setValueAt(new Boolean(true),i,STATUS_COL);
+                }else{
+                    table.setValueAt(new Boolean(false),i,STATUS_COL);
+                }
+                tableModel.setRating(i,curRating);
+            }
+            changed = true;
+        }catch (Exception e){
+            e.printStackTrace();
+        }
+    }
+
+    public boolean[] getMarkerResults(){
+        for (int i = 0; i < table.getColumnCount(); i++){
+            sorter.setSortingStatus(i,TableSorter.NOT_SORTED);
+        }
+        boolean[] markerResults = new boolean[table.getRowCount()];
+        for (int i = 0; i < table.getRowCount(); i++){
+            markerResults[i] = ((Boolean)table.getValueAt(i,CheckDataPanel.STATUS_COL)).booleanValue();
+        }
+
+        return markerResults;
+    }
+
+    public void saveTableToText(File outfile) throws IOException {
+        FileWriter checkWriter = null;
+        if (outfile != null){
+            checkWriter = new FileWriter(outfile);
+        }else{
+            throw new IOException("Error saving checkdata to file.");
+        }
+
+        Vector names = new Vector();
+        for (int i = 0; i < table.getColumnCount(); i ++){
+            names.add(table.getColumnName(i));
+        }
+        int numCols = names.size();
+        StringBuffer header = new StringBuffer();
+        for (int i = 0; i < numCols; i++){
+            header.append(names.get(i)).append("\t");
+        }
+        header.append("\n");
+        checkWriter.write(header.toString());
+
+        for (int i = 0; i < table.getRowCount(); i++){
+            StringBuffer sb = new StringBuffer();
+            //don't print the true/false vals in last column
+            for (int j = 0; j < numCols-1; j++){
+                sb.append(table.getValueAt(i,j)).append("\t");
+            }
+            //print BAD if last column is false
+            if (((Boolean)table.getValueAt(i,numCols-1)).booleanValue()){
+                sb.append("\n");
+            }else{
+                sb.append("BAD\n");
+            }
+            checkWriter.write(sb.toString());
+        }
+
+        checkWriter.close();
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if (command.equals("Advanced Views")) {
+            AdvancedDialog ad = new AdvancedDialog("Advanced Views");
+            ad.pack();
+            ad.setVisible(true);
+        }
+    }
+
+    class CheckDataTableModel extends AbstractTableModel {
+        static final long serialVersionUID = 6488280863408672540L;
+        Vector columnNames; Vector data; int[] ratings; int[] dups;
+
+        public CheckDataTableModel(Vector c, Vector d, int[] r, int[] dups){
+            columnNames=c;
+            data=d;
+            ratings = r;
+            this.dups = dups;
+        }
+
+        public int getColumnCount(){
+            return columnNames.size();
+        }
+
+        public int getRowCount(){
+            return data.size();
+        }
+
+        public Object getValueAt(int row, int column){
+            Object value = ((Vector)data.elementAt(row)).elementAt(column);
+            if ((getColumnName(column).equals("ObsHET") || getColumnName(column).equals("PredHET") || getColumnName(column).equals("HWpval")) &&
+                    Chromosome.getDataChrom().equalsIgnoreCase("chrx") && ((Double)value).doubleValue() == Double.MAX_VALUE){
+                value = "NA";
+            }
+            return value;
+        }
+
+        public Class getColumnClass(int c){
+            return getValueAt(0, c).getClass();
+        }
+
+        public int getRating(int row){
+            return ratings[row];
+        }
+
+        public int getDupStatus(int row){
+            return dups[row];
+        }
+
+        public void setRating(int row, int value) {
+            if(row < ratings.length) {
+                ratings[row] = value;
+            }
+        }
+
+        public String getColumnName(int n){
+            return (String)columnNames.elementAt(n);
+        }
+
+        public boolean isCellEditable(int row, int col){
+            if (getColumnName(col).equals("Rating")){
+                return true;
+            }else{
+                return false;
+            }
+        }
+
+        public void setValueAt(Object value, int row, int col){
+            ((Vector)data.elementAt(row)).set(col, value);
+            fireTableCellUpdated(row, col);
+        }
+    }
+
+    class CheckDataCellRenderer extends DefaultTableCellRenderer {
+        static final long serialVersionUID = 2291163738308005244L;
+        public Component getTableCellRendererComponent
+                (JTable table, Object value, boolean isSelected,
+                 boolean hasFocus, int row, int column)
+        {
+            Component cell = super.getTableCellRendererComponent
+                    (table, value, isSelected, hasFocus, row, column);
+            int myRating = ((CheckDataTableSorter)table.getModel()).getRating(row);
+            int myDupStatus = ((CheckDataTableSorter)table.getModel()).getDupStatus(row);
+            String thisColumnName = table.getColumnName(column);
+            cell.setForeground(Color.black);
+            cell.setBackground(Color.white);
+
+            if (myDupStatus > 0){
+                //I'm a dup so color the background in bright, ugly yellow
+                cell.setBackground(Color.yellow);
+            }
+
+            if (hv.getChosenMarker() != null){
+                if (((String)table.getValueAt(row,1)).equals(hv.getChosenMarker())){
+                    cell.setBackground(Color.cyan);
+                }
+            }
+
+            //bitmasking to decode the status bits
+            if (myRating < 0){
+                myRating *= -1;
+                if ((myRating & 1) != 0){
+                    if(thisColumnName.equals("ObsHET")){
+                        cell.setForeground(Color.red);
+                    }
+                }
+                if ((myRating & 2) != 0){
+                    if (thisColumnName.equals("%Geno")){
+                        cell.setForeground(Color.red);
+                    }
+                }
+                if ((myRating & 4) != 0){
+                    if (thisColumnName.equals("HWpval")){
+                        cell.setForeground(Color.red);
+                    }
+                }
+                if ((myRating & 8) != 0){
+                    if (thisColumnName.equals("MendErr")){
+                        cell.setForeground(Color.red);
+                    }
+                }
+                if ((myRating & 16) != 0){
+                    if (thisColumnName.equals("MAF")){
+                        cell.setForeground(Color.red);
+                    }
+                }
+            }
+            return cell;
+        }
+    }
+
+    public boolean isPlink(){
+      return (hv.plinkPanel != null);
+    }
+    class CheckDataTableSorter extends TableSorter {
+        static final long serialVersionUID = -2595109594459247282L;
+
+        CheckDataTableSorter(TableModel tm){
+            super(tm);
+        }
+
+        public int getRating(int row){
+            return ((CheckDataPanel.CheckDataTableModel)tableModel).getRating(modelIndex(row));
+        }
+
+        public int getDupStatus(int row){
+            return ((CheckDataPanel.CheckDataTableModel)tableModel).getDupStatus(modelIndex(row));
+        }
+    }
+
+    class AdvancedDialog extends JDialog implements ActionListener {
+        static final long serialVersionUID = -2462574330267016326L;
+
+        JFileChooser fc;
+        AdvancedDialog(String title){
+            super(hv, title);
+
+            JPanel contents = new JPanel();
+            contents.setPreferredSize(new Dimension(150,125));
+            contents.setLayout(new GridBagLayout());
+            GridBagConstraints c = new GridBagConstraints();
+
+            c.anchor = GridBagConstraints.WEST;
+            c.insets = new Insets(0,0,2,0);
+            int size = 0;
+
+
+            JButton individualButton = new JButton("Individual Summary");
+            individualButton.addActionListener(this);
+            contents.add(individualButton,c);
+            c.insets = new Insets(2,0,2,0);
+            if (hv.theData.getPedFile().getAxedPeople().size() != 0){
+                JButton missingButton = new JButton("Excluded Individuals");
+                missingButton.addActionListener(this);
+                c.gridy = 1;
+                contents.add(missingButton,c);
+                size++;
+            }
+            if (hv.theData.getPedFile().getMendelsExist()){
+                JButton mendelButton = new JButton("Mendel Errors");
+                mendelButton.addActionListener(this);
+                c.gridy = 2;
+                contents.add(mendelButton,c);
+            }
+            if (hv.theData.getPedFile().getHaploidHets() != null){
+                JButton maleHetsButton = new JButton("Male Heterozygotes");
+                maleHetsButton.addActionListener(this);
+                c.gridy = 3;
+                c.insets = new Insets(2,0,0,0);
+                contents.add(maleHetsButton,c);
+                size++;
+            }
+
+            if (size == 0){
+                contents.setPreferredSize(new Dimension(150,50));
+            }
+
+            setContentPane(contents);
+            this.setLocation(this.getParent().getX() + 100,
+                    this.getParent().getY() + 100);
+            this.setModal(true);
+
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            String command = e.getActionCommand();
+
+            if (command.equals("Excluded Individuals")) {
+                //show details of individuals removed due to excessive missing data
+                FilteredIndividualsDialog fid = new FilteredIndividualsDialog(hv,"Excluded Individuals");
+                fid.pack();
+                fid.setVisible(true);
+                this.dispose();
+            }else if (command.equals("Individual Summary")) {
+                IndividualDialog fd = new IndividualDialog(hv,"Individual Summary");
+                fd.pack();
+                fd.setVisible(true);
+                this.dispose();
+            }else if (command.equals("Mendel Errors")) {
+                MendelDialog md = new MendelDialog(hv,"Mendel Errors");
+                md.pack();
+                md.setVisible(true);
+                this.dispose();
+            }else if (command.equals("Male Heterozygotes")){
+                HetsDialog hd = new HetsDialog(hv,"Male Heterozygotes");
+                hd.pack();
+                hd.setVisible(true);
+                this.dispose();
+            }
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/CheckDataTableSorter.java b/edu/mit/wi/haploview/CheckDataTableSorter.java
new file mode 100755
index 0000000..4a388cd
--- /dev/null
+++ b/edu/mit/wi/haploview/CheckDataTableSorter.java
@@ -0,0 +1,19 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.table.TableModel;
+
+public class CheckDataTableSorter extends TableSorter {
+    static final long serialVersionUID = -1547877018850397541L;
+
+    CheckDataTableSorter(TableModel tm){
+        super(tm);
+    }
+
+    public int getRating(int row){
+        return ((CheckDataPanel.CheckDataTableModel)tableModel).getRating(modelIndex(row));
+    }
+
+    public int getDupStatus(int row){
+        return ((CheckDataPanel.CheckDataTableModel)tableModel).getDupStatus(modelIndex(row));
+    }
+}
diff --git a/edu/mit/wi/haploview/Chromosome.java b/edu/mit/wi/haploview/Chromosome.java
new file mode 100755
index 0000000..5c885ab
--- /dev/null
+++ b/edu/mit/wi/haploview/Chromosome.java
@@ -0,0 +1,187 @@
+package edu.mit.wi.haploview;
+
+import java.util.Vector;
+
+
+public class Chromosome{
+
+    private String ped;
+    private String individual;
+
+    private int affected;
+    //kidAffected stores the status of the child of this chromosome if it is from a trio
+    private Integer kidAffected;
+    //genotypes[] used to be private but the accessor was wasting a lot of time when it would
+    //get called literally millions of times. so we allow other classes to touch this array
+    //in the interest of speed
+    byte[] genotypes;
+    private String origin;
+
+    private static String dataChrom = "none";
+    //todo: why aren't these null by default? there's some reason but I can't remember
+    private static String dataBuild = "none";
+    public static int[] realIndex;
+    public static int[] filterIndex;
+    static Vector markers;
+    static int trueSize;
+    private boolean haploid = false;
+    //private boolean phased = false;
+
+    Chromosome(String p, String i, byte[] g, String o, int a, boolean isPhased) throws HaploViewException{
+        ped = p;
+        individual = i;
+        genotypes = g;
+        if(a < 0 || a >2) {
+            throw new HaploViewException("invalid affected status");
+        }
+        affected = a;
+        origin = o;
+        trueSize = genotypes.length;
+        //phased = isPhased;
+    }
+
+    Chromosome(String p, String i, byte[] g, int a, int kidA, boolean isPhased) throws HaploViewException{
+        ped = p;
+        individual = i;
+        genotypes = g;
+        if(a < 0 || a >2) {
+            throw new HaploViewException("invalid affected status");
+        }
+        affected = a;
+        kidAffected = new Integer(kidA);
+        origin = "unknown";
+        trueSize = genotypes.length;
+        //phased = isPhased;
+    }
+
+    public int getAffected() {
+        return affected;
+    }
+
+    public static void doFilter(boolean[] markerResults) {
+        //set up the indexing to take into account skipped markers. Need
+        //to loop through twice because first time we just count number of
+        //unskipped markers
+        int count = 0;
+        for (int i = 0; i < markerResults.length; i++){
+            if (markerResults[i]){
+                count++;
+            }
+        }
+        Chromosome.filterIndex = new int[markerResults.length];
+        Chromosome.realIndex = new int[count];
+        int k = 0;
+        for (int i =0; i < markerResults.length; i++){
+            if (markerResults[i]){
+                realIndex[k] = i;
+                filterIndex[i] = k;
+                k++;
+            }else{
+                filterIndex[i] = -1;
+            }
+        }
+    }
+
+    public static void doFilter(int size){
+        realIndex = new int[size];
+        filterIndex = new int[size];
+        for (int i = 0; i < size; i++){
+            realIndex[i] = i;
+            filterIndex[i] = i;
+        }
+    }
+
+    //all of these accessors below (getGenotype, getSize and getMarker) are for the filtered
+    //array of markers because after the data are loaded one usually wants to only be looking
+    //at the markers that haven't been filtered out of subsequent analyses. the getUnfiltered
+    //versions of same are mostly used in the early processing steps
+
+    public byte getGenotype(int i){
+        //gets genotype from filtered position i
+        return genotypes[realIndex[i]];
+    }
+
+    public byte getUnfilteredGenotype(int i){
+        //gets genotype from unfiltered position i
+        return genotypes[i];
+    }
+
+    public static int getSize(){
+        //get number of filtered markers
+        return realIndex.length;
+    }
+
+    public static int getUnfilteredSize(){
+        //get total number of markers (i.e. without filtering)
+        return trueSize;
+    }
+
+    public static SNP getUnfilteredMarker(int i){
+        //get SNP at unfiltered position i
+        return (SNP)markers.get(i);
+    }
+
+    public static SNP getMarker(int i){
+        //get SNP at filtered position i
+        return (SNP)markers.get(realIndex[i]);
+    }
+
+    public static Vector getAllMarkers(){
+        return markers;
+    }
+
+    public String getPed(){
+        return ped;
+    }
+
+    public String getIndividual(){
+        return individual;
+    }
+
+    public String getOrigin(){
+        return origin;
+    }
+
+    public void setGenotype(byte gen,int pos){
+        this.genotypes[pos] = gen;
+    }
+
+    public static void setDataChrom(String chrom) {
+        if (chrom != null){
+            dataChrom = chrom.toLowerCase();
+        }else{
+            dataChrom = null;
+        }
+    }
+
+    public static String getDataChrom(){
+        return dataChrom;
+    }
+
+    public static void setDataBuild(String build){
+        dataBuild = build;
+    }
+
+    public static String getDataBuild(){
+        return dataBuild;
+    }
+
+    public Integer getKidAffected() {
+        return kidAffected;
+    }
+
+    public void setKidAffected(int kidAffected) {
+        this.kidAffected = new Integer(kidAffected);
+    }
+
+
+    public boolean isHaploid() {
+        return haploid;
+    }
+
+    public void setHaploid(boolean haploid) {
+        this.haploid = haploid;
+    }
+}
+
+
diff --git a/edu/mit/wi/haploview/Configuration.java b/edu/mit/wi/haploview/Configuration.java
new file mode 100755
index 0000000..32a9ac4
--- /dev/null
+++ b/edu/mit/wi/haploview/Configuration.java
@@ -0,0 +1,74 @@
+package edu.mit.wi.haploview;
+
+import java.util.*;
+import java.io.*;
+/**
+ * Created by IntelliJ IDEA.
+ * User: julian
+ * Date: Aug 25, 2004
+ * Time: 10:54:29 AM
+ * To change this template use File | Settings | File Templates.
+ */
+public class Configuration {
+    private static Properties config;
+
+    private static void initializeConfig() {
+        Properties defaults = new Properties();
+        defaults.setProperty("checkForUpdate","true");
+        config = new Properties(defaults);
+    }
+
+
+    public static void readConfigFile() {
+        if(config == null) {
+            initializeConfig();
+        }
+
+        String homeDir = System.getProperty("user.home");
+        File configFile = new File(homeDir + "/.haploview");
+        if(configFile.exists()) {
+            BufferedInputStream bis = null;
+            try {
+                bis = new BufferedInputStream(new FileInputStream(configFile));
+                config.load(bis);
+                bis.close();
+            } catch(IOException ioe) {
+                //it doesnt really matter if we cant read the file; we just use the defaults
+            }
+
+        }
+    }
+
+    public static void writeConfigFile() {
+        if(config != null) {
+            String homeDir = System.getProperty("user.home");
+            File configFile = new File(homeDir + "/.haploview");
+            BufferedOutputStream bos = null;
+            try {
+                bos = new BufferedOutputStream(new FileOutputStream(configFile));
+                config.store(bos, "Haploview Configuration file. Automatically generated, do not edit");
+            } catch(IOException ioe) {
+                //well, something went wrong writing the file, but we dont really care
+            }
+        }
+    }
+
+    public static boolean isCheckForUpdate() {
+        if(config == null) {
+            initializeConfig();
+        }
+        return (config.getProperty("checkForUpdate").equals("true"));
+    }
+
+    public static void setCheckForUpdate(boolean b) {
+        if(config == null) {
+            initializeConfig();
+        }
+        if(b) {
+            config.setProperty("checkForUpdate", "true");
+        } else {
+            config.setProperty("checkForUpdate", "false");
+        }
+
+    }
+}
diff --git a/edu/mit/wi/haploview/Constants.java b/edu/mit/wi/haploview/Constants.java
new file mode 100755
index 0000000..10b2c46
--- /dev/null
+++ b/edu/mit/wi/haploview/Constants.java
@@ -0,0 +1,243 @@
+package edu.mit.wi.haploview;
+
+
+public interface Constants {
+
+    //main jframe setup stuff & labels.
+    public static final double VERSION = 4.1;
+    public static final int BETA_VERSION = 0;
+    public static final String EMAIL_STRING = "haploview at broad.mit.edu";
+    public static final String TITLE_STRING = "Haploview "+VERSION;
+    public static final String JAVA_VERSION = System.getProperty("java.version");
+    public static final String USER_AGENT = "Haploview/" + VERSION + " Java/" + JAVA_VERSION;
+    public static final String RELEASE_DATE = "29 April, 2008";
+    public static final String WEBSITE_STRING = "http://www.broad.mit.edu/mpg/haploview/";
+    public static final String BETA_WEBSITE_STRING = "http://www.broad.mit.edu/mpg/haploview/beta.php";
+    public static final String CITATION_STRING = "Citation: Barrett JC, Fry B, Maller J, Daly MJ.\nHaploview: analysis and visualization of LD and haplotype maps.\nBioinformatics. 2005";
+    public static final String ABOUT_STRING = TITLE_STRING + "\n" + RELEASE_DATE + "\n" +
+            WEBSITE_STRING + "\n\n" + CITATION_STRING + "\n\n" +
+            "Daly Lab at the Broad Institute\n" +
+            "Cambridge, MA 02141, USA\n\n"+
+            "Jeffrey Barrett\n" +
+            "Julian B. Maller\n" +
+            "David J. Bender\n" +
+            "Jesse C. Whitworth\n" +
+            EMAIL_STRING;
+
+    public static final String READ_GENOTYPES = "Open new data";
+    public static final String READ_MARKERS = "Load marker data";
+    public static final String READ_ANALYSIS_TRACK = "Load analysis track";
+    public static final String READ_BLOCKS_FILE = "Load block definitions";
+    public static final String DOWNLOAD_GBROWSE = "Download HapMap info track";
+    public static final String GBROWSE_OPTS = "HapMap Info Track Options";
+
+    public static final String EXPORT_TEXT = "Export current tab to text";
+    public static final String EXPORT_PNG = "Export current tab to PNG";
+    public static final String EXPORT_OPTIONS = "Export options";
+
+    public static final String CLEAR_BLOCKS = "Clear all blocks";
+    public static final String CUST_BLOCKS = "Customize Block Definitions";
+
+    public static final String VIEW_DPRIME = "LD Plot";
+    public static final String VIEW_HAPLOTYPES = "Haplotypes";
+    public static final String VIEW_CHECK_PANEL = "Check Markers";
+    public static final String VIEW_ASSOC = "Association";
+    public static final String VIEW_PLINK = "PLINK";
+    public static final String VIEW_TAGGER = "Tagger";
+
+    //main frame tab numbers
+    public static final int VIEW_D_NUM = 0;
+    public static final int VIEW_HAP_NUM = 1;
+    public static final int VIEW_CHECK_NUM = 2;
+    public static final int VIEW_TAGGER_NUM = 3;
+    public static final int VIEW_PLINK_NUM = 4;
+    public static final int VIEW_ASSOC_NUM = 5;
+
+    //association tab subtab indices
+    public static final int VIEW_SINGLE_ASSOC = 0;
+    public static final int VIEW_HAPLO_ASSOC = 1;
+
+    //export modes
+    public static final int PNG_MODE = 0;
+    public static final int TXT_MODE = 1;
+    public static final int COMPRESSED_PNG_MODE = 2;
+    public static final int SVG_MODE = 3;
+    public static final int TABLE_TYPE = 0;
+    public static final int LIVE_TYPE = 1;
+
+    //block defs
+    public static final int BLOX_GABRIEL = 0;
+    public static final int BLOX_4GAM = 1;
+    public static final int BLOX_SPINE = 2;
+    public static final int BLOX_CUSTOM = 3;
+    public static final int BLOX_ALL = 4;
+    public static final int BLOX_NONE = 5;
+
+    //filetypes
+    static final int GENO_FILE = 0;
+    static final int INFO_FILE = 1;
+    static final int HAPS_FILE = 2;
+    static final int PED_FILE = 3;
+    static final int HMP_FILE = 4;
+    static final int ASSOC_FILE = 5;
+    static final int PHASEHMP_FILE = 6;
+    static final int HMPDL_FILE = 7;
+    static final int SAMPLEHMP_FILE = 8;
+    static final int LEGENDHMP_FILE = 9;
+    static final int PLINK_FILE = 10;
+    static final int MAP_FILE = 11;
+    //static final int FASTPHASE_FILE = 12;
+
+    //color modes
+    static final int STD_SCHEME = 0;
+    static final int RSQ_SCHEME = 1;
+    static final int WMF_SCHEME = 2;
+    static final int GAB_SCHEME = 3;
+    static final int GAM_SCHEME = 4;
+    static final int GOLD_SCHEME = 5;
+
+    //what LD stat to print
+    static final int D_PRIME = 0;
+    static final int R_SQ = 1;
+    static final int LD_NONE = 2;
+
+    //association test modes
+    static final int ASSOC_NONE = 0;
+    static final int ASSOC_TRIO = 1;
+    static final int ASSOC_CC = 2;
+
+    //tdt types
+    static final int TDT_STD = 0;
+    static final int TDT_PAREN = 1;
+
+    //single marker association display stuff
+    static final int SHOW_SINGLE_COUNTS = 0;
+    static final int SHOW_SINGLE_FREQS = 1;
+
+    //haplotype association display stuff
+    static final int SHOW_HAP_COUNTS = 0;
+    static final int SHOW_HAP_RATIOS = 1;
+
+    //default LD comparison distance (in kb)
+    static final int MAXDIST_DEFAULT = 500;
+
+    //phased data stuff
+    static final String[] CHROM_NAMES = {"1","2","3","4","5","6","7","8","9","10",
+            "11","12","13","14","15","16","17","18","19","20","21","22","X","Y"};
+    static final String[] PANEL_NAMES = {"CEU", "YRI", "CHB+JPT"};
+    static final String[] RELEASE_NAMES = {"16a","21","22"};
+    //static final String[] PHASE_FORMATS = {"HapMap PHASE","fastPHASE"};
+    static final String[] DOWNLOAD_FORMATS = {"Region","Gene ID"};
+
+    //GeneCruiser stuff
+    static final String[] GENE_DATABASES = {"Ensembl","HUGO","SNP"};
+    static final int ENSEMBL = 0;
+    static final int HUGO = 1;
+    static final int SNP = 2;
+
+    //plot types
+    static final String[] PLOT_TYPES = {"Untransformed", "-log10", "ln"};
+    static final int UNTRANSFORMED_PLOT = 0;
+    static final int LOG10_PLOT = 1;
+    static final int LN_PLOT = 2;
+
+    //GBrowse options
+    static final String[] GB_TYPES = {"gtsh", "mRNA", "recomb", "NT", "DNA"};
+    static final String[] GB_OPTS = {"gtsh%201", "mRNA%203", "", "", ""};
+    static final String[] GB_OPTS_NAMES = {"HapMap SNPs", "Entrez Genes", "Recombination Rate", "NT Contigs", "DNA/GC Content"};
+    static final String GB_DEFAULT_OPTS = GB_OPTS[0] + "+" + GB_OPTS[1];
+    static final String GB_DEFAULT_TYPES = GB_TYPES[0] + "+" + GB_TYPES[1];
+
+
+    static final String HELP_OUTPUT = TITLE_STRING + " Command line options\n" +
+            "-h, -help                       Print this message\n" +
+            "-memory <memsize>               allocates <memsize> megabytes of memory (default 512)\n"+
+            "-n, -nogui                      Command line output only\n" +
+            "-q, -quiet                      Quiet mode- doesnt print any warnings or information to screen\n" +
+            "-log <filename>                 Specify a logfile name (defaults to haploview.log if no name specified)\n" +
+            "-out <fileroot>                 Specify a fileroot to be used for all output files\n" +
+            "-pedfile <pedfile>              Specify an input file (or http:// location) in pedigree file format\n" +
+            "-hapmap <hapmapfile>            Specify an input file (or http:// location) in HapMap format\n" +
+            "-phasedhmpdata <phasedfile>     Specify a HapMap PHASE data file (or http:// location)\n" +
+            "-phasedhmpsample <samplefile>   Specify a HapMap PHASE sample file (or http:// location)\n" +
+            "-phasedhmplegend <legendfile>   Specify a HapMap PHASE legend file (or http:// location)\n" +
+            //"-fastphase <fastphasefile>      Specify an input file (or http:// location) in fastPHASE format\n" +
+            "-gzip                           Indicates that phased input files use GZIP compression\n" +
+            "-hapmapDownload                 Specify a phased HapMap download\n" +
+            "-haps <hapsfile>                Specify an input file (or http:// location) in .haps format\n" +
+            "-info <infofile>                Specify a marker info file (or http:// location)\n" +
+            "-plink <plinkfile>              Specify a PLINK or other results file (or http:// location)\n" +
+            "-map <mapfile>                  Specify a map file or binary map file (or http:// location)\n" +
+            "-nonSNP                         Specify that the accompanying PLINK file is non-SNP based output\n" +
+            "-selectCols                     Activate the preloading column filter for PLINK loads\n" +
+            "-batch <batchfile>              Batch mode. Each line in batch file should contain a genotype file \n"+
+            "                                followed by an optional info file, separated by a space.\n" +
+            "-blocks <blockfile>             Blocks file (or http:// location), one block per line, will force output for these blocks\n" +
+            "-track <trackfile>              Specify an input analysis track file (or http:// location)\n"+
+            "-excludeMarkers <markers>       Specify markers (in range 1-N where N is total number of markers) to be\n"+
+            "                                skipped for all analyses. Format: 1,2,5..12\n"+
+            "-skipcheck                      Skips the various genotype file checks\n" +
+            "-chromosome <1-22,X,Y>          Specifies the chromosome for this file or download\n" +
+            "-panel <CEU,YRI,CHB+JPT>        Specifies the analysis panel for this HapMap download\n" +
+            "-startpos <integer>             Specifies the start position in kb for this HapMap download\n" +
+            "-endpos <integer>               Specifies the end position in kb for this HapMap download\n" +
+            "-release <16a,21,22>            Specifies the HapMap phase for this HapMap download (defaults to 21)\n" +
+            "-dprime                         Outputs LD text to <fileroot>.LD\n" +
+            "-png                            Outputs LD display to <fileroot>.LD.PNG\n"+
+            "-compressedpng                  Outputs compressed LD display to <fileroot>.LD.PNG\n"+
+            "-svg                            Outputs svg format LD display to <fileroot>.LD.SVG\n"+
+            "-infoTrack                      Downloads and displays HapMap info track on PNG image output\n"+
+            "-ldcolorscheme <argument>       Specify an LD color scheme. <argument> should be one of:\n" +
+            "                                DEFAULT, RSQ, DPALT, GAB, GAM\n" +
+            "-ldvalues <DPRIME,RSQ,NONE>     Specify what to print in LD image output. default is DPrime\n" +
+            "-check                          Outputs marker checks to <fileroot>.CHECK\n" +
+            "                                note: -dprime  and -check default to no blocks output. \n" +
+            "                                Use -blockoutput to also output blocks\n" +
+            "-indcheck                       Outputs genotype percent per individual to <fileroot>.INDCHECK\n" +
+            "-mendel                         Outputs Mendel error information to <fileroot>.MENDEL\n" +
+            "-malehets                       Outputs male heterozygote information to <fileroot>.MALEHETS\n" +
+            "-blockoutput <GAB,GAM,SPI,ALL>  Output type. Gabriel, 4 gamete, spine output or all 3. default is Gabriel.\n" +
+            "-blockCutHighCI <thresh>        Gabriel 'Strong LD' high confidence interval D' cutoff.\n" +
+            "-blockCutLowCI <thresh>         Gabriel 'Strong LD' low confidence interval D' cutoff.\n" +
+            "-blockMAFThresh <thresh>        Gabriel MAF threshold.\n" +
+            "-blockRecHighCI <thresh>        Gabriel recombination high confidence interval D' cutoff.\n" +
+            "-blockInformFrac <thresh>       Gabriel fraction of informative markers required to be in LD.\n" +
+            "-block4GamCut <thresh>          4 Gamete block cutoff for frequency of 4th pairwise haplotype.\n" +
+            "-blockSpineDP <thresh>          Solid Spine blocks D' cutoff for 'Strong LD\n"+
+            "-maxdistance <distance>         Maximum comparison distance in kilobases (integer). Default is 500\n" +
+            "-hapthresh <frequency>          Only output haps with at least this frequency\n" +
+            "-spacing <threshold>            Proportional spacing of markers in LD display. <threshold> is a value\n" +
+            "                                between 0 (no spacing) and 1 (max spacing). Default is 0\n"  +
+            "-minMAF <threshold>             Minimum minor allele frequency to include a marker. <threshold> is a value\n" +
+            "                                between 0 and 0.5. Default is .001\n" +
+            "-maxMendel <integer>            Markers with more than <integer> Mendel errors will be excluded. Default is 1.\n" +
+            "-minGeno <threshold>            Exclude markers with less than <threshold> valid data. <threshold> is a value\n" +
+            "                                between 0 and 1. Default is .75\n" +
+            "-hwcutoff <threshold>           Exclude markers with a HW p-value smaller than <threshold>. <threshold> is a value\n" +
+            "                                between 0 and 1. Default is .001\n" +
+            "-missingCutoff <threshold>      Exclude individuals with more than <threshold> fraction missing data.\n" +
+            "                                <threshold> is a value between 0 and 1. Default is .5 \n" +
+            "-assocCC                        Outputs case control association results to <fileroot>.ASSOC and <fileroot>.HAPASSOC\n" +
+            "-assocTDT                       Outputs trio association results to <fileroot>.ASSOC and <fileroot>.HAPASSOC\n" +
+            "-customAssoc <file>             Loads a set of custom tests for association.\n" +
+            "-permtests <numtests>           Performs <numtests> permutations on default association tests (or custom tests\n" +
+            "                                if a custom association file is specified) and writes to <fileroot>.PERMUT\n" +
+            "-pairwiseTagging                Generates pairwise tagging information in <fileroot>.TAGS and .TESTS\n" +
+            "-aggressiveTagging              As above but generates 2-marker haplotype tags unless specified otherwise by -aggressiveNumMarkers\n" +
+            //"-tagrsqcounts                   Generates conditional haplotype probabilities from tagger in <fileroot>.CHAPS\n" +
+            "-aggressiveNumMarkers <2,3>     Specifies whether to use 2-marker haplotype tags or 2 and 3-marker haplotype tags.\n" +
+            "-maxNumTags <n>                 Only selects <n> best tags.\n" +
+            "-dontaddtags                    Only uses forced in tags.\n" +
+            "-includeTags <markers>          Forces in a comma separated list of marker names as tags.\n" +
+            "-includeTagsFile <file>         Forces in a file (or http:// location) of one marker name per line as tags.\n" +
+            "-excludeTags <markers>          Excludes a comma separated list of marker names from being used as tags.\n" +
+            "-excludeTagsFile <file>         Excludes a file (or http:// location) of one marker name per line from being used as tags.\n" +
+            "-captureAlleles <file>          Capture only the alleles contained in a file (or http:// location) of one marker name per line.\n" +
+            "-designScores <file>            Specify design scores in a file (or http:// location) of one marker name and one score per line\n" +
+            "-mindesignscore <threshold>     Specify a minimum design score threshold.\n" +
+            "-mintagdistance <distance>      Specify a Minimum distance in bases between picked tags.\n" +
+            "-taglodcutoff <thresh>          Tagger LOD cutoff for creating multimarker tag haplotypes.\n" +
+            "-tagrsqcutoff <thresh>          Tagger r^2 cutoff.\n"
+            ;
+
+}
diff --git a/edu/mit/wi/haploview/DPrimeDisplay.java b/edu/mit/wi/haploview/DPrimeDisplay.java
new file mode 100755
index 0000000..7b0cf54
--- /dev/null
+++ b/edu/mit/wi/haploview/DPrimeDisplay.java
@@ -0,0 +1,1831 @@
+package edu.mit.wi.haploview;
+
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+import org.jfree.ui.RectangleEdge;
+import org.jfree.ui.RectangleInsets;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.apache.batik.dom.GenericDOMImplementation;
+import org.apache.batik.svggen.SVGGraphics2D;
+//import org.freehep.util.export.ExportDialog;
+//import org.freehep.graphics2d.VectorGraphics;
+//import org.freehep.graphicsio.ps.PSGraphics2D;
+
+import java.awt.*;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.event.*;
+import java.util.*;
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.io.*;
+import javax.swing.*;
+import javax.swing.border.CompoundBorder;
+
+
+public class DPrimeDisplay extends JComponent
+        implements MouseListener, MouseMotionListener, Constants {
+    static final long serialVersionUID = -1547877018850397541L;
+    private static final int H_BORDER = 30;
+    private static final int V_BORDER = 15;
+    private static final int TEXT_GAP = 3;
+    private static final int GBROWSE_MARGIN = 25;
+    private static final int MAX_GBROWSE_WIDTH = 30000;
+    private static final int LAST_SELECTION_LEFT = 7;
+    private static final int LAST_SELECTION_TOP  = 18;
+
+
+    private static final int BOX_SIZES[] = {50, 24, 12};
+    private static final int BOX_RADII[] = {24, 11, 6};
+    private static final int TICK_HEIGHT = 8;
+    private static final int TICK_BOTTOM = 50;
+
+    private static final int TRACK_BUMPER = 3;
+    private static final int TRACK_PALETTE = 50;
+    private static final int TRACK_HEIGHT = TRACK_PALETTE + TRACK_BUMPER*2;
+    private static final int TRACK_GAP = 5;
+
+    private int widestMarkerName = 80; //default size
+    private int blockDispHeight = 0, infoHeight = 0;
+    private int boxSize = BOX_SIZES[0];
+    private int boxRadius = BOX_RADII[0];
+    private int lowX, highX, lowY, highY;
+    private int left = H_BORDER;
+    private int top = V_BORDER;
+    private int clickXShift, clickYShift;
+    private Vector displayStrings;
+    private final int popupHorizMargin = 12;
+
+    private final Color BG_GREY = new Color(212,208,200);
+
+    private BufferedImage gBrowseImage = null;
+
+    BasicStroke thickerStroke = new BasicStroke(1);
+    BasicStroke thinnerStroke = new BasicStroke(0.35f);
+    BasicStroke fatStroke = new BasicStroke(2.5f);
+    float dash1[] = {5.0f};
+    BasicStroke dashedFatStroke = new BasicStroke(2.5f,
+            BasicStroke.CAP_BUTT,
+            BasicStroke.JOIN_MITER,
+            5.0f, dash1, 0.0f);
+    BasicStroke dashedThinStroke = new BasicStroke(0.35f,
+            BasicStroke.CAP_BUTT,
+            BasicStroke.JOIN_MITER,
+            5.0f, dash1, 0.0f);
+    private Font boxFont = new Font("SansSerif", Font.PLAIN, 12);
+    private Font markerNumFont = new Font("SansSerif", Font.BOLD, 12);
+    private Font markerNameFont = new Font("Default", Font.PLAIN, 12);
+    private Font boldMarkerNameFont = new Font("Default", Font.BOLD, 12);
+    private Font popupFont = new Font("Monospaced", Font.PLAIN, 12);
+
+    private int printWhat = D_PRIME;
+    private boolean printMarkerNames = true;
+    private boolean forExport = false;
+    private int exportStart, exportStop;
+    private boolean showWM = false;
+    private int zoomLevel = 0;
+    private boolean noImage = true;
+
+    private Rectangle wmInteriorRect = new Rectangle();
+    private Rectangle wmResizeCorner = new Rectangle(0,0,-1,-1);
+    private Rectangle resizeWMRect = null;
+    private Point popupDrawPoint = null;
+    private BufferedImage worldmap;
+    private HaploData theData;
+    private HaploView theHV;
+    private Dimension chartSize=null;
+    private int wmMaxWidth=0;
+    private Rectangle blockRect = null;
+    private int blockStartX = 0;
+    private double[] alignedPositions;
+    private String currentSelection;
+    private String lastSelection = new String("");
+
+
+    DPrimeDisplay(HaploView h){
+        //called when in gui mode
+        theData=h.theData;
+        theHV = h;
+        this.computePreferredSize(theHV.getGraphics());
+        this.colorDPrime();
+        this.setDoubleBuffered(true);
+        addMouseListener(this);
+        addMouseMotionListener(this);
+        this.setAutoscrolls(true);
+    }
+
+    DPrimeDisplay(HaploData hd){
+        //called when in cmd line mode, used to dump pngs
+        theData = hd;
+        this.computePreferredSize();
+        this.colorDPrime();
+    }
+
+    public void colorDPrime(){
+        int scheme = Options.getLDColorScheme();
+        DPrimeTable dPrime = theData.dpTable;
+        noImage = true;
+
+        if (scheme == STD_SCHEME){
+            // set coloring based on LOD and D'
+            for (int i = 0; i < Chromosome.getSize()-1; i++){
+                for (int j = i+1; j < dPrime.getLength(i)+i; j++){
+                    PairwiseLinkage thisPair = dPrime.getLDStats(i,j);
+                    if (thisPair == null){
+                        continue;
+                    }
+
+                    double d = thisPair.getDPrime();
+                    double l = thisPair.getLOD();
+                    Color boxColor;
+                    if (l > 2) {
+                        if (d < 0.5) {
+                            //high LOD, low D'
+                            boxColor = new Color(255, 224, 224);
+                        } else {
+                            //high LOD, high D' shades of red
+                            double blgr = (255-32)*2*(1-d);
+                            boxColor = new Color(255, (int) blgr, (int) blgr);
+                        }
+                    } else if (d > 0.99) {
+                        //high D', low LOD blueish color
+                        boxColor = new Color(192, 192, 240);
+                    } else {
+                        //no LD
+                        boxColor = Color.white;
+                    }
+                    thisPair.setColor(boxColor);
+                }
+            }
+        }else if (scheme == GAB_SCHEME){
+            for (int x = 0; x < Chromosome.getSize()-1; x++){
+                for (int y = x+1; y < Chromosome.getSize(); y++){
+                    PairwiseLinkage thisPair = dPrime.getLDStats(x,y);
+                    if (thisPair == null){
+                        continue;
+                    }
+                    //get the right bits
+                    double lowCI = thisPair.getConfidenceLow();
+                    double highCI = thisPair.getConfidenceHigh();
+
+                    //color in squares
+                    if (lowCI >= FindBlocks.cutLowCI && highCI >= FindBlocks.cutHighCI) {
+                        thisPair.setColor(Color.darkGray);  //strong LD
+                    }else if (highCI >= FindBlocks.recHighCI) {
+                        thisPair.setColor(Color.lightGray); //uninformative
+                    } else {
+                        thisPair.setColor(Color.white); //recomb
+                    }
+                }
+            }
+        }else if (scheme == GAM_SCHEME){
+            for (int x = 0; x < Chromosome.getSize()-1; x++){
+                for (int y = x+1; y < Chromosome.getSize(); y++){
+                    PairwiseLinkage thisPair = dPrime.getLDStats(x,y);
+                    if (thisPair == null) {
+                        continue;
+                    }
+
+                    double[] freqs = thisPair.getFreqs();
+                    int numGam = 0;
+                    for (int i = 0; i < freqs.length; i++){
+                        //add a little bump for EM probs which should be zero but are really like 10^-10
+                        if (freqs[i] > FindBlocks.fourGameteCutoff + 1E-8) numGam++;
+                    }
+
+                    //color in squares
+                    if(numGam > 3){
+                        thisPair.setColor(Color.white);
+                    }else{
+                        thisPair.setColor(Color.darkGray);
+                    }
+                }
+            }
+        }else if (scheme == WMF_SCHEME){
+            // set coloring based on LOD and D', but without (arbitrary) cutoffs to introduce
+            // "color damage" (Tufte)
+
+            // first get the maximum LOD score so we can scale relative to that.
+
+            double max_l = 0.0;
+
+            for (int i = 0; i < Chromosome.getSize(); i++){
+                for (int j = i+1; j < i + dPrime.getLength(i); j++){
+                    PairwiseLinkage thisPair = dPrime.getLDStats(i,j);
+                    if (thisPair == null){
+                        continue;
+                    }
+
+                    if (thisPair.getLOD() > max_l) max_l = thisPair.getLOD();
+                }
+            }
+
+            // cap the max LOD score
+            if (max_l > 5.0) max_l = 5.0;
+
+            for (int i = 0; i < Chromosome.getSize(); i++){
+                for (int j = i+1; j < i + dPrime.getLength(i); j++){
+                    PairwiseLinkage thisPair = dPrime.getLDStats(i,j);
+                    if (thisPair == null){
+                        continue;
+                    }
+
+                    double d = thisPair.getDPrime();
+                    double l = thisPair.getLOD();
+                    Color boxColor = null;
+
+                    double lod_scale = l / max_l;
+
+                    // if greater than the cap, call it the cap
+                    if (lod_scale > 1.0) lod_scale = 1.0;
+
+                    // there can be negative LOD scores, apparently
+                    if (lod_scale < 0.0) lod_scale = 0.0;
+
+                    // also, scale the D' so anything under .2 is white.
+                    d = (1.0 / 0.8) * (d - 0.2);
+                    if (d < 0.0) d = 0.0;
+
+                    // if there is low(er) D' but big LOD score, this should be in a gray scale
+                    // scaled to the D' value
+                    if (lod_scale > d) { lod_scale = d; }
+
+                    int r, g, b;
+
+                    // r = (int)(200.0 * d + 55.0 * lod_scale);
+                    // g = (int)(255.0 * d - 255.0 * lod_scale);
+                    // b = (int)(255.0 * d - 255.0 * lod_scale);
+
+                    double ap, cp, dp, ep, jp, kp;
+
+                    ap = 0.0;
+                    cp = -255.0;
+                    dp = -55.0;
+                    ep = -200.0;
+                    jp = 255.0;
+                    kp = 255.0;
+
+                    r =     (int)(ap * d + cp * lod_scale + jp);
+                    g = b = (int)(dp * d + ep * lod_scale + kp);
+
+                    if (r < 0) r = 0;
+                    if (g < 0) g = 0;
+                    if (b < 0) b = 0;
+
+                    boxColor = new Color(r, g, b);
+
+                    thisPair.setColor(boxColor);
+                }
+            }
+        }else if (scheme == RSQ_SCHEME){
+            // set coloring based on R-squared values
+
+            for (int i = 0; i < Chromosome.getSize(); i++){
+                for (int j = i+1; j < i + dPrime.getLength(i); j++){
+                    PairwiseLinkage thisPair = dPrime.getLDStats(i,j);
+                    if (thisPair == null){
+                        continue;
+                    }
+
+                    double rsq = thisPair.getRSquared();
+                    Color boxColor = null;
+
+                    int r, g, b;
+
+                    r = g = b = (int)(255.0 * (1.0 - rsq));
+
+                    boxColor = new Color(r, g, b);
+
+                    thisPair.setColor(boxColor);
+                }
+            }
+        }else if (scheme == GOLD_SCHEME){
+            for (int i = 0; i < Chromosome.getSize(); i++){
+                for (int j = i+1; j < i + dPrime.getLength(i); j++){
+                    PairwiseLinkage thisPair = dPrime.getLDStats(i,j);
+                    if (thisPair == null){
+                        continue;
+                    }
+                    double dprime = thisPair.getDPrime();
+                    int r,g,b;
+                    if (dprime < 0.2){
+                        r = 0;
+                        g = 0;
+                        b = 127 + (int)((dprime/0.2)*127);
+                    }else if(dprime < 0.4){
+                        r = 0;
+                        g = (int)(((dprime-0.2)/0.2)*255);
+                        b = 255;
+                    }else if (dprime < 0.6){
+                        r = 0;
+                        g = 127 + (int)(((dprime-0.4)/0.2)*127);
+                        b = 0;
+                    }else if (dprime < 0.8){
+                        r = (int)(((dprime-0.6)/0.2)*255);
+                        g = 255;
+                        b = 0;
+                    }else{
+                        r = 255;
+                        g = (int)(((1-dprime)/0.2)*255);
+                        b = 0;
+                    }
+                    thisPair.setColor(new Color(r,g,b));
+                }
+            }
+
+        }
+        repaint();
+    }
+
+    public BufferedImage export(int start, int stop, boolean compress) throws HaploViewException {
+        forExport = true;
+
+        exportStart = -1;
+        if (start < 0){
+            start = 0;
+        }
+        while (true){
+            //if the marker we want has been filtered walk up until we find a valid one
+            exportStart = Chromosome.filterIndex[start];
+            if (exportStart == -1){
+                start++;
+                if (start >= Chromosome.getUnfilteredSize()){
+                    forExport = false;
+                    throw new HaploViewException("Invalid marker range for export.");
+                }
+            }else{
+                break;
+            }
+        }
+
+        exportStop = -1;
+        if (stop > Chromosome.getUnfilteredSize()){
+            stop = Chromosome.getUnfilteredSize();
+        }
+        while (true){
+            //if the marker we want has been filtered walk down until we find a valid one
+            exportStop = Chromosome.filterIndex[stop-1];
+            if (exportStop == -1){
+                stop--;
+                if (stop < 0){
+                    forExport = false;
+                    throw new HaploViewException("Invalid marker range for export.");
+                }
+            }else{
+                break;
+            }
+        }
+
+
+        this.computePreferredSize();
+
+        int startBS = boxSize;
+        int startBR = boxRadius;
+        int startPW = printWhat;
+        boolean startMN = printMarkerNames;
+        int startZL = zoomLevel;
+
+        if (compress){
+            zoomLevel = 2;
+            printWhat = LD_NONE;
+            printMarkerNames = false;
+
+            if (boxSize > (1200/(stop - start))){
+                boxSize = 1200/(stop - start);
+
+                if (boxSize < 2){
+                    boxSize = 2;
+                }
+                //to make picture not look dumb we need to avoid odd numbers for really teeny boxes
+                if (boxSize < 10){
+                    if (boxSize%2 != 0){
+                        boxSize++;
+                    }
+                }
+                boxRadius = boxSize/2;
+            }
+            this.computePreferredSize();
+        }
+
+        Dimension pref = getPreferredSize();
+        if(pref.width > 10000 || pref.height > 10000) {
+            throw new HaploViewException("Image too large. Try saving as compressed PNG.");
+        }
+        BufferedImage i = new BufferedImage(pref.width, pref.height,
+                BufferedImage.TYPE_3BYTE_BGR);
+        paintComponent(i.getGraphics());
+
+/*        try{
+            VectorGraphics g = new PSGraphics2D(new File("foo.ps"),new Dimension(pref.width,pref.height));
+            g.startExport();
+            print(g);
+            g.endExport();
+        }catch (Exception e){
+
+        }
+        //todo: rewrite this so freehep correctly sizes object when exporting.
+        //org.freehep.util.export.ExportDialog ed = new ExportDialog();
+        //ed.showExportDialog(this,"zoo",this,"zoo");
+        */
+
+        boxSize = startBS;
+        boxRadius = startBR;
+        zoomLevel = startZL;
+        printMarkerNames = startMN;
+        printWhat = startPW;
+        forExport = false;
+        this.computePreferredSize();
+        return i;
+    }
+
+    public SVGGraphics2D exportSVG(int start, int stop) throws HaploViewException {
+       forExport = true;
+
+        exportStart = -1;
+        if (start < 0){
+            start = 0;
+        }
+        while (true){
+            //if the marker we want has been filtered walk up until we find a valid one
+            exportStart = Chromosome.filterIndex[start];
+            if (exportStart == -1){
+                start++;
+                if (start >= Chromosome.getUnfilteredSize()){
+                    forExport = false;
+                    throw new HaploViewException("Invalid marker range for export.");
+                }
+            }else{
+                break;
+            }
+        }
+
+        exportStop = -1;
+        if (stop > Chromosome.getUnfilteredSize()){
+            stop = Chromosome.getUnfilteredSize();
+        }
+        while (true){
+            //if the marker we want has been filtered walk down until we find a valid one
+            exportStop = Chromosome.filterIndex[stop-1];
+            if (exportStop == -1){
+                stop--;
+                if (stop < 0){
+                    forExport = false;
+                    throw new HaploViewException("Invalid marker range for export.");
+                }
+            }else{
+                break;
+            }
+        }
+
+
+        this.computePreferredSize();
+
+        int startBS = boxSize;
+        int startBR = boxRadius;
+        int startPW = printWhat;
+        boolean startMN = printMarkerNames;
+        int startZL = zoomLevel;
+
+        DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
+        Document document = domImpl.createDocument(null, "svg", null);
+        SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
+        svgGenerator.getGeneratorContext().setPrecision(6); //TODO: Look at changing precision
+        paintComponent(svgGenerator);
+
+        boxSize = startBS;
+        boxRadius = startBR;
+        zoomLevel = startZL;
+        printMarkerNames = startMN;
+        printWhat = startPW;
+        forExport = false;
+        this.computePreferredSize();
+
+        return svgGenerator;
+    }
+
+    public void zoom(int type){
+        int diff = type - zoomLevel;
+
+        zoomLevel = type;
+
+        if (zoomLevel == 0){
+            printMarkerNames = true;
+        } else{
+            printMarkerNames = false;
+        }
+
+        int x=0, y=0;
+        int oldX = getVisibleRect().x;
+        int oldY = getVisibleRect().y;
+        int oldWidth = getVisibleRect().width;
+        int oldHeight = getVisibleRect().height;
+
+        if (diff > 0){
+            //we're zooming out
+            x = oldX /(2*diff) - oldWidth/4*diff;
+            y = oldY /(2*diff) - oldHeight/4*diff;
+        } else if (diff < 0 ) {
+            //we're zooming in
+            diff = -diff;
+            x = oldX*2*diff + oldWidth/2*diff;
+            y = oldY*2*diff + oldHeight/2*diff;
+
+            //for cases when zoomed out view doesn't take up whole screen we don't wanna end
+            //up zooming in to some random place, but rather the upper left corner.
+            if (oldX == 0){
+                x = 0;
+            }
+            if (oldY == 0){
+                y = 0;
+            }
+        }else{
+            //we didn't change the zoom so don't waste cycles
+            return;
+        }
+
+        if (x < 0){
+            x = 0;
+        }
+        if (y < 0){
+            y = 0;
+        }
+        boxSize = BOX_SIZES[zoomLevel];
+        boxRadius = BOX_RADII[zoomLevel];
+        this.computePreferredSize();
+        //System.out.println(oldX + " " + x + " " + oldY + " " + y);
+        ((JViewport)getParent()).setViewPosition(new Point(x,y));
+    }
+
+    public void paintComponent(Graphics g){
+        DPrimeTable dPrimeTable = theData.dpTable;
+        if (Chromosome.getSize() < 2){
+            //if there zero or only one valid marker
+            return;
+        }
+        Vector blocks = theData.blocks;
+        Rectangle visRect = getVisibleRect();
+
+        //deal with zooming
+        if (chartSize.getWidth() > (3*visRect.width)){
+            showWM = true;
+        }else{
+            showWM = false;
+        }
+
+        boolean printValues = true;
+        if (zoomLevel != 0 || Options.getPrintWhat() == LD_NONE){
+            printValues = false;
+        }
+        printWhat = Options.getPrintWhat();
+
+        Graphics2D g2 = (Graphics2D) g;
+        Dimension size = getSize();
+        Dimension pref = getPreferredSize();
+        g2.setColor(BG_GREY);
+
+        //if it's a big dataset, resize properly, if it's small make sure to fill whole background
+        if (size.height < pref.height){
+            g2.fillRect(0,0,pref.width,pref.height);
+            setSize(pref);
+        }else{
+            g2.fillRect(0,0,size.width, size.height);
+        }
+        g2.setColor(Color.black);
+
+
+        //okay so this dumb if block is to prevent the ugly repainting
+        //bug when loading markers after the data are already being displayed,
+        //results in a little off-centering for small datasets, but not too bad.
+        if (!forExport){
+            if (!theData.infoKnown){
+                g2.translate((size.width - pref.width) / 2,
+                        (size.height - pref.height) / 2);
+            } else {
+                g2.translate((size.width - pref.width) / 2,
+                        0);
+            }
+        }
+
+        FontMetrics boxFontMetrics = g2.getFontMetrics(boxFont);
+
+        int diamondX[] = new int[4];
+        int diamondY[] = new int[4];
+        Polygon diamond;
+
+
+        double lineSpan = alignedPositions[alignedPositions.length-1] - alignedPositions[0];
+        long minpos = Chromosome.getMarker(0).getPosition();
+        long maxpos = Chromosome.getMarker(Chromosome.getSize()-1).getPosition();
+        double spanpos = maxpos - minpos;
+
+
+        //See http://www.hapmap.org/cgi-perl/gbrowse/gbrowse_img
+        //for more info on GBrowse img.
+        int imgHeight = 0;
+        if (Options.isGBrowseShown() && Chromosome.getDataChrom() != null && !Chromosome.getDataChrom().equalsIgnoreCase("none") && gBrowseImage != null){
+            g2.drawImage(gBrowseImage,H_BORDER-GBROWSE_MARGIN,V_BORDER,this);
+            imgHeight = gBrowseImage.getHeight(this) + TRACK_GAP; // get height so we can shift everything down
+        }
+        left = H_BORDER;
+        top = V_BORDER + imgHeight; // push the haplotype display down to make room for gbrowse image.
+
+
+        if (forExport){
+            left -= exportStart * boxSize;
+        }
+
+        FontMetrics boldMetrics;
+        int ascent;
+
+        g2.setFont(boldMarkerNameFont);
+        boldMetrics = g2.getFontMetrics();
+        ascent = boldMetrics.getAscent();
+
+        //the following values are the bounds on the boxes we want to
+        //display given that the current window is 'visRect'
+        lowX = getBoundaryMarker(visRect.x-clickXShift-(visRect.y +visRect.height-clickYShift)) - 1;
+        highX = getBoundaryMarker(visRect.x + visRect.width);
+        lowY = getBoundaryMarker((visRect.x-clickXShift)+(visRect.y-clickYShift)) - 1;
+        highY = getBoundaryMarker((visRect.x-clickXShift+visRect.width) + (visRect.y-clickYShift+visRect.height));
+        if (lowX < 0) {
+            lowX = 0;
+        }
+        if (highX > Chromosome.getSize()-1){
+            highX = Chromosome.getSize()-1;
+        }
+        if (lowY < lowX+1){
+            lowY = lowX+1;
+        }
+        if (highY > Chromosome.getSize()){
+            highY = Chromosome.getSize();
+        }
+
+        if (forExport){
+            lowX = exportStart;
+            lowY = exportStart;
+            highX = exportStop;
+            highY = exportStop+1;
+        }
+
+
+        if (theData.trackExists){
+            //draw the analysis track above where the marker positions will be marked
+
+            JFreeChart jfc = ChartFactory.createXYLineChart(null,null,null,
+                    theData.analysisTracks,
+                    PlotOrientation.VERTICAL,false,false,false);
+
+            //customise the analysis track
+            XYPlot xyp = (XYPlot)jfc.getPlot();
+
+            //no x axis, since it takes up too much space.
+            xyp.getDomainAxis().setAxisLineVisible(false);
+            xyp.getDomainAxis().setTickLabelsVisible(false);
+            xyp.getDomainAxis().setTickMarksVisible(false);
+
+            //x range must align with markers
+            xyp.getDomainAxis().setRange(minpos,maxpos);
+
+            //size of the axis and graph inset
+            double axisWidth = xyp.getRangeAxis().
+                    reserveSpace(g2,xyp,new Rectangle(0,TRACK_HEIGHT),RectangleEdge.LEFT,null).getLeft();
+            RectangleInsets insets = xyp.getInsets();
+
+            jfc.setBackgroundPaint(BG_GREY);
+            BufferedImage bi = jfc.createBufferedImage(
+                    (int)(lineSpan + axisWidth + insets.getLeft() + insets.getRight()),TRACK_HEIGHT);
+            //hide the axis in the margin so everything lines up.
+            g2.drawImage(bi,(int)(left - axisWidth - insets.getLeft()),top,this);
+            top += TRACK_HEIGHT + TRACK_GAP;
+        }
+
+        if (theData.infoKnown) {
+	    Color green = new Color(0, 170, 0);
+
+            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                    RenderingHints.VALUE_ANTIALIAS_ON);
+
+            //// draw the marker locations
+
+            g2.setStroke(thinnerStroke);
+            g2.setColor(Color.white);
+            g2.fill(new Rectangle2D.Double(left+1, top+1, lineSpan-1, TICK_HEIGHT-1));
+            g2.setColor(Color.black);
+            g2.draw(new Rectangle2D.Double(left, top, lineSpan, TICK_HEIGHT));
+
+            for (int i = 0; i < Chromosome.getSize(); i++){
+                double pos = (Chromosome.getMarker(i).getPosition() - minpos) / spanpos;
+
+                double xx = left + lineSpan*pos;
+
+                // if we're zoomed, use the line color to indicate whether there is extra data available
+                // (since the marker names are not displayed when zoomed)
+
+                if (Chromosome.getMarker(i).getExtra() != null) g2.setColor(green);
+
+                //draw tick
+                g2.setStroke(thickerStroke);
+                g2.draw(new Line2D.Double(xx, top, xx, top + TICK_HEIGHT));
+
+                if (Chromosome.getMarker(i).getExtra() != null) g2.setStroke(thickerStroke);
+                else g2.setStroke(thinnerStroke);
+                //draw connecting line
+                g2.draw(new Line2D.Double(xx, top + TICK_HEIGHT,
+                        left + alignedPositions[i], top+TICK_BOTTOM));
+
+                if (theHV != null){
+                    if (Chromosome.getMarker(i).getDisplayName().equals(theHV.getChosenMarker())){
+                        float cornerx = (float)xx-10;
+                        float cornery = top;
+                        float xpoints[] = {cornerx,cornerx+20,cornerx+10};
+                        float ypoints[] = {cornery,cornery,cornery-10};
+                        GeneralPath triangle = new GeneralPath(GeneralPath.WIND_NON_ZERO,xpoints.length);
+                        triangle.moveTo(xpoints[0],ypoints[0]);
+                        for (int index = 1; index < xpoints.length; index++){
+                            triangle.lineTo(xpoints[index],ypoints[index]);
+                        }
+                        triangle.closePath();
+                        g2.fill(triangle);
+                    }
+                }
+
+                g2.setColor(Color.black);
+            }
+
+            top += TICK_BOTTOM + TICK_HEIGHT;
+
+            //// draw the marker names
+            if (printMarkerNames){
+                g2.setFont(boldMarkerNameFont);
+                FontMetrics markerMetrics = g2.getFontMetrics();
+                widestMarkerName = markerMetrics.stringWidth(Chromosome.getMarker(0).getDisplayName());
+                for (int x = 1; x < Chromosome.getSize(); x++) {
+                    int thiswide = markerMetrics.stringWidth(Chromosome.getMarker(x).getDisplayName());
+                    if (thiswide > widestMarkerName) widestMarkerName = thiswide;
+                }
+
+                g2.translate(left, top + widestMarkerName);
+                g2.rotate(-Math.PI / 2.0);
+                for (int x = 0; x < Chromosome.getSize(); x++) {
+                    if (theData.isInBlock[x]){
+                        g2.setFont(boldMarkerNameFont);
+                        markerMetrics = g2.getFontMetrics();
+                    }else{
+                        g2.setFont(markerNameFont);
+                        markerMetrics = g2.getFontMetrics();
+                    }
+                    if (theHV != null){
+                        if (Chromosome.getMarker(x).getDisplayName().equals(theHV.getChosenMarker())){
+                            g2.setColor(Color.white);
+                            g2.fillRect(TEXT_GAP,(int)alignedPositions[x] - ascent/2,
+                                    markerMetrics.stringWidth(Chromosome.getMarker(x).getDisplayName()),ascent);
+                            g2.setColor(green);
+                            g2.drawRect(TEXT_GAP-1,(int)alignedPositions[x] - ascent/2 - 1,
+                                    markerMetrics.stringWidth(Chromosome.getMarker(x).getDisplayName())+1,ascent+1);
+                        }
+                    }
+                    if (Chromosome.getMarker(x).getExtra() != null) g2.setColor(green);
+
+
+                    g2.drawString(Chromosome.getMarker(x).getDisplayName(),(float)TEXT_GAP, (float)alignedPositions[x] + ascent/3);
+                    g2.setColor(Color.black);
+                }
+
+                g2.rotate(Math.PI / 2.0);
+                g2.translate(-left, -(top + widestMarkerName));
+
+                // move everybody down
+                top += widestMarkerName + TEXT_GAP;
+            }
+
+            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                    RenderingHints.VALUE_ANTIALIAS_OFF);
+        }
+
+        top += blockDispHeight;
+
+        //// draw the marker numbers
+        if (printMarkerNames){
+            g2.setFont(markerNumFont);
+            FontMetrics numMetrics = g2.getFontMetrics();
+            ascent = numMetrics.getAscent();
+
+            for (int x = 0; x < Chromosome.getSize(); x++) {
+                String mark = String.valueOf(Chromosome.realIndex[x] + 1);
+                g2.drawString(mark,
+                        (float)(left + alignedPositions[x] - numMetrics.stringWidth(mark)/2),
+                        (float)(top + ascent));
+            }
+
+            top += boxRadius/2; // give a little space between numbers and boxes
+        }
+
+        //clickxshift and clickyshift are used later to translate from x,y coords
+        //to the pair of markers comparison at those coords
+        if (!(theData.infoKnown)){
+            clickXShift = left + (size.width-pref.width)/2;
+            clickYShift = top + (size.height - pref.height)/2;
+        } else {
+            clickXShift = left + (size.width-pref.width)/2;
+            clickYShift = top;
+        }
+
+
+        // draw table column by column
+        for (int x = lowX; x < highX; x++) {
+
+            //always draw the fewest possible boxes
+            if (lowY < x+1){
+                lowY = x+1;
+            }
+
+            for (int y = lowY; y < highY; y++) {
+                if (dPrimeTable.getLDStats(x,y) == null){
+                    continue;
+                }
+                double d = dPrimeTable.getLDStats(x,y).getDPrime();
+                double r = dPrimeTable.getLDStats(x,y).getRSquared();
+                //double l = dPrimeTable.getLDStats(x,y).getLOD();
+                Color boxColor = dPrimeTable.getLDStats(x,y).getColor();
+
+                // draw markers above
+                int xx = left + (int)((alignedPositions[x] + alignedPositions[y])/2);
+                int yy = top + (int)((alignedPositions[y] - alignedPositions[x]) / 2);
+
+                diamondX[0] = xx; diamondY[0] = yy - boxRadius;
+                diamondX[1] = xx + boxRadius; diamondY[1] = yy;
+                diamondX[2] = xx; diamondY[2] = yy + boxRadius;
+                diamondX[3] = xx - boxRadius; diamondY[3] = yy;
+
+                diamond = new Polygon(diamondX, diamondY, 4);
+                g2.setColor(boxColor);
+                g2.fillPolygon(diamond);
+
+                if(printValues){
+                    g2.setFont(boxFont);
+                    ascent = boxFontMetrics.getAscent();
+                    int val;
+                    if (printWhat == D_PRIME){
+                        val = (int) (d * 100);
+                    }else if (printWhat == R_SQ){
+                        val = (int) (r * 100);
+                    }else{
+                        val = 100;
+                    }
+                    if (boxColor.getGreen() < 175 && boxColor.getBlue() < 175 && boxColor.getRed() < 175){
+                        g2.setColor(Color.white);
+                    }else{
+                        g2.setColor((val < 50) ? Color.gray : Color.black);
+                    }
+                    if (val != 100) {
+                        String valu = String.valueOf(val);
+                        int widf = boxFontMetrics.stringWidth(valu);
+                        g.drawString(valu, xx - widf/2, yy + ascent/2);
+                    }
+                }
+            }
+        }
+
+        //highlight blocks
+        g2.setFont(markerNameFont);
+        ascent = g2.getFontMetrics().getAscent();
+        //g.setColor(new Color(153,255,153));
+        g2.setColor(Color.black);
+        //g.setColor(new Color(51,153,51));
+        for (int i = 0; i < blocks.size(); i++){
+            int[] theBlock = (int[])blocks.elementAt(i);
+            int first = theBlock[0];
+            int last = theBlock[theBlock.length-1];
+
+            //big vee around whole thing
+            g2.setStroke(fatStroke);
+            g2.draw(new Line2D.Double(left + alignedPositions[first] - boxRadius,
+                    top,
+                    left + (alignedPositions[first] + alignedPositions[last])/2,
+                    top + (alignedPositions[last] - alignedPositions[first])/2 + boxRadius));
+            g2.draw(new Line2D.Double(left + (alignedPositions[first] + alignedPositions[last])/2,
+                    top + (alignedPositions[last] - alignedPositions[first])/2 + boxRadius,
+                    left + alignedPositions[last] + boxRadius,
+                    top));
+
+            for (int j = first; j < last; j++){
+                g2.setStroke(fatStroke);
+                if (theData.isInBlock[j]){
+                    g2.draw(new Line2D.Double(left+alignedPositions[j]-boxSize/2,
+                        top-blockDispHeight,
+                        left+alignedPositions[j+1]-boxSize/2,
+                        top-blockDispHeight));
+                }else{
+                    g2.draw(new Line2D.Double(left + alignedPositions[j] + boxSize/2,
+                        top-blockDispHeight,
+                        left+alignedPositions[j+1]-boxSize/2,
+                        top-blockDispHeight));
+                    g2.setStroke(dashedFatStroke);
+                    g2.draw(new Line2D.Double(left+alignedPositions[j] - boxSize/2,
+                        top-blockDispHeight,
+                        left+alignedPositions[j] + boxSize/2,
+                        top-blockDispHeight));
+                }
+            }
+            //cap off the end of the block
+            g2.setStroke(fatStroke);
+            g2.draw(new Line2D.Double(left+alignedPositions[last]-boxSize/2,
+                        top-blockDispHeight,
+                        left+alignedPositions[last]+boxSize/2,
+                        top-blockDispHeight));
+
+
+            //lines to connect to block display
+            g2.setStroke(fatStroke);
+            g2.draw(new Line2D.Double(left + alignedPositions[first]-boxSize/2,
+                    top-1,
+                    left+alignedPositions[first]-boxSize/2,
+                    top-blockDispHeight));
+            g2.draw(new Line2D.Double(left+alignedPositions[last]+boxSize/2,
+                    top-1,
+                    left+alignedPositions[last]+boxSize/2,
+                    top-blockDispHeight));
+            if (printMarkerNames){
+                String labelString = new String ("Block " + (i+1));
+                if (theData.infoKnown){
+                    long blockSize = Chromosome.getMarker(last).getPosition() -
+                            Chromosome.getMarker(first).getPosition();
+                    labelString += " (" + blockSize/1000 + " kb)";
+                }
+                g2.drawString(labelString,
+                        (float)(left+alignedPositions[first]-boxSize/2+TEXT_GAP),
+                        (float)(top-boxSize/3));
+            }
+        }
+        g2.setStroke(thickerStroke);
+
+        if (showWM && !forExport){
+            //dataset is big enough to require worldmap
+            if (wmMaxWidth == 0){
+                wmMaxWidth = visRect.width/3;
+            }
+            double scalefactor;
+            scalefactor = (double)(chartSize.width)/wmMaxWidth;
+            double prefBoxSize = boxSize/(scalefactor*((double)wmMaxWidth/(double)(wmMaxWidth)));
+
+            //stick WM_BD in the middle of the blank space at the top of the worldmap
+            final int WM_BD_GAP = (int)(infoHeight/(scalefactor*2));
+            final int WM_BD_HEIGHT = 2;
+            CompoundBorder wmBorder = new CompoundBorder(BorderFactory.createRaisedBevelBorder(),
+                    BorderFactory.createLoweredBevelBorder());
+
+            if (noImage){
+                //first time through draw a worldmap if dataset is big:
+                worldmap = new BufferedImage((int)(chartSize.width/scalefactor)+wmBorder.getBorderInsets(this).left*2,
+                        (int)(chartSize.height/scalefactor)+wmBorder.getBorderInsets(this).top*2,
+                        BufferedImage.TYPE_3BYTE_BGR);
+
+                Graphics gw = worldmap.getGraphics();
+                Graphics2D gw2 = (Graphics2D)(gw);
+                gw2.setColor(BG_GREY);
+                gw2.fillRect(1,1,worldmap.getWidth()-1,worldmap.getHeight()-1);
+                //make a pretty border
+                gw2.setColor(Color.black);
+
+                wmBorder.paintBorder(this,gw2,0,0,worldmap.getWidth(),worldmap.getHeight());
+                wmInteriorRect = wmBorder.getInteriorRectangle(this,0,0,worldmap.getWidth(), worldmap.getHeight());
+
+                float[] smallDiamondX = new float[4];
+                float[] smallDiamondY = new float[4];
+                GeneralPath gp;
+                for (int x = 0; x < Chromosome.getSize()-1; x++){
+                    for (int y = x+1; y < Chromosome.getSize(); y++){
+                        if (dPrimeTable.getLDStats(x,y) == null){
+                            continue;
+                        }
+                        double xx = ((alignedPositions[y] + alignedPositions[x])/(scalefactor*2)) +
+                                wmBorder.getBorderInsets(this).left;
+                        double yy = ((alignedPositions[y] - alignedPositions[x] + infoHeight*2)/(scalefactor*2)) +
+                                wmBorder.getBorderInsets(this).top;
+
+                        smallDiamondX[0] = (float)xx; smallDiamondY[0] = (float)(yy - prefBoxSize/2);
+                        smallDiamondX[1] = (float)(xx + prefBoxSize/2); smallDiamondY[1] = (float)yy;
+                        smallDiamondX[2] = (float)xx; smallDiamondY[2] = (float)(yy + prefBoxSize/2);
+                        smallDiamondX[3] = (float)(xx - prefBoxSize/2); smallDiamondY[3] = (float)yy;
+
+                        gp =  new GeneralPath(GeneralPath.WIND_EVEN_ODD,  smallDiamondX.length);
+                        gp.moveTo(smallDiamondX[0],smallDiamondY[0]);
+                        for (int i = 1; i < smallDiamondX.length; i++){
+                            gp.lineTo(smallDiamondX[i], smallDiamondY[i]);
+                        }
+                        gp.closePath();
+
+                        gw2.setColor(dPrimeTable.getLDStats(x,y).getColor());
+                        gw2.fill(gp);
+
+                    }
+                }
+                noImage = false;
+            }
+
+            //draw block display in worldmap
+            Graphics gw = worldmap.getGraphics();
+            Graphics2D gw2 = (Graphics2D)(gw);
+            gw2.setColor(BG_GREY);
+            gw2.fillRect(wmBorder.getBorderInsets(this).left,
+                    wmBorder.getBorderInsets(this).top+WM_BD_GAP,
+                    wmInteriorRect.width,
+                    WM_BD_HEIGHT);
+            gw2.setColor(Color.black);
+            boolean even = true;
+            for (int i = 0; i < blocks.size(); i++){
+                int first = ((int[])blocks.elementAt(i))[0];
+                int last = ((int[])blocks.elementAt(i))[((int[])blocks.elementAt(i)).length-1];
+                int voffset;
+                if (even){
+                    voffset = 0;
+                }else{
+                    voffset = WM_BD_HEIGHT/2;
+                }
+                gw2.fillRect(wmBorder.getBorderInsets(this).left - (int)prefBoxSize/2 + (int)(alignedPositions[first]/scalefactor),
+                        wmBorder.getBorderInsets(this).top+voffset+WM_BD_GAP,
+                        (int)(prefBoxSize + (alignedPositions[last] - alignedPositions[first])/scalefactor),
+                        WM_BD_HEIGHT/2);
+                even = !even;
+            }
+
+            wmResizeCorner = new Rectangle(visRect.x + worldmap.getWidth() - (worldmap.getWidth()-wmInteriorRect.width)/2,
+                    visRect.y + visRect.height - worldmap.getHeight(),
+                    (worldmap.getWidth()-wmInteriorRect.width)/2,
+                    (worldmap.getHeight() -wmInteriorRect.height)/2);
+
+            g2.drawImage(worldmap,visRect.x,
+                    visRect.y + visRect.height - worldmap.getHeight(),
+                    this);
+            wmInteriorRect.x = visRect.x + (worldmap.getWidth() - wmInteriorRect.width)/2;
+            wmInteriorRect.y = visRect.y+visRect.height-worldmap.getHeight() +
+                    (worldmap.getHeight() - wmInteriorRect.height)/2;
+
+            //draw the outline of the viewport
+            g2.setColor(Color.black);
+            double hRatio = wmInteriorRect.getWidth()/pref.getWidth();
+            double vRatio = wmInteriorRect.getHeight()/pref.getHeight();
+            int hBump = worldmap.getWidth()-wmInteriorRect.width;
+            int vBump = worldmap.getHeight()-wmInteriorRect.height;
+            //bump a few pixels to avoid drawing on the border
+            g2.drawRect((int)(visRect.x*hRatio)+hBump/2+visRect.x,
+                    (int)(visRect.y*vRatio)+vBump/2+(visRect.y + visRect.height - worldmap.getHeight()),
+                    (int)(visRect.width*hRatio),
+                    (int)(visRect.height*vRatio));
+        }
+
+
+        //see if the user has right-clicked to popup some marker info
+        if(popupDrawPoint != null){
+
+            //dumb bug where little datasets popup the box in the wrong place
+            int smallDatasetSlopH = 0;
+
+            if (pref.getWidth() < visRect.width){
+                smallDatasetSlopH = (int)(visRect.width - pref.getWidth())/2;
+            }
+
+            g2.setFont(popupFont);
+            FontMetrics popupMetrics = g2.getFontMetrics();
+
+            int maxStrWidth = 0;
+            for (int x = 0; x < displayStrings.size(); x++){
+                if (maxStrWidth < popupMetrics.stringWidth((String)displayStrings.elementAt(x))){
+                    maxStrWidth = popupMetrics.stringWidth((String)displayStrings.elementAt(x));
+                }
+            }
+            //edge shifts prevent window from popping up partially offscreen
+            int visRightBound = (int)(getVisibleRect().getWidth() + getVisibleRect().getX());
+            int visBotBound = (int)(getVisibleRect().getHeight() + getVisibleRect().getY());
+            int rightEdgeShift = 0;
+            if (popupDrawPoint.x + maxStrWidth + popupHorizMargin*2 > visRightBound){
+                rightEdgeShift = popupDrawPoint.x + maxStrWidth + popupHorizMargin*2 - visRightBound;
+            }
+            popupDrawPoint.x = popupDrawPoint.x - rightEdgeShift;
+
+
+            int botEdgeShift = 0;
+            if (popupDrawPoint.y + displayStrings.size()*popupMetrics.getHeight()+10 > visBotBound){
+                botEdgeShift = popupDrawPoint.y + displayStrings.size()*popupMetrics.getHeight()+15 - visBotBound;
+            }
+
+            popupDrawPoint.y = popupDrawPoint.y-botEdgeShift;
+
+            g2.setColor(Color.white);
+            g2.fillRect(popupDrawPoint.x+1-smallDatasetSlopH,
+                    popupDrawPoint.y+1,
+                    maxStrWidth + 2*popupHorizMargin,
+                    displayStrings.size()*popupMetrics.getHeight()+popupMetrics.getDescent());
+            g2.setColor(Color.black);
+            g2.drawRect(popupDrawPoint.x-smallDatasetSlopH,
+                    popupDrawPoint.y,
+                    maxStrWidth + 2*popupHorizMargin,
+                    displayStrings.size()*popupMetrics.getHeight()+popupMetrics.getDescent());
+
+
+            for (int x = 0; x < displayStrings.size(); x++){
+                g.drawString((String)displayStrings.elementAt(x),popupDrawPoint.x + popupHorizMargin-smallDatasetSlopH,
+                        popupDrawPoint.y+((x+1)*popupMetrics.getHeight()));
+            }
+        }
+
+
+        // draw the cached last right-click selection
+        // The purpose of testing for empty string is just to avoid an 2-unit empty white box
+        if (lastSelection != null){
+            if ((zoomLevel == 0) && (!lastSelection.equals("")) && (!forExport))
+            {
+                g2.setFont(boxFont);
+                // a bit extra on all side
+                int last_descent = g2.getFontMetrics().getDescent();
+                int last_box_x = (visRect.x + LAST_SELECTION_LEFT) - 2;
+                int last_box_y = (visRect.y - g2.getFontMetrics().getHeight() + LAST_SELECTION_TOP + last_descent) - 1 ;
+                int last_box_width = g2.getFontMetrics().stringWidth(lastSelection) + 4;
+                int last_box_height = g2.getFontMetrics().getHeight() + 2;
+                g2.setColor(Color.white);
+                g2.fillRect(last_box_x, last_box_y, last_box_width, last_box_height);
+                g2.setColor(Color.black);
+                g2.drawRect(last_box_x, last_box_y, last_box_width, last_box_height);
+                g2.drawString(lastSelection, LAST_SELECTION_LEFT + visRect.x, LAST_SELECTION_TOP + visRect.y);
+            }
+        }
+
+
+        //see if we're drawing a worldmap resize rect
+        if (resizeWMRect != null){
+            g2.setColor(Color.black);
+            g2.drawRect(resizeWMRect.x,
+                    resizeWMRect.y,
+                    resizeWMRect.width,
+                    resizeWMRect.height);
+        }
+
+        //see if we're drawing a block selector rect
+        if (blockRect != null){
+            g2.setColor(Color.black);
+            g2.setStroke(dashedThinStroke);
+            g2.drawRect(blockRect.x, blockRect.y,
+                    blockRect.width, blockRect.height);
+        }
+    }
+
+    public double[] doMarkerLayout(double[] snpPositions, double goalSpan){
+        //create an array for the projected positions, initialized to starting positions
+        double spp[] = new double[snpPositions.length];
+        System.arraycopy(snpPositions, 0, spp, 0, spp.length);
+
+        /*
+        Create some simple structures to keep track of which snps are bumping into each other (and whose
+        positions are dependent on each other)
+        */
+        BitSet[] conflicts = new BitSet[snpPositions.length];
+        for (int i=0; i<conflicts.length; ++i) {
+            conflicts[i] = new BitSet();
+            conflicts[i].set(i);
+        }
+
+        while (true) {
+            boolean trouble = false;
+            for (int i=0; i<spp.length-1; ++i) {
+
+                //if two SNPs are overlapping (i.e. centers are < boxSize apart)
+                if (spp[i+1]-spp[i]<boxSize-.0001) {
+                    trouble = true;
+
+                    //update the bump structures .. these two snps now bump (and have positions that are
+                    //dependent on each other) .. indicate that in the bump structure
+                    int ip = i+1;
+                    conflicts[i].set(ip);
+                    conflicts[ip].set(i);
+
+                    //Come up with the full set all snps that are involved in a bump/dependency with either
+                    //of these two snps
+                    BitSet full = new BitSet();
+                    for (int j=0; j<conflicts[i].size(); ++j) {
+                        if (conflicts[i].get(j)) full.set(j);
+                    }
+                    for (int j=0; j<conflicts[ip].size(); ++j) {
+                        if (conflicts[ip].get(j)) full.set(j);
+                    }
+
+                    /*
+                    decide on the bounds of this full set of snps for which a bump problem exists
+                    each snp inherits this full set of snps for its bump/dependency structure
+                    */
+                    int li = -1;
+                    int hi = -1;
+                    int conflict_count=0;
+                    for (int j=0; j<full.size(); ++j) {
+                        if (full.get(j)) {
+                            conflicts[j] = (BitSet)full.clone();
+                            if (li==-1) {li=j;}
+                            hi=j;
+                            conflict_count++;
+                        }
+                    }
+
+                    //reposition the projected positions of the bumping snps, centered over
+                    //the non-projected snp range of that set of snps .. with boundary conditions
+                    double total_space_to_be_spanned = boxSize*(conflict_count-1);
+                    double low_point = snpPositions[li];
+                    double high_point = snpPositions[hi];
+                    double first_snp_proj_pos = low_point - (total_space_to_be_spanned-(high_point-low_point))/2;
+                    if (first_snp_proj_pos<0.0) first_snp_proj_pos=0.0;
+                    if (first_snp_proj_pos+total_space_to_be_spanned>goalSpan) {first_snp_proj_pos = goalSpan-total_space_to_be_spanned;}
+                    for (int j=li; j<=hi; ++j) {
+                        spp[j] = first_snp_proj_pos + boxSize*(j-li);
+                    }
+                    break;
+                }
+            }
+            if (!trouble) break;
+        }
+        return spp;
+    }
+
+    public void computePreferredSize(){
+        this.computePreferredSize(this.getGraphics());
+    }
+
+    public void computePreferredSize(Graphics g) {
+        if (Chromosome.getSize() == 0){
+            //no valid markers so return an empty size
+            setPreferredSize(new Dimension(0,0));
+            return;
+        }
+
+        //setup marker positions
+        //force it to run through the aligner once by setting this val as negative
+        double aligned = -1;
+        long minpos = Chromosome.getMarker(0).getPosition();
+        long maxpos = Chromosome.getMarker(Chromosome.getSize()-1).getPosition();
+        double spanpos = maxpos - minpos;
+        double[] initialPositions = new double[Chromosome.getSize()];
+        alignedPositions = new double[Chromosome.getSize()];
+        double lineSpan = (Chromosome.getSize()-1) * boxSize;
+
+        //keep trying until we've got at least a certain fraction of the markers aligned
+        while (aligned < Options.getSpacingThreshold()){
+            double numAligned = 0;
+            for (int i = 0; i < initialPositions.length; i++){
+                initialPositions[i] = (lineSpan*((Chromosome.getMarker(i).getPosition()-minpos)/spanpos));
+            }
+            alignedPositions = doMarkerLayout(initialPositions, lineSpan);
+            for (int i = 0; i < initialPositions.length; i++){
+                //if the alignedPos is less than two pixels from the intitialpos we
+                //decide that's "close enough" to being aligned
+                if (initialPositions[i] == alignedPositions[i])
+                    numAligned++;
+            }
+            aligned = numAligned/initialPositions.length;
+            //if we haven't finished yet we want to try again with a longer line...
+            lineSpan += 0.05 * lineSpan;
+        }
+        double gblineSpan = alignedPositions[alignedPositions.length-1] - alignedPositions[0];
+
+        //generate gbrowse image if appropriate
+        int gbImageHeight = 0;
+        if (Options.isGBrowseShown() && Chromosome.getDataChrom() != null && !Chromosome.getDataChrom().equalsIgnoreCase("none")){
+            try{
+                long gbleft, gbright;
+                if (Options.getgBrowseLeft() != 0 || Options.getgBrowseRight() != 0){
+                    gbleft = Options.getgBrowseLeft();
+                    gbright = Options.getgBrowseRight()+1;
+                }else{
+                    gbleft = minpos;
+                    gbright = maxpos+1;
+                }
+                String dataBuild;
+                if (!Chromosome.getDataBuild().equalsIgnoreCase("none")){
+                    dataBuild = "_" + Chromosome.getDataBuild().substring(5).toUpperCase();
+                }else{
+                    dataBuild = "_B35";
+                }
+
+                String gChrom;
+                if (Chromosome.getDataChrom().equalsIgnoreCase("chrp")){ //account for pseudoautosomal
+                    gChrom = "chrx";
+                }else{
+                    gChrom = Chromosome.getDataChrom();
+                }
+
+                //A NOTE ON VERY WIDE IMAGES:
+                //hapmap.org freaks out if image request is > 32717 pixels wide, because it adds 50
+                //for a margin, and evidently can't draw something wider than the max value of a short
+                //so if we really want something that big, we break the request into several
+                //adjacent chunks below. If the image size is < 30K then the code below
+                //should reduce to doing what it used to, i.e. get just one image
+                //interestingly, java has a similar problem with images of type INT_RGB, despite
+                //the "INT" in the name, so we use type INT_ARGB which doesn't have this issue.
+                // --jcb
+
+                Vector imageVector = new Vector();
+                int numChunks = (int)(Math.ceil(gblineSpan/MAX_GBROWSE_WIDTH));
+                long chunkSize = (long)((MAX_GBROWSE_WIDTH/gblineSpan) * (gbright - gbleft));
+                long[] chunkLefts = new long[numChunks];
+                long[] chunkRights = new long[numChunks];
+                double[] chunkSpans = new double[numChunks];
+                for (int i = 0; i < numChunks; i++){
+                    chunkLefts[i] = gbleft + i*chunkSize;
+                    chunkRights[i] = chunkLefts[i] + chunkSize;
+                    chunkSpans[i] = MAX_GBROWSE_WIDTH;
+                }
+                //remainder, or cases when width is less than max allowed
+                if (numChunks > 0){
+                    chunkRights[numChunks-1] = gbright;
+                    chunkSpans[numChunks-1] = gblineSpan - MAX_GBROWSE_WIDTH*(numChunks-1);
+                }
+
+                int totalHeight = 0;
+                int totalWidth = 0;
+                for (int i = 0; i < numChunks; i++){
+                    URL imageUrl = new URL("http://www.hapmap.org/cgi-perl/gbrowse/gbrowse_img/hapmap" + dataBuild + "/?name=" +
+                            gChrom + ":" + chunkLefts[i] + ".." + chunkRights[i] + ";width=" + chunkSpans[i] +
+                            ";type="+ Options.getgBrowseTypes() + ";options=" + Options.getgBrowseOpts() + ";nocache=1");
+                    Toolkit toolkit = Toolkit.getDefaultToolkit();
+                    HttpURLConnection con = (HttpURLConnection)imageUrl.openConnection();
+                    con.setRequestProperty("User-agent",Constants.USER_AGENT);
+                    con.connect();
+
+                    int response = con.getResponseCode();
+
+                    if ((response != HttpURLConnection.HTTP_ACCEPTED) && (response != HttpURLConnection.HTTP_OK)) {
+                        //if something went wrong
+                        throw new IOException("Could not connect to HapMap server.");
+                    }
+
+                    InputStream inputStream = con.getInputStream();
+                    BufferedInputStream bis = new BufferedInputStream(inputStream);
+                    byte[] buf = new byte[2048];
+                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                    BufferedOutputStream bos = new BufferedOutputStream(baos);
+                    int bytesRead;
+                    while ((bytesRead = bis.read(buf,0,buf.length)) != -1){
+                        bos.write(buf,0,bytesRead);
+                    }
+                    bos.flush();
+                    bos.close();
+                    bis.close();
+
+                    Image img = toolkit.createImage(baos.toByteArray());
+                    MediaTracker mt = new MediaTracker(this);
+                    mt.addImage(img,0);
+                    setCursor(new Cursor(Cursor.WAIT_CURSOR));
+                    mt.waitForID(0);
+                    setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+                    imageVector.add(img);
+
+                    if (img.getHeight(this) > totalHeight){
+                        totalHeight = img.getHeight(this);
+                    }
+                    totalWidth += img.getWidth(this);
+                }
+
+                if (totalWidth > 0 && totalHeight > 0){
+                    gBrowseImage = new BufferedImage(totalWidth,totalHeight,BufferedImage.TYPE_INT_ARGB);
+                    gBrowseImage.getGraphics().setColor(Color.white);
+                    gBrowseImage.getGraphics().fillRect(0,0,gBrowseImage.getWidth(),gBrowseImage.getHeight());
+                    int bumper = 0;
+                    for (int j = 0; j < imageVector.size(); j++){
+                        Image img = (Image)imageVector.get(j);
+                        gBrowseImage.getGraphics().drawImage(img,bumper,0, this);
+                        bumper += chunkSpans[j];
+                    }
+                    gbImageHeight = gBrowseImage.getHeight(this) + TRACK_GAP; // get height so we can shift everything down
+                }else{
+                    //couldn't get the image for whatever reason.
+                    JOptionPane.showMessageDialog(theHV,
+                            "An error occured while accessing the HapMap website.\n",
+                            "HapMap Info Track",
+                            JOptionPane.ERROR_MESSAGE);
+                    gBrowseImage = null;
+                    Options.setShowGBrowse(false);
+                }
+            }catch (IOException e){
+                //couldn't get the image for whatever reason.
+                JOptionPane.showMessageDialog(theHV,
+                        "An error occurred while accessing the HapMap website.\n"+e.getMessage(),
+                        "HapMap Info Track",
+                        JOptionPane.ERROR_MESSAGE);
+                Options.setShowGBrowse(false);
+            }catch (InterruptedException e){
+                //just in case something awful happens
+                gBrowseImage = null;
+                Options.setShowGBrowse(false);
+            }
+        }
+
+
+
+        //loop through table to find deepest non-null comparison
+        DPrimeTable dPrimeTable = theData.dpTable;
+        int upLim, loLim;
+        if (forExport){
+            loLim = exportStart;
+            upLim = exportStop;
+        }else{
+            loLim = 0;
+            upLim = Chromosome.getSize()-1;
+        }
+        double sep = 0;
+        for (int x = loLim; x < upLim; x++){
+            for (int y = x+1; y <= upLim; y++){
+                if (dPrimeTable.getLDStats(x,y) != null){
+                    if (sep < alignedPositions[y]-alignedPositions[x]){
+                        sep = alignedPositions[y]-alignedPositions[x];
+                    }
+                }
+            }
+        }
+        //add one so we don't clip bottom box
+        sep += boxSize;
+
+
+        if (g != null){
+            g.setFont(markerNameFont);
+            FontMetrics fm = g.getFontMetrics();
+            if (printMarkerNames){
+                blockDispHeight = boxSize/3 + fm.getHeight();
+            }else{
+                blockDispHeight = boxSize/3;
+            }
+        }
+
+        //"high" represents the total height of the panel. "infoheight" is the total height of the
+        //header info above the LD plot. When we draw the worldmap we want to nudge the LD plot down
+        //by a scaled factor of infoheight so that clicking lines it up properly.
+        int high = (int)(sep/2) + V_BORDER*2 + blockDispHeight;
+        if (theData.infoKnown){
+            infoHeight = TICK_HEIGHT + TICK_BOTTOM + widestMarkerName + TEXT_GAP + gbImageHeight;
+        }else{
+            infoHeight = 0;
+        }
+        if (theData.trackExists){
+            //make room for analysis track at top
+            infoHeight += TRACK_HEIGHT + TRACK_GAP;
+        }
+        high += infoHeight;
+
+        int wide = 2*H_BORDER + (int)(alignedPositions[upLim] - alignedPositions[loLim]);
+        //this dimension is just the area taken up by the dprime chart
+        //it is used in drawing the worldmap
+        //for other elements add their heights in the next code hunk!
+        chartSize = new Dimension(wide, high);
+
+
+        Rectangle visRect = getVisibleRect();
+        //big datasets often scroll way offscreen in zoom-out mode
+        //but aren't the full height of the viewport
+        if (high < visRect.height && showWM && !forExport){
+            high = visRect.height;
+        }
+        if (!getPreferredSize().equals(new Dimension(wide,high))){
+            noImage=true;
+
+        }
+        setPreferredSize(new Dimension(wide, high));
+        if (getParent() != null){
+            JViewport par = (JViewport)getParent();
+            //OK, if the resizing involves a dataset which is larger than the visible Rect we need to prod the
+            //Viewport into resizing itself. if the resizing is all within the visrect, we don't want to do this
+            //because it makes the screen flicker with a double-repaint.
+            if (par.getVisibleRect().width < par.getViewSize().width ||
+                    par.getVisibleRect().height < par.getViewSize().height){
+                par.setViewSize(getPreferredSize());
+            }
+        }
+    }
+
+    public int getBoundaryMarker(double pos){
+        //if pos is in the array the binarysearch returns the positive index
+        //otherwise it returns the negative "insertion index" - 1
+        int where = Arrays.binarySearch(alignedPositions, pos);
+        if (where >= 0){
+            return where;
+        }else{
+            return -where - 1;
+        }
+    }
+
+    public int getPreciseMarkerAt(double pos){
+        int where = Arrays.binarySearch(alignedPositions,pos);
+        if (where >= 0){
+            return where;
+        }else{
+            int left = -where-2;
+            int right = -where-1;
+            if (left < 0){
+                left = 0;
+                right = 1;
+            }
+            if (right >= alignedPositions.length){
+               right = alignedPositions.length-1;
+                left = alignedPositions.length-1;
+            }
+            if (Math.abs(alignedPositions[right] - pos) < boxRadius){
+                return right;
+            }else if (Math.abs(pos - alignedPositions[left]) < boxRadius){
+                return left;
+            } else{
+                //out of bounds
+                return -1;
+            }
+        }
+    }
+
+    public void mouseClicked(MouseEvent e) {
+        if ((e.getModifiers() & InputEvent.BUTTON1_MASK) ==
+                InputEvent.BUTTON1_MASK) {
+            int clickX = e.getX();
+            int clickY = e.getY();
+            if (showWM && wmInteriorRect.contains(clickX,clickY)){
+                //convert a click on the worldmap to a point on the big picture
+                int bigClickX = (((clickX - getVisibleRect().x - (worldmap.getWidth()-wmInteriorRect.width)/2)
+                        * chartSize.width) /
+                        wmInteriorRect.width)-getVisibleRect().width/2;
+                int bigClickY = (((clickY - getVisibleRect().y -
+                        (worldmap.getHeight() - wmInteriorRect.height)/2 -
+                        (getVisibleRect().height-worldmap.getHeight())) *
+                        chartSize.height) / wmInteriorRect.height) -
+                        getVisibleRect().height/2;
+
+                //if the clicks are near the edges, correct values
+                if (bigClickX > chartSize.width - getVisibleRect().width){
+                    bigClickX = chartSize.width - getVisibleRect().width;
+                }
+                if (bigClickX < 0){
+                    bigClickX = 0;
+                }
+                if (bigClickY > chartSize.height - getVisibleRect().height){
+                    bigClickY = chartSize.height - getVisibleRect().height;
+                }
+                if (bigClickY < 0){
+                    bigClickY = 0;
+                }
+
+                ((JViewport)getParent()).setViewPosition(new Point(bigClickX,bigClickY));
+            }else{
+                theHV.changeBlocks(BLOX_CUSTOM);
+                Rectangle2D blockselector = new Rectangle2D.Double(clickXShift-boxRadius,clickYShift - boxRadius,
+                        alignedPositions[alignedPositions.length-1] + boxSize, boxSize);
+                if(blockselector.contains(clickX,clickY)){
+                    int whichMarker = getPreciseMarkerAt(clickX - clickXShift);
+                    if (whichMarker > -1){
+                        if (theData.isInBlock[whichMarker]){
+                            theData.removeFromBlock(whichMarker);
+                            repaint();
+                        } else if (whichMarker > 0 && whichMarker < Chromosome.realIndex.length){
+                            theData.addMarkerIntoSurroundingBlock(whichMarker);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public void mousePressed (MouseEvent e) {
+        Rectangle blockselector = new Rectangle(clickXShift-boxRadius,clickYShift - boxRadius,
+                (int)alignedPositions[alignedPositions.length-1]+boxSize, boxSize);
+
+        //if users right clicks & holds, pop up the info
+        if ((e.getModifiers() & InputEvent.BUTTON3_MASK) ==
+                InputEvent.BUTTON3_MASK){
+            Graphics g = getGraphics();
+            g.setFont(popupFont);
+            //FontMetrics metrics = g.getFontMetrics();
+            DPrimeTable dPrimeTable = theData.dpTable;
+            final int clickX = e.getX();
+            final int clickY = e.getY();
+            final int boxX, boxY;
+            boxX = getPreciseMarkerAt(clickX - clickXShift - (clickY-clickYShift));
+            boxY = getPreciseMarkerAt(clickX - clickXShift + (clickY-clickYShift));
+            displayStrings = null;
+
+            if ((boxX >= lowX && boxX <= highX) &&
+                    (boxY > boxX && boxY < highY) &&
+                    !(wmInteriorRect.contains(clickX,clickY))){
+                if (dPrimeTable.getLDStats(boxX,boxY) != null){
+                    double[] freqs = dPrimeTable.getLDStats(boxX,boxY).getFreqs();
+
+                    displayStrings = new Vector();
+                    currentSelection = new String ("Last Selection: ("); // update the cached value
+                    if (theData.infoKnown){
+                        displayStrings.add(new String ("(" +Chromosome.getMarker(boxX).getDisplayName() +
+                                ", " + Chromosome.getMarker(boxY).getDisplayName() + ")"));
+                        double sep = (int)((Chromosome.getMarker(boxY).getPosition() -
+                                Chromosome.getMarker(boxX).getPosition())/100);
+                        sep /= 10;
+                        displayStrings.add(new Double(sep).toString() + " kb");
+                        currentSelection += Chromosome.getMarker(boxX).getName() +
+                                ", " + Chromosome.getMarker(boxY).getName();
+                    }else{
+                        displayStrings.add(new String("(" + (Chromosome.realIndex[boxX]+1) + ", " +
+                                (Chromosome.realIndex[boxY]+1) + ")"));
+                        currentSelection += new String((Chromosome.realIndex[boxX]+1) +
+                                 ", " + (Chromosome.realIndex[boxY]+1));
+                    }
+                    displayStrings.add(new String ("D': " + dPrimeTable.getLDStats(boxX,boxY).getDPrime()));
+                    displayStrings.add(new String ("LOD: " + dPrimeTable.getLDStats(boxX,boxY).getLOD()));
+                    displayStrings.add( new String ("r-squared: " + dPrimeTable.getLDStats(boxX,boxY).getRSquared()));
+                    displayStrings.add(new String ("D' conf. bounds: " +
+                            dPrimeTable.getLDStats(boxX,boxY).getConfidenceLow() + "-" +
+                            dPrimeTable.getLDStats(boxX,boxY).getConfidenceHigh()));
+                    currentSelection += ")  -   D': " + dPrimeTable.getLDStats(boxX,boxY).getDPrime() +
+                            "   LOD: " + dPrimeTable.getLDStats(boxX,boxY).getLOD() +
+                            "   r-squared: " + dPrimeTable.getLDStats(boxX,boxY).getRSquared();
+
+
+
+                    //get the alleles for the 4 two-marker haplotypes
+                    String[] alleleStrings = new String[4];
+                    String[] alleleMap = {"X", "A","C","G","T"};
+                    if (freqs[0] + freqs[1] > freqs[2] + freqs[3]){
+                        alleleStrings[0] = alleleMap[Chromosome.getMarker(boxX).getMajor()];
+                        alleleStrings[1] = alleleMap[Chromosome.getMarker(boxX).getMajor()];
+                        alleleStrings[2] = alleleMap[Chromosome.getMarker(boxX).getMinor()];
+                        alleleStrings[3] = alleleMap[Chromosome.getMarker(boxX).getMinor()];
+                    }else{
+                        alleleStrings[0] = alleleMap[Chromosome.getMarker(boxX).getMinor()];
+                        alleleStrings[1] = alleleMap[Chromosome.getMarker(boxX).getMinor()];
+                        alleleStrings[2] = alleleMap[Chromosome.getMarker(boxX).getMajor()];
+                        alleleStrings[3] = alleleMap[Chromosome.getMarker(boxX).getMajor()];
+                    }
+                    if (freqs[0] + freqs[3] > freqs[1] + freqs[2]){
+                        alleleStrings[0] += alleleMap[Chromosome.getMarker(boxY).getMajor()];
+                        alleleStrings[1] += alleleMap[Chromosome.getMarker(boxY).getMinor()];
+                        alleleStrings[2] += alleleMap[Chromosome.getMarker(boxY).getMinor()];
+                        alleleStrings[3] += alleleMap[Chromosome.getMarker(boxY).getMajor()];
+                    }else{
+                        alleleStrings[0] += alleleMap[Chromosome.getMarker(boxY).getMinor()];
+                        alleleStrings[1] += alleleMap[Chromosome.getMarker(boxY).getMajor()];
+                        alleleStrings[2] += alleleMap[Chromosome.getMarker(boxY).getMajor()];
+                        alleleStrings[3] += alleleMap[Chromosome.getMarker(boxY).getMinor()];
+                    }
+
+                    displayStrings.add(new String("Frequencies:"));
+                    for (int i = 0; i < 4; i++){
+                        if (freqs[i] > 1.0E-10){
+                            displayStrings.add( new String(alleleStrings[i] + " = " +
+                                    Math.rint(1000 * freqs[i])/10 + "%"));
+                        }
+                    }
+                }
+            } else if (blockselector.contains(clickX, clickY)){
+                int marker = getPreciseMarkerAt(clickX - clickXShift);
+                if (marker >= 0){
+                    displayStrings = new Vector();
+                    currentSelection = new String ("Last Selection: "); // update the cached value
+
+                    if (theData.infoKnown){
+                        displayStrings.add(new String (Chromosome.getMarker(marker).getDisplayName()));
+                        currentSelection += Chromosome.getMarker(marker).getName();
+                    }else{
+                        displayStrings.add(new String("Marker " + (Chromosome.realIndex[marker]+1)));
+                        currentSelection += new String("Marker " + (Chromosome.realIndex[marker]+1));
+                    }
+                    displayStrings.add(new String ("MAF: " + Chromosome.getMarker(marker).getMAF()));
+                    if (Chromosome.getMarker(marker).getExtra() != null)
+                        displayStrings.addAll(Chromosome.getMarker(marker).getExtra());
+                    currentSelection += new String (", MAF: " + Chromosome.getMarker(marker).getMAF());
+                }
+            }
+            if (displayStrings != null){
+                popupDrawPoint = new Point(clickX,clickY);
+                repaint();
+            }
+        }else if ((e.getModifiers() & InputEvent.BUTTON1_MASK) ==
+                InputEvent.BUTTON1_MASK){
+            // clear the last selection if the mouse is left clicked
+            lastSelection = new String ("");
+
+            int x = e.getX();
+            int y = e.getY();
+            if (blockselector.contains(x,y)){
+                setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
+                blockStartX = x;
+            }
+        }
+    }
+
+    public void mouseReleased(MouseEvent e) {
+        if ((e.getModifiers() & InputEvent.BUTTON3_MASK) ==
+                InputEvent.BUTTON3_MASK){
+            //remove popped up window
+            popupDrawPoint = null;
+
+            //cache last selection.
+            lastSelection = currentSelection;
+            currentSelection = null;
+
+            repaint();
+        } else if ((e.getModifiers() & InputEvent.BUTTON1_MASK) ==
+                InputEvent.BUTTON1_MASK){
+            //resize window once user has ceased dragging
+            if (getCursor() == Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR)){
+                noImage = true;
+                if (resizeWMRect.width > 20){
+                    wmMaxWidth = resizeWMRect.width;
+                }
+                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+                resizeWMRect = null;
+                repaint();
+            }
+            if (getCursor() == Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)){
+                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+                if (blockRect != null){
+                    //don't add the block if the dragging was really short, as it was probably just a twitch while clicking
+                    if (Math.abs(e.getX() - blockStartX) > boxRadius/2){
+                        int firstMarker = getPreciseMarkerAt(blockStartX - clickXShift);
+                        int lastMarker = getPreciseMarkerAt(e.getX() - clickXShift);
+                        //we're moving left to right
+                        if (blockStartX > e.getX()){
+                            int temp = firstMarker;
+                            firstMarker = lastMarker;
+                            lastMarker = temp;
+                        }
+                        //negative results represent starting or stopping the drag in "no-man's land"
+                        //so we adjust depending on which side we're on
+                        if (firstMarker < 0){
+                            firstMarker = -firstMarker + 1;
+                        }
+                        if (lastMarker < 0){
+                            lastMarker = -lastMarker;
+                        }
+                        theHV.changeBlocks(BLOX_CUSTOM);
+                        theData.addBlock(firstMarker, lastMarker);
+                    }
+                    blockRect = null;
+                    repaint();
+                }
+            }
+
+        }
+    }
+
+    public void mouseDragged(MouseEvent e) {
+        if ((e.getModifiers() & InputEvent.BUTTON1_MASK) ==
+                InputEvent.BUTTON1_MASK) {
+            //conveniently, we can tell what do do with the drag event
+            //based on what the cursor is
+            if (getCursor() == Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR)){
+                int width = e.getX() - wmInteriorRect.x;
+                double ratio = (double)width/(double)worldmap.getWidth();
+                int height = (int)(ratio*worldmap.getHeight());
+
+                resizeWMRect = new Rectangle(wmInteriorRect.x+1,
+                        wmInteriorRect.y + wmInteriorRect.height - height,
+                        width,
+                        height-1);
+                repaint();
+            }else if (getCursor() == Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)){
+                Rectangle r = getVisibleRect();
+
+                int xcorner,width;
+                if (e.getX() < blockStartX){
+                    if (e.getX() < r.x +2){
+                        scrollRectToVisible(new Rectangle(r.x -25, r.y, r.width, 1));
+                    }
+                    //we're dragging right to left, so flip it.
+                    xcorner = e.getX() - clickXShift + left;
+                    width =  blockStartX - e.getX();
+                }else{
+                    if (e.getX() > r.x + r.width - 2){
+                        scrollRectToVisible(new Rectangle(r.x+25,r.y,r.width,1));
+                    }
+                    xcorner = blockStartX - clickXShift + left;
+                    width = e.getX() - blockStartX;
+                }
+                blockRect = new Rectangle(xcorner, top - boxRadius/2 - TEXT_GAP,
+                        width,boxRadius);
+                repaint();
+            }
+        }
+    }
+
+    public void mouseMoved(MouseEvent e){
+        //when the user mouses over the corner of the worldmap, change the cursor
+        //to the resize cursor
+        if (getCursor() == Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)){
+            if (wmResizeCorner.contains(e.getPoint())){
+                setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
+            }
+        } else if (getCursor() == Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR)){
+            if (!(wmResizeCorner.contains(e.getPoint()))){
+                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+            }
+        }
+    }
+
+    public void mouseEntered(MouseEvent e) {
+    }
+
+    public void mouseExited(MouseEvent e) {
+    }
+
+}
+
diff --git a/edu/mit/wi/haploview/DPrimeTable.java b/edu/mit/wi/haploview/DPrimeTable.java
new file mode 100755
index 0000000..3e16603
--- /dev/null
+++ b/edu/mit/wi/haploview/DPrimeTable.java
@@ -0,0 +1,45 @@
+package edu.mit.wi.haploview;
+
+import java.util.Vector;
+
+public class DPrimeTable {
+    private PairwiseLinkage[][] theTable;
+
+    public DPrimeTable(int numMarkers){
+        theTable = new PairwiseLinkage[numMarkers][];
+    }
+
+    public void addMarker(Vector marker, int pos){
+        theTable[pos] = (PairwiseLinkage[]) marker.toArray(new PairwiseLinkage[0]);
+    }
+
+    public PairwiseLinkage getLDStats(int pos1, int pos2){
+        //we need to convert the input of an absolute position into the relative position
+        //to index into the DP array. here we jump through the additional hoop of un-filtering the input
+        //numbers
+        int x = Chromosome.realIndex[pos1];
+        int y = Chromosome.realIndex[pos2] - x - 1;
+        if (x < theTable.length-1){
+            if (y < theTable[x].length){
+                return theTable[x][y];
+            }else{
+                return null;
+            }
+        }else{
+            return null;
+        }
+    }
+
+    public int getLength(int x){
+        //same as above but for the filtered dataset
+        int whichMarker = Chromosome.realIndex[x];
+        if (whichMarker >= theTable.length-1) return 0;
+        for (int m = theTable[whichMarker].length+whichMarker; m > whichMarker; m--){
+            if (Chromosome.filterIndex[m] != -1){
+                //length of array is one greater than difference of first and last elements
+                return Chromosome.filterIndex[m] - Chromosome.filterIndex[whichMarker] + 1;
+            }
+        }
+        return 0;
+    }
+}
\ No newline at end of file
diff --git a/edu/mit/wi/haploview/EM.java b/edu/mit/wi/haploview/EM.java
new file mode 100755
index 0000000..4cb5028
--- /dev/null
+++ b/edu/mit/wi/haploview/EM.java
@@ -0,0 +1,1290 @@
+package edu.mit.wi.haploview;
+
+import java.util.*;
+
+public class EM implements Constants {
+    //results fields
+    private int[][] haplotypes;
+    private double[] frequencies;
+    private Vector obsT, obsU;
+    private Vector controlCounts, caseCounts;
+    private Vector discordantCounts;
+
+    final int MISSINGLIMIT = 4;
+    final int MAXLOCI = 500;
+    final int MAXLN = 1000;
+    final double PSEUDOCOUNT = 0.1;
+    OBS[] data;
+    SUPER_OBS[] superdata;
+    static int[] two_n = new int[32];
+    static {
+        //an old-school speedup courtesy of mjdaly
+        two_n[0]=1;
+        for (int i=1; i<31; i++){
+            two_n[i]=2*two_n[i-1];
+        }
+    }
+    int[][] ambighet;
+    private Vector chromosomes;
+    private int numTrios;
+    private int numFilteredTrios;
+    private boolean[][][] kidConsistentCache;
+    private Vector realAffectedStatus, realKidAffectedStatus;
+    private MapWrap fullProbMap;
+    private boolean[] haploid;
+
+    private int extraTrioCount;
+    private int updatedExtraTrioCount;
+
+
+    EM(Vector chromosomes, int numTrios, Vector extraInds){
+
+        //we need to add extra copies of haploid chromosomes so we add a second copy
+        this.chromosomes = new Vector();
+
+        if(extraInds != null){
+            for(int i=0;i<extraInds.size();i++){
+                this.chromosomes.add(extraInds.elementAt(i));
+                if(((Chromosome)this.chromosomes.lastElement()).isHaploid()){
+                    this.chromosomes.add(extraInds.elementAt(i));
+                }
+            }
+        }
+
+        extraTrioCount = this.chromosomes.size()/4;
+
+        for(int i=0;i<chromosomes.size();i++) {
+            this.chromosomes.add(chromosomes.elementAt(i));
+            if(((Chromosome)this.chromosomes.lastElement()).isHaploid()){
+                this.chromosomes.add(chromosomes.elementAt(i));
+            }
+        }
+
+        this.numTrios = numTrios + extraTrioCount;
+
+    }
+
+
+    class MapWrap {
+        private HashMap theMap;
+        private Double defaultVal;
+
+        MapWrap(double d) {
+            theMap = new HashMap();
+            defaultVal = new Double(d);
+        }
+
+        double get(Object key) {
+            if(theMap.containsKey(key)) {
+                return ((Double) theMap.get(key)).doubleValue();
+            } else {
+                return defaultVal.doubleValue();
+            }
+        }
+
+        void put(Object key, double val) {
+            theMap.put(key,new Double(val));
+        }
+
+        void setDefaultVal(double val) {
+            defaultVal = new Double(val);
+        }
+
+        void normalize(double normalizer) {
+            Iterator itr = theMap.keySet().iterator();
+            while(itr.hasNext()) {
+                Object key = itr.next();
+                put(key,(get(key) / normalizer));
+            }
+            defaultVal = new Double(defaultVal.doubleValue() / normalizer);
+        }
+
+        Set getKeySet() {
+            return theMap.keySet();
+        }
+    }
+
+    class DiscordantTally {
+        private double[] counts = new double[9];
+        private long theAllele;
+
+        public DiscordantTally(long a){
+            theAllele = a;
+        }
+
+        public void tally(long affectedHap1,long affectedHap2,long unaffectedHap1, long unaffectedHap2, double freq){
+            int offset = 0;
+            if(affectedHap1 == affectedHap2 && affectedHap1 == theAllele) {
+                //aff is DD
+                offset  = 0;
+            }else if(affectedHap1 == theAllele || affectedHap2 == theAllele) {
+                //aff is Dd
+                offset = 1;
+            }else {
+                offset = 2;
+            }
+
+            if(unaffectedHap1 == theAllele) {
+                if(unaffectedHap1 == unaffectedHap2) {
+                    //unaff is DD
+                    counts[0+offset]+= freq;
+                }else {
+                    //unaff is Dd
+                    counts[3+ offset]+= freq;
+                }
+            } else if(unaffectedHap2 == theAllele) {
+                //unaff is Dd
+                counts[3 + offset]+= freq;
+            }else {
+                //unaff is dd
+                counts[6+offset]+= freq;
+            }
+        }
+
+        public void combine(DiscordantTally dt) {
+            for(int i=0;i<9;i++) {
+                counts[i] += dt.counts[i];
+            }
+        }
+
+        public void normalize(double d){
+            for(int i=0;i<9;i++) {
+                counts[i] /= d;
+            }
+        }
+
+        public double[] getCounts() {
+            return counts;
+        }
+    }
+
+    class Recovery {
+        long h1;
+        long h2;
+        float p;
+        public Recovery() {
+            h1=0;
+            h2=0;
+            p=0.0f;
+        }
+    }
+
+    class OBS {
+        int nposs;
+        //Recovery[] poss;
+        Vector poss;
+        public OBS() {
+            //intialize poss vector to size passed in, and set its capacity increment to 16
+            poss = new Vector(20,8);
+            //poss = new Recovery[size];
+            //for (int i=0; i<size; ++i) poss[i] = new Recovery();
+        }
+    }
+
+    class SUPER_OBS {
+        int nblocks;
+        int[] nposs;
+        Recovery[][] poss;
+        int nsuper;
+        Recovery[] superposs;
+        public SUPER_OBS(int size) {
+            poss = new Recovery[size][];
+            nposs = new int[size];
+        }
+    }
+
+    public void doEM(int[] theBlock) throws HaploViewException{
+        //break up large blocks if needed
+        int[] block_size;
+        if (theBlock.length < 9){
+            block_size = new int[1];
+            block_size[0] = theBlock.length;
+        } else {
+            //some base-8 arithmetic
+            int ones = theBlock.length%8;
+            int eights = (theBlock.length - ones)/8;
+            if (ones == 0){
+                block_size = new int[eights];
+                for (int i = 0; i < eights; i++){
+                    block_size[i]=8;
+                }
+            } else {
+                block_size = new int[eights+1];
+                for (int i = 0; i < eights-1; i++){
+                    block_size[i]=8;
+                }
+                block_size[eights-1] = (8+ones)/2;
+                block_size[eights] = 8+ones-block_size[eights-1];
+            }
+        }
+
+
+
+
+        byte[] thisHap;
+        Vector inputHaploSingletons = new Vector();
+        Vector inputHaploTrios = new Vector();
+        Vector affSingletons = new Vector();
+        Vector affTrios = new Vector();
+        Vector affKids = new Vector();
+        Vector haploidTrios = new Vector();
+        Vector haploidSingletons = new Vector();
+        //whichVector[i] stores a value which indicates which vector chromosome i's genotype should go in
+        //1 indicates inputHaploSingletons (singletons), 2 indicates inputHaploTrios,
+        //3 indicates a person from a broken trio who is treated as a singleton
+        //0 indicates none (too much missing data)
+        int[] whichVector = new int[chromosomes.size()];
+        updatedExtraTrioCount = 0;
+
+        for(int i=0;i<numTrios*4; i+=4) {
+            Chromosome parentAFirst = (Chromosome) chromosomes.elementAt(i);
+            Chromosome parentASecond = (Chromosome) chromosomes.elementAt(i+1);
+            Chromosome parentBFirst = (Chromosome) chromosomes.elementAt(i+2);
+            Chromosome parentBSecond = (Chromosome) chromosomes.elementAt(i+3);
+            boolean tooManyMissingInASegmentA = false;
+            boolean tooManyMissingInASegmentB = false;
+            int totalMissingA = 0;
+            int totalMissingB = 0;
+            int segmentShift = 0;
+            for (int n = 0; n < block_size.length; n++){
+                int missingA = 0;
+                int missingB = 0;
+                for (int j = 0; j < block_size[n]; j++){
+                    byte AFirstGeno = parentAFirst.getGenotype(theBlock[segmentShift+j]);
+                    byte ASecondGeno = parentASecond.getGenotype(theBlock[segmentShift+j]);
+                    byte BFirstGeno = parentBFirst.getGenotype(theBlock[segmentShift+j]);
+                    byte BSecondGeno = parentBSecond.getGenotype(theBlock[segmentShift+j]);
+
+                    if(AFirstGeno == 0 || ASecondGeno == 0) missingA++;
+                    if(BFirstGeno == 0 || BSecondGeno == 0) missingB++;
+                }
+                segmentShift += block_size[n];
+                if (missingA >= MISSINGLIMIT){
+                    tooManyMissingInASegmentA = true;
+                }
+                if (missingB >= MISSINGLIMIT){
+                    tooManyMissingInASegmentB = true;
+                }
+                totalMissingA += missingA;
+                totalMissingB += missingB;
+            }
+
+            if(!tooManyMissingInASegmentA && totalMissingA <= 1+theBlock.length/3
+                    && !tooManyMissingInASegmentB && totalMissingB <= 1+theBlock.length/3) {
+                //both parents are good so all 4 chroms are added as a trio
+                whichVector[i] = 2;
+                whichVector[i+1] = 2;
+                whichVector[i+2] = 2;
+                whichVector[i+3] = 2;
+
+                if (i/4 < extraTrioCount){
+                    updatedExtraTrioCount++;
+                }
+            }
+            else if((!tooManyMissingInASegmentA && totalMissingA <= 1+theBlock.length/3)
+                    && i/4 >=  extraTrioCount ) {
+                //first person good, so he's added as a singleton, other parent is dropped
+                whichVector[i] = 3;
+                whichVector[i+1] =3;
+                whichVector[i+2] =0;
+                whichVector[i+3]=0;
+            }
+            else if(!tooManyMissingInASegmentB && totalMissingB <= 1+theBlock.length/3
+                    &&  i/4 >= extraTrioCount){
+                //second person good, so he's added as a singleton, other parent is dropped
+                whichVector[i] = 0;
+                whichVector[i+1] =0;
+                whichVector[i+2] =3;
+                whichVector[i+3]=3;
+            }
+            else {
+                //both people have too much missing data so neither is used
+                whichVector[i] = 0;
+                whichVector[i+1] =0;
+                whichVector[i+2] =0;
+                whichVector[i+3]=0;
+            }
+
+        }
+
+
+        for (int i = numTrios*4; i < chromosomes.size(); i++){
+            Chromosome thisChrom = (Chromosome)chromosomes.elementAt(i);
+            Chromosome nextChrom = (Chromosome)chromosomes.elementAt(++i);
+            boolean tooManyMissingInASegment = false;
+            int totalMissing = 0;
+            int segmentShift = 0;
+            for (int n = 0; n < block_size.length; n++){
+                int missing = 0;
+                for (int j = 0; j < block_size[n]; j++){
+                    byte theGeno = thisChrom.getGenotype(theBlock[segmentShift+j]);
+                    byte nextGeno = nextChrom.getGenotype(theBlock[segmentShift+j]);
+                    if(theGeno == 0 || nextGeno == 0) missing++;
+                }
+                segmentShift += block_size[n];
+                if (missing >= MISSINGLIMIT){
+                    tooManyMissingInASegment = true;
+                }
+                totalMissing += missing;
+            }
+            //we want to use chromosomes without too many missing genotypes in a given
+            //subsegment (first term) or without too many missing genotypes in the
+            //whole block (second term)
+            if (!tooManyMissingInASegment && totalMissing <= 1+theBlock.length/3){
+                whichVector[i-1] = 1;
+                whichVector[i] = 1;
+            }
+        }
+
+        //we only want to add an affected status every other chromosome, so we flip this boolean each time
+        boolean addAff = true;
+        for (int i = 0; i < chromosomes.size(); i++){
+            Chromosome thisChrom = (Chromosome)chromosomes.elementAt(i);
+
+            if(whichVector[i] > 0) {
+                thisHap = new byte[theBlock.length];
+                for (int j = 0; j < theBlock.length; j++){
+                    byte a1 = Chromosome.getMarker(theBlock[j]).getMajor();
+                    byte a2 = Chromosome.getMarker(theBlock[j]).getMinor();
+                    byte theGeno = thisChrom.getGenotype(theBlock[j]);
+                    if (theGeno >= 5){
+                        thisHap[j] = 'h';
+                    } else {
+                        if (theGeno == 0){
+                            thisHap[j] = '0';
+                        }else if (theGeno == a1){
+                            thisHap[j] = '1';
+                        }else if (theGeno == a2){
+                            thisHap[j] = '2';
+                        }else{
+                            throw new HaploViewException("Marker with > 2 alleles: " +
+                                    Chromosome.getMarker(theBlock[j]).getDisplayName());
+                        }
+                    }
+                }
+                if(whichVector[i] == 1) {
+                    inputHaploSingletons.add(thisHap);
+                    if(addAff) {
+                        affSingletons.add(new Integer(thisChrom.getAffected()));
+                        haploidSingletons.add(new Boolean(thisChrom.isHaploid()));
+                    }
+                }
+                else if(whichVector[i] ==2) {
+                    inputHaploTrios.add(thisHap);
+                    if(addAff) {
+                        affTrios.add(new Integer(thisChrom.getAffected()));
+                        affKids.add(thisChrom.getKidAffected());
+                        haploidTrios.add(new Boolean(thisChrom.isHaploid()));
+                    }
+                }else if (whichVector[i] == 3){
+                    inputHaploSingletons.add(thisHap);
+                    if(addAff) {
+                        affSingletons.add(new Integer(0));
+                        haploidSingletons.add(new Boolean(thisChrom.isHaploid()));
+                    }
+                }
+                if(addAff) {
+                    addAff = false;
+                }
+                else {
+                    addAff =true;
+                }
+            }
+        }
+        numFilteredTrios  = inputHaploTrios.size() / 4;
+        inputHaploTrios.addAll(inputHaploSingletons);
+        affTrios.addAll(affSingletons);
+
+        byte[][] input_haplos = (byte[][])inputHaploTrios.toArray(new byte[0][0]);
+
+        haploidTrios.addAll(haploidSingletons);
+
+        haploid = new boolean[haploidTrios.size()];
+        for(int i=0;i<haploidTrios.size();i++){
+            haploid[i] = ((Boolean)haploidTrios.elementAt(i)).booleanValue();
+        }
+
+        full_em_breakup(input_haplos, block_size, affTrios, affKids);
+
+
+    }
+
+    private void full_em_breakup( byte[][] input_haplos, int[] block_size, Vector affStatus, Vector kidAffStatus) throws HaploViewException{
+        int num_poss, iter;
+        double total = 0;
+        int block, start_locus, end_locus, biggest_block_size;
+        int num_indivs = 0;
+
+        int num_blocks = block_size.length;
+        int num_haplos = input_haplos.length;
+        int num_loci = input_haplos[0].length;
+
+        Recovery tempRec;
+
+        if (num_loci > MAXLOCI){
+            throw new HaploViewException("Too many loci in a single block (> "+MAXLOCI+" non-redundant)");
+        }
+
+        //figure out the size of the biggest block
+        biggest_block_size=block_size[0];
+        for (int i=1; i<num_blocks; i++) {
+            if (block_size[i] > biggest_block_size)
+                biggest_block_size=block_size[i];
+        }
+
+        num_poss = two_n[biggest_block_size];
+        data = new OBS[num_haplos/2];
+        for (int i=0; i<num_haplos/2; i++)
+            data[i]= new OBS();
+
+        superdata = new SUPER_OBS[num_haplos/2];
+        for (int i=0; i<num_haplos/2; i++)
+            superdata[i]= new SUPER_OBS(num_blocks);
+
+        double[][] hprob = new double[num_blocks][num_poss];
+        int[][] hlist = new int[num_blocks][num_poss];
+        int[] num_hlist = new int[num_blocks];
+        int[] hint = new int[num_poss];
+
+        MapWrap probMap = new MapWrap(PSEUDOCOUNT);
+
+        /* for trio option */
+        if (Options.getAssocTest() == ASSOC_TRIO) {
+            ambighet = new int[(num_haplos/4)][num_loci];
+            store_dhet_status(num_haplos,num_loci,input_haplos);
+        }
+
+        end_locus=-1;
+        //now we loop through the blocks
+        for (block=0; block<num_blocks; block++) {
+            start_locus=end_locus+1;
+            end_locus=start_locus+block_size[block]-1;
+            num_poss=two_n[block_size[block]];
+
+            //read_observations initializes the values in data[] (array of OBS)
+            num_indivs=read_observations(num_haplos,num_loci,input_haplos,start_locus,end_locus);
+
+            total=(double)num_poss;
+            total *= PSEUDOCOUNT;
+
+            /* starting prob is phase known haps + 0.1 (PSEUDOCOUNT) count of every haplotype -
+            i.e., flat when nothing is known, close to phase known if a great deal is known */
+
+            for (int i=0; i<num_indivs; i++) {
+                if (data[i].nposs==1 && i >= updatedExtraTrioCount*2) {
+                    tempRec = (Recovery)data[i].poss.elementAt(0);
+                    probMap.put(new Long(tempRec.h1), probMap.get(new Long(tempRec.h1)) + 1.0);
+                    if (!haploid[i]){
+                        probMap.put(new Long(tempRec.h2), probMap.get(new Long(tempRec.h2)) + 1.0);
+                        total+=2.0;
+                    }else{
+                        total+=1.0;
+                    }
+                }
+            }
+
+            probMap.normalize(total);
+
+            // EM LOOP: assign ambiguous data based on p, then re-estimate p
+            iter=0;
+            while (iter<20) {
+                // compute probabilities of each possible observation
+                for (int i=0; i<num_indivs; i++) {
+                    total=0.0;
+                    for (int k=0; k<data[i].nposs; k++) {
+                        tempRec = (Recovery) data[i].poss.elementAt(k);
+                        if(haploid[i]){
+                            if (tempRec.h1 == tempRec.h2){
+                                //for haploids we only consider reconstructions where both chroms are equal,
+                                //since those are the only truly possible ones (i.e. heterozygous reconstructions
+                                //are mistakes)
+                                tempRec.p = (float)(probMap.get(new Long(tempRec.h1)));
+                            }else{
+                                tempRec.p = 0;
+                            }
+                        }else {
+                            tempRec.p = (float)(probMap.get(new Long(tempRec.h1))*probMap.get(new Long(tempRec.h2)));
+                        }
+                        total+=tempRec.p;
+                    }
+                    // normalize
+                    for (int k=0; k<data[i].nposs; k++) {
+                        tempRec = (Recovery) data[i].poss.elementAt(k);
+                        tempRec.p /= total;
+                    }
+                }
+
+                // re-estimate prob
+                probMap = new MapWrap(1e-10);
+
+                total=num_poss*1e-10;
+
+                for (int i=0; i<num_indivs; i++) {
+                    if(i >= updatedExtraTrioCount*2){
+                        for (int k=0; k<data[i].nposs; k++) {
+                            tempRec = (Recovery) data[i].poss.elementAt(k);
+                            probMap.put(new Long(tempRec.h1),probMap.get(new Long(tempRec.h1)) + tempRec.p);
+                            if (!haploid[i]){
+                                probMap.put(new Long(tempRec.h2),probMap.get(new Long(tempRec.h2)) + tempRec.p);
+                                total+=(2.0*(tempRec.p));
+                            }else{
+                                total += tempRec.p;
+                            }
+                        }
+                    }
+                }
+
+                probMap.normalize(total);
+
+                iter++;
+            }
+
+            int m=0;
+            for(long j=0;j<num_poss; j++){
+                hint[(int)j]=-1;
+                if (probMap.get(new Long(j)) > .001) {
+                    // printf("haplo %s   p = %.4lf\n",haplo_str(j,block_size[block]),prob[j]);
+                    hlist[block][m]=(int)j;
+                    hprob[block][m]=probMap.get(new Long(j));
+                    hint[(int)j]=m;
+                    m++;
+                }
+            }
+            num_hlist[block]=m;
+
+            // store current block results in super obs structure
+            store_block_haplos(hlist, hprob, hint, block, num_indivs);
+
+        } /* for each block */
+
+        double poss_full=1;
+        for (block=0; block<num_blocks; block++) {
+            poss_full *= num_hlist[block];
+        }
+
+        /* LIGATE and finish this mess :) */
+
+        fullProbMap = new MapWrap(PSEUDOCOUNT);
+
+        create_super_haplos(num_indivs,num_blocks,num_hlist);
+
+        /* run standard EM on supercombos */
+
+        /* start prob array with probabilities from full observations */
+
+        total = poss_full * PSEUDOCOUNT;
+
+        /* starting prob is phase known haps + 0.1 (PSEUDOCOUNT) count of every haplotype -
+        i.e., flat when nothing is known, close to phase known if a great deal is known */
+
+
+        for (int i=0; i<num_indivs; i++) {
+            if (superdata[i].nsuper==1 && i >= updatedExtraTrioCount*2)  {
+                Long h1 = new Long(superdata[i].superposs[0].h1);
+                Long h2 = new Long(superdata[i].superposs[0].h2);
+
+                fullProbMap.put(h1,fullProbMap.get(h1) +1.0);
+                if (!haploid[i]){
+                    fullProbMap.put(h2,fullProbMap.get(h2) +1.0);
+                    total+=2.0;
+                }else{
+                    total+=1.0;
+                }
+            }
+        }
+
+        fullProbMap.normalize(total);
+
+        /* EM LOOP: assign ambiguous data based on p, then re-estimate p */
+        iter=0;
+        while (iter<20) {
+            /* compute probabilities of each possible observation */
+            for (int i=0; i<num_indivs; i++) {
+                total=0.0;
+                for (int k=0; k<superdata[i].nsuper; k++) {
+                    if(haploid[i]){
+                        if (superdata[i].superposs[k].h1 == superdata[i].superposs[k].h2){
+                            //only consider reconstructions of haploid chromosomes where h1 == h2
+                            //since heterozygous reconstructions aren't possible for haploids
+                            superdata[i].superposs[k].p = (float)
+                                    (fullProbMap.get(new Long(superdata[i].superposs[k].h1)));
+                        }else{
+                            superdata[i].superposs[k].p = 0;
+                        }
+                    }else{
+                        superdata[i].superposs[k].p = (float)
+                                (fullProbMap.get(new Long(superdata[i].superposs[k].h1))*
+                                        fullProbMap.get(new Long(superdata[i].superposs[k].h2)));
+                    }
+                    total+=superdata[i].superposs[k].p;
+                }
+                /* normalize */
+                for (int k=0; k<superdata[i].nsuper; k++) {
+                    superdata[i].superposs[k].p /= total;
+                }
+            }
+
+            /* re-estimate prob */
+            fullProbMap = new MapWrap(1e-10);
+
+            total=poss_full*1e-10;
+
+            for (int i=0; i<num_indivs; i++) {
+                if(i >= updatedExtraTrioCount*2){
+                    for (int k=0; k<superdata[i].nsuper; k++) {
+                        fullProbMap.put(new Long(superdata[i].superposs[k].h1),fullProbMap.get(new Long(superdata[i].superposs[k].h1)) + superdata[i].superposs[k].p);
+                        if(!haploid[i]){
+                            fullProbMap.put(new Long(superdata[i].superposs[k].h2),fullProbMap.get(new Long(superdata[i].superposs[k].h2)) + superdata[i].superposs[k].p);
+                            total+=(2.0*superdata[i].superposs[k].p);
+                        }else{
+                            total += superdata[i].superposs[k].p;
+                        }
+                    }
+                }
+            }
+
+            fullProbMap.normalize(total);
+
+            iter++;
+        }
+
+        /* we're done - the indices of superprob now have to be
+        decoded to reveal the actual haplotypes they represent */
+
+        if(Options.getAssocTest() == ASSOC_TRIO) {
+            kidConsistentCache = new boolean[numFilteredTrios][][];
+            for(int i=0;i<numFilteredTrios*2;i+=2) {
+                if (((Integer)kidAffStatus.elementAt(i)).intValue() == 2){
+                    kidConsistentCache[i/2] = new boolean[superdata[i].nsuper][];
+                    for (int n=0; n<superdata[i].nsuper; n++) {
+                        kidConsistentCache[i/2][n] = new boolean[superdata[i+1].nsuper];
+                        for (int m=0; m<superdata[i+1].nsuper; m++) {
+                            kidConsistentCache[i/2][n][m] = kid_consistent(superdata[i].superposs[n].h1,
+                                    superdata[i+1].superposs[m].h1,num_blocks,
+                                    block_size,hlist,num_hlist,i/2,num_loci);
+                        }
+                    }
+                }
+            }
+        }
+
+        realAffectedStatus = affStatus;
+        realKidAffectedStatus = kidAffStatus;
+
+        doAssociationTests(affStatus, null,null, kidAffStatus);
+
+
+        Vector haplos_present = new Vector();
+        Vector haplo_freq= new Vector();
+
+        ArrayList keys =  new ArrayList(fullProbMap.theMap.keySet());
+        Collections.sort(keys);
+        Iterator kitr = keys.iterator();
+        while(kitr.hasNext()) {
+            Object key = kitr.next();
+            long keyLong = ((Long)key).longValue();
+            if(fullProbMap.get(key) > .001) {
+                haplos_present.addElement(decode_haplo_str(keyLong,num_blocks,block_size,hlist,num_hlist));
+                haplo_freq.addElement(new Double(fullProbMap.get(key)));
+            }
+        }
+
+        double[] freqs = new double[haplo_freq.size()];
+        for(int j=0;j<haplo_freq.size();j++) {
+            freqs[j] = ((Double)haplo_freq.elementAt(j)).doubleValue();
+        }
+
+
+        this.haplotypes = (int[][])haplos_present.toArray(new int[0][0]);
+        this.frequencies = freqs;
+    }
+
+    public void doAssociationTests(Vector affStatus, Vector permuteInd,Vector permuteDiscPar, Vector kidAffStatus) {
+        if(fullProbMap == null || superdata == null || realAffectedStatus == null || realKidAffectedStatus == null) {
+            return;
+        }
+        if(affStatus == null){
+            affStatus = realAffectedStatus;
+        }
+
+        if (kidAffStatus == null){
+            kidAffStatus = realKidAffectedStatus;
+        }
+
+        if(permuteInd == null) {
+            permuteInd = new Vector();
+            for (int i = 0; i < superdata.length; i++){
+                permuteInd.add(new Boolean(false));
+            }
+        }
+
+        if(permuteDiscPar == null) {
+            permuteDiscPar = new Vector();
+            for(int i=0; i< superdata.length; i++) {
+                permuteDiscPar.add(new Boolean(false));
+            }
+        }
+
+        Vector caseCounts = new Vector();
+        Vector controlCounts = new Vector();
+        if (Options.getAssocTest() == ASSOC_CC){
+            MapWrap totalCase = new MapWrap(0);
+            MapWrap totalControl = new MapWrap(0);
+
+
+            for (int i = numFilteredTrios*2; i < superdata.length; i++){
+                MapWrap tempCase = new MapWrap(0);
+                MapWrap tempControl = new MapWrap(0);
+                double tempnorm=0;
+
+                for (int n=0; n<superdata[i].nsuper; n++) {
+                    Long long1 = new Long(superdata[i].superposs[n].h1);
+                    Long long2 = new Long(superdata[i].superposs[n].h2);
+                    if (((Integer)affStatus.elementAt(i)).intValue() == 1){
+                        tempControl.put(long1,tempControl.get(long1) + superdata[i].superposs[n].p);
+                        if(!haploid[i]){
+                            tempControl.put(long2,tempControl.get(long2) + superdata[i].superposs[n].p);
+                        }
+                    }else if (((Integer)affStatus.elementAt(i)).intValue() == 2){
+                        tempCase.put(long1,tempCase.get(long1) + superdata[i].superposs[n].p);
+                        if(!haploid[i]){
+                            tempCase.put(long2,tempCase.get(long2) + superdata[i].superposs[n].p);
+                        }
+                    }
+                    tempnorm += superdata[i].superposs[n].p;
+                }
+                if (tempnorm > 0.00) {
+                    Iterator itr = fullProbMap.getKeySet().iterator();
+                    while(itr.hasNext()) {
+                        Long curHap = (Long) itr.next();
+                        if (tempCase.get(curHap) > 0.0000 || tempControl.get(curHap) > 0.0000) {
+                            totalCase.put(curHap,totalCase.get(curHap) + (tempCase.get(curHap)/tempnorm));
+                            totalControl.put(curHap,totalControl.get(curHap) + (tempControl.get(curHap)/tempnorm));
+                        }
+                    }
+                }
+            }
+            ArrayList sortedKeySet = new ArrayList(fullProbMap.getKeySet());
+            Collections.sort(sortedKeySet);
+            for (int j = 0; j <sortedKeySet.size(); j++){
+                if (fullProbMap.get(sortedKeySet.get(j)) > .001) {
+                    caseCounts.add(new Double(totalCase.get(sortedKeySet.get(j))));
+                    controlCounts.add(new Double(totalControl.get(sortedKeySet.get(j))));
+                }
+            }
+        }
+
+        Vector obsT = new Vector();
+        Vector obsU = new Vector();
+        if(Options.getAssocTest() == ASSOC_TRIO)
+        {
+            double product;
+            MapWrap totalT = new MapWrap(0);
+            MapWrap totalU = new MapWrap(0);
+            discordantCounts = new Vector();
+
+            HashMap totalDiscordantCounts = new HashMap();
+            for (int i=0; i<numFilteredTrios*2; i+=2) {
+                MapWrap tempT = new MapWrap(0);
+                MapWrap tempU = new MapWrap(0);
+                HashMap tempDiscordantCounts = new HashMap();
+                double tempnorm = 0;
+                if (((Integer)kidAffStatus.elementAt(i)).intValue() == 2){
+                    boolean discordantParentPhenos = false;
+                    if(Options.getTdtType() == TDT_PAREN &&
+                            ((Integer)affStatus.elementAt(i)) .intValue() != ((Integer)affStatus.elementAt(i+1)).intValue()) {
+                        discordantParentPhenos = true;
+                    }
+                    for (int n=0; n<superdata[i].nsuper; n++) {
+                        for (int m=0; m<superdata[i+1].nsuper; m++) {
+                            if(kidConsistentCache[i/2][n][m]) {
+                                product=superdata[i].superposs[n].p*superdata[i+1].superposs[m].p;
+                                Long h1 = new Long(superdata[i].superposs[n].h1);
+                                Long h2 = new Long(superdata[i].superposs[n].h2);
+                                Long h3 = new Long(superdata[i+1].superposs[m].h1);
+                                Long h4 = new Long(superdata[i+1].superposs[m].h2);
+                                if(((Boolean)permuteInd.get(i)).booleanValue()) {
+                                    if (superdata[i].superposs[n].h1 != superdata[i].superposs[n].h2) {
+                                        tempU.put(h1, tempU.get(h1) + product);
+                                        tempT.put(h2, tempT.get(h2) + product);
+                                    }
+                                    if (superdata[i+1].superposs[m].h1 != superdata[i+1].superposs[m].h2) {
+                                        tempU.put(h3, tempU.get(h3) + product);
+                                        tempT.put(h4, tempT.get(h4) + product);
+                                    }
+                                } else {
+                                    if (superdata[i].superposs[n].h1 != superdata[i].superposs[n].h2) {
+                                        tempT.put(h1, tempT.get(h1) + product);
+                                        tempU.put(h2, tempU.get(h2) + product);
+                                    }
+                                    if (superdata[i+1].superposs[m].h1 != superdata[i+1].superposs[m].h2) {
+                                        tempT.put(h3, tempT.get(h3) + product);
+                                        tempU.put(h4, tempU.get(h4) + product);
+                                    }
+                                }
+                                // normalize by all possibilities, even double hom
+                                tempnorm+=product;
+
+                                if(discordantParentPhenos) {
+                                    Long aff1,aff2,unaff1,unaff2;
+                                    if(((Integer)affStatus.elementAt(i)).intValue() == 2) {
+                                        aff1 = h1;
+                                        aff2 = h2;
+                                        unaff1 = h3;
+                                        unaff2 = h4;
+                                    }else {
+                                        unaff1 = h1;
+                                        unaff2 = h2;
+                                        aff1 = h3;
+                                        aff2 = h4;
+                                    }
+                                    //if were permuting then we switch the affected and unaffected
+                                    if(((Boolean)permuteDiscPar.get(i)).booleanValue()){
+                                        Long temp1 = aff1;
+                                        Long temp2 = aff2;
+                                        aff1 = unaff1;
+                                        aff2 = unaff2;
+                                        unaff1 = temp1;
+                                        unaff2 = temp2;
+                                    }
+                                    DiscordantTally dt = getTally(aff1,tempDiscordantCounts);
+                                    dt.tally(aff1.longValue(),aff2.longValue(),unaff1.longValue(),unaff2.longValue(),product);
+
+                                    if(!aff2.equals(aff1)) {
+                                        dt = getTally(aff2,tempDiscordantCounts);
+                                        dt.tally(aff1.longValue(),aff2.longValue(),unaff1.longValue(),unaff2.longValue(),product);
+                                    }
+                                    if(!unaff1.equals(aff1) && !unaff1.equals(aff2)) {
+                                        dt = getTally(unaff1,tempDiscordantCounts);
+                                        dt.tally(aff1.longValue(),aff2.longValue(),unaff1.longValue(),unaff2.longValue(),product);
+                                    }
+                                    if(!unaff2.equals(aff1) && !unaff2.equals(aff2) && !unaff2.equals(unaff1)) {
+                                        dt = getTally(unaff2,tempDiscordantCounts);
+                                        dt.tally(aff1.longValue(),aff2.longValue(),unaff1.longValue(),unaff2.longValue(),product);
+                                    }
+                                }
+                            }
+                        }
+                    }
+
+                    if (tempnorm > 0.00) {
+                        Iterator itr = fullProbMap.getKeySet().iterator();
+                        while(itr.hasNext()) {
+                            Long curHap = (Long) itr.next();
+                            if (tempT.get(curHap) > 0.0000 || tempU.get(curHap) > 0.0000) {
+                                totalT.put(curHap, totalT.get(curHap) + tempT.get(curHap)/tempnorm);
+                                totalU.put(curHap, totalU.get(curHap) + tempU.get(curHap)/tempnorm);
+                            }
+                        }
+
+                        itr = tempDiscordantCounts.keySet().iterator();
+                        while(itr.hasNext()) {
+                            Long key = (Long)itr.next();
+                            DiscordantTally dt = (DiscordantTally) tempDiscordantCounts.get(key);
+                            dt.normalize(tempnorm);
+                            DiscordantTally totalDT = getTally(key,totalDiscordantCounts);
+                            totalDT.combine(dt);
+                        }
+
+                    }
+                }
+            }
+            ArrayList sortedKeySet = new ArrayList(fullProbMap.getKeySet());
+            Collections.sort(sortedKeySet);
+            for (int j = 0; j <sortedKeySet.size(); j++){
+                if (fullProbMap.get(sortedKeySet.get(j)) > .001) {
+                    obsT.add(new Double(totalT.get(sortedKeySet.get(j))));
+                    obsU.add(new Double(totalU.get(sortedKeySet.get(j))));
+                    if(Options.getTdtType() == TDT_PAREN) {
+                        if(totalDiscordantCounts.containsKey(sortedKeySet.get(j))) {
+                            discordantCounts.add(((DiscordantTally)totalDiscordantCounts.get(sortedKeySet.get(j))).getCounts());
+                        }else {
+                            discordantCounts.add(new double[9]);
+                        }
+                    }
+                }
+            }
+        }
+
+        if (Options.getAssocTest() == ASSOC_TRIO){
+            this.obsT = obsT;
+            this.obsU = obsU;
+        } else if (Options.getAssocTest() == ASSOC_CC){
+            this.caseCounts = caseCounts;
+            this.controlCounts = controlCounts;
+        }
+    }
+
+
+
+    private DiscordantTally getTally(Long key,HashMap tallyMap){
+        DiscordantTally dt;
+        if(tallyMap.containsKey(key)) {
+            dt = (DiscordantTally) tallyMap.get(key);
+        }else {
+            dt = new DiscordantTally(key.longValue());
+            tallyMap.put(key,dt);
+        }
+        return dt;
+
+    }
+
+
+    public int read_observations(int num_haplos, int num_loci, byte[][] haplo, int start_locus, int end_locus) throws HaploViewException {
+        //TODO: this should return something useful
+        int i, j, a1, a2,  two_n, num_poss, loc, ind;
+        long h1, h2;
+        byte c1, c2;
+        int currentInd = 0;
+        int[] dhet = new int[MAXLOCI];
+        int[] missing1 = new int[MAXLOCI];
+        int[] missing2 = new int[MAXLOCI];
+        for (i=0; i<MAXLOCI; ++i) {
+            dhet[i]=0;
+            missing1[i]=0;
+            missing2[i]=0;
+        }
+
+        for (ind=0; ind<num_haplos; ind+=2) {
+
+            two_n=1; h1=h2=0; num_poss=1;
+
+            for (loc=start_locus; loc<=end_locus; loc++) {
+                i = loc-start_locus;
+
+                c1=haplo[ind][loc]; c2=haplo[ind+1][loc];
+                if (c1=='h' || c1=='9') {
+                    a1=0; a2=1; dhet[i]=1; missing1[i]=0; missing2[i]=0;
+                } else {
+                    dhet[i]=0; missing1[i]=0; missing2[i]=0;
+                    if (c1 > '0' && c1 < '3') {
+                        a1=c1-'1';
+                    }
+                    else {
+                        a1=0; missing1[i]=1;
+                    }
+
+                    if (c2 > '0' && c2 < '3') {
+                        a2=c2-'1';
+                    }
+                    else {
+                        a2=0;
+                        missing2[i]=1;
+                    }
+                }
+
+                h1 += two_n*a1;
+                h2 += two_n*a2;
+                if (dhet[i]==1) num_poss*=2;
+                if (missing1[i]==1) num_poss*=2;
+                if (missing2[i]==1) num_poss*=2;
+
+                two_n *= 2;
+            }
+
+            Recovery tempRec;
+
+            if(data[currentInd].poss.size() < num_poss)
+            {
+                for(int k=data[currentInd].poss.size();k<num_poss;k++)
+                {
+                    tempRec = new Recovery();
+                    data[currentInd].poss.add(tempRec);
+
+                }
+            }
+
+            data[currentInd].nposs = num_poss;
+            tempRec = (Recovery) data[currentInd].poss.elementAt(0);
+            tempRec.h1=h1;
+            tempRec.h2=h2;
+            tempRec.p=0.0f;
+
+            /*    printf("h1=%s, ",haplo_str(h1));
+            printf("h2=%s, dhet=%d%d%d%d%d, nposs=%d\n",haplo_str(h2),dhet[0],dhet[1],dhet[2],dhet[3],dhet[4],num_poss); */
+
+            two_n=1; num_poss=1;
+            for (i=0; i<=end_locus-start_locus; i++) {
+                if (dhet[i]!=0) {
+                    for (j=0; j<num_poss; j++) {
+                        /* flip bits at this position and call this num_poss+j */
+                        tempRec = (Recovery) data[currentInd].poss.elementAt(j);
+                        h1 = tempRec.h1;
+                        h2 = tempRec.h2;
+                        /* printf("FLIP: position %d, two_n=%d, h1=%d, h2=%d, andh1=%d, andh2=%d\n",i,two_n,h1,h2,h1&two_n,h2&two_n);  */
+                        if ((h1&two_n)==two_n && (h2&two_n)==0) {
+                            h1 -= two_n; h2 += two_n;
+                        } else if ((h1&two_n)==0 && (h2&two_n)==two_n) {
+                            h1 += two_n; h2 -= two_n;
+                        } else {
+                            //printf("error - attepmting to flip homozygous position\n");
+                        }
+                        tempRec = (Recovery) data[currentInd].poss.elementAt(num_poss+j);
+                        tempRec.h1=h1;
+                        tempRec.h2=h2;
+                        tempRec.p=0.0f;
+                    }
+                    num_poss *= 2;
+                }
+
+                if (missing1[i]!=0) {
+                    for (j=0; j<num_poss; j++) {
+                        /* flip bits at this position and call this num_poss+j */
+                        tempRec = (Recovery) data[currentInd].poss.elementAt(j);
+                        h1 = tempRec.h1;
+                        h2 = tempRec.h2;
+                        /* printf("MISS1: position %d, two_n=%d, h1=%d, h2=%d, newh1=%d, newh2=%d\n",i,two_n,h1,h2,h1+two_n,h2); */
+                        if ((h1&two_n)==0) {
+                            h1 += two_n;
+                        } else {
+                            //printf("error - attempting to flip missing !=0\n");
+                        }
+                        tempRec = (Recovery) data[currentInd].poss.elementAt(num_poss+j);
+                        tempRec.h1=h1;
+                        tempRec.h2=h2;
+                        tempRec.p=0.0f;
+                    }
+                    num_poss *= 2;
+                }
+
+                if (missing2[i]!=0) {
+                    for (j=0; j<num_poss; j++) {
+                        /* flip bits at this position and call this num_poss+j */
+                        tempRec = (Recovery) data[currentInd].poss.elementAt(j);
+                        h1 = tempRec.h1;
+                        h2 = tempRec.h2;
+                        /* printf("MISS2: position %d, two_n=%d, h1=%d, h2=%d, newh1=%d, newh2=%d\n",i,two_n,h1,h2,h1,h2+two_n);  */
+                        if ((h2&two_n)==0) {
+                            h2 += two_n;
+                        } else {
+                            //printf("error - attempting to flip missing !=0\n");
+                        }
+                        tempRec = (Recovery) data[currentInd].poss.elementAt(num_poss+j);
+                        tempRec.h1=h1;
+                        tempRec.h2=h2;
+                        tempRec.p=0.0f;
+                    }
+                    num_poss *= 2;
+                }
+
+                two_n *= 2;
+            }
+            /* printf("num_poss = %d  also %d\n",num_poss, data[currentInd].nposs); */
+            currentInd++;
+        }
+
+        return(currentInd);
+    }
+
+
+    public void store_block_haplos(int[][] hlist, double[][] hprob, int[] hint, int block, int num_indivs)
+    {
+        int i, j, k, num_poss,h1,h2;
+        Recovery tempRec;
+
+        for (i=0; i<num_indivs; i++) {
+            num_poss=0;
+            for (j=0; j<data[i].nposs; j++) {
+                tempRec = (Recovery) data[i].poss.elementAt(j);
+                h1 = (int)tempRec.h1;
+                h2 = (int)tempRec.h2;
+                if (hint[h1] >= 0 && hint[h2] >= 0) {
+                    /* valid combination, both haplos passed to 2nd round */
+                    num_poss++;
+                }
+            }
+            /* allocate and store */
+            superdata[i].nposs[block]=num_poss;
+            if (num_poss > 0) {
+                //superdata[i].poss[block] = (Recovery *) malloc (num_poss * sizeof(Recovery));
+                superdata[i].poss[block] = new Recovery[num_poss];
+                for (int ii=0; ii<num_poss; ++ii) superdata[i].poss[block][ii] = new Recovery();
+                k=0;
+                for (j=0; j<data[i].nposs; j++) {
+                    tempRec = (Recovery) data[i].poss.elementAt(j);
+                    h1 = (int)tempRec.h1;
+                    h2 = (int)tempRec.h2;
+                    if (hint[h1] >= 0 && hint[h2] >= 0) {
+                        superdata[i].poss[block][k].h1 = hint[h1];
+                        superdata[i].poss[block][k].h2 = hint[h2];
+                        k++;
+                    }
+                }
+            }
+        }
+    }
+
+    public int[] decode_haplo_str(long chap, int num_blocks, int[] block_size, int[][] hlist, int[] num_hlist)
+    {
+        int i, val,size=0,counter=0;
+        for(i =0;i<num_blocks;i++) {
+            size += block_size[i];
+        }
+        int[] decoded = new int[size];
+
+        for (i=0; i<num_blocks; i++) {
+
+            val = (int) (chap % num_hlist[i]);
+            for(int j=0; j<block_size[i];j++) {
+                if((hlist[i][val] & two_n[j]) == two_n[j]) {
+                    decoded[counter] = 2;
+                }
+                else {
+                    decoded[counter]=1;
+                }
+                counter++;
+            }
+            chap -= val;
+            chap /= num_hlist[i];
+        }
+        return decoded;
+    }
+
+    public void create_super_haplos(int num_indivs, int num_blocks, int[] num_hlist)
+    {
+        //System.out.println("Entering create_super_haplos");
+        int i, j, num_poss, h1, h2;
+
+        for (i=0; i<num_indivs; i++) {
+            num_poss=1;
+            for (j=0; j<num_blocks; j++) {
+                num_poss *= superdata[i].nposs[j];
+            }
+
+            superdata[i].nsuper=0;
+            superdata[i].superposs = new Recovery[num_poss];
+            //System.out.println(num_poss);
+            for (int ii=0; ii<num_poss; ++ii) superdata[i].superposs[ii] = new Recovery();
+
+            /* block 0 */
+            for (j=0; j<superdata[i].nposs[0]; j++) {
+                h1 = (int)superdata[i].poss[0][j].h1;
+                h2 = (int)superdata[i].poss[0][j].h2;
+                recursive_superposs(h1,h2,1,num_blocks,num_hlist,i);
+            }
+
+            if (superdata[i].nsuper != num_poss) {
+                System.out.println("error in superfill" + " " + i);
+            }
+        }
+    }
+
+    public void recursive_superposs(long h1, long h2, int block, int num_blocks, int[] num_hlist, int indiv) {
+        int j;
+        long newh1, newh2, curr_prod;
+
+        if (block == num_blocks) {
+            superdata[indiv].superposs[superdata[indiv].nsuper].h1 = h1;
+            superdata[indiv].superposs[superdata[indiv].nsuper].h2 = h2;
+            superdata[indiv].nsuper++;
+        } else {
+            curr_prod=1;
+            for (j=0; j<block; j++) { curr_prod*=num_hlist[j]; }
+
+            for (j=0; j<superdata[indiv].nposs[block]; j++) {
+                newh1 = h1 + (superdata[indiv].poss[block][j].h1 * curr_prod);
+                newh2 = h2 + (superdata[indiv].poss[block][j].h2 * curr_prod);
+                recursive_superposs(newh1,newh2,block+1,num_blocks,num_hlist,indiv);
+            }
+        }
+    }
+
+    void store_dhet_status(int num_haplos, int num_loci, byte[][] haplo)
+    {
+        int ind, loc;
+        byte c1, c2;
+
+        for (ind=0; ind+2<num_haplos; ind+=4) {
+            for (loc=0; loc<num_loci; loc++) {
+                c1=haplo[ind][loc];
+                c2=haplo[ind+2][loc];
+                ambighet[ind/4][loc]=0;
+
+                if (c1=='h' || c1=='9') {
+                    if (c2=='h' || c2=='9' || c2=='0') {
+                        ambighet[ind/4][loc]=1;
+                    }
+                } else if (c1=='0' && (c2=='h' || c2=='9')) {
+                    ambighet[ind/4][loc]=1;
+                }
+            }
+        }
+    }
+
+    boolean kid_consistent(long chap1, long chap2, int num_blocks, int[] block_size, int[][] hlist, int[] num_hlist, int this_trio, int num_loci)
+    {
+        int i;
+        boolean retval;
+
+        int[] temp1 = decode_haplo_str(chap1,num_blocks,block_size,hlist,num_hlist);
+        int[] temp2 = decode_haplo_str(chap2,num_blocks,block_size,hlist,num_hlist);
+
+        retval=true;
+        for (i=0; i<num_loci; i++) {
+            if (ambighet[this_trio][i] !=0) {
+                if (temp1[i] == temp2[i])
+                {
+                    retval=false;
+                    break;
+                }
+            }
+        }
+        return(retval);
+    }
+
+    public int[][] getHaplotypes() {
+        return haplotypes;
+    }
+
+    public void setHaplotypes(int[][] haplotypes) {
+        this.haplotypes = haplotypes;
+    }
+
+    public double[] getFrequencies() {
+        return frequencies;
+    }
+
+    public void setFrequencies(double[] frequencies) {
+        this.frequencies = frequencies;
+    }
+
+    public double getTransCount(int i) {
+        return ((Double)obsT.elementAt(i)).doubleValue();
+    }
+
+    public void setObsT(Vector obsT) {
+        this.obsT = obsT;
+    }
+
+    public double getUntransCount(int i) {
+        return ((Double)obsU.elementAt(i)).doubleValue();
+    }
+
+    public void setObsU(Vector obsU) {
+        this.obsU = obsU;
+    }
+
+    public double getControlCount(int i) {
+        return ((Double)controlCounts.elementAt(i)).doubleValue();
+    }
+
+    public void setControlCounts(Vector controlCounts) {
+        this.controlCounts = controlCounts;
+    }
+
+    public double getCaseCount(int i) {
+        return ((Double)caseCounts.elementAt(i)).doubleValue();
+    }
+
+    public void setCaseCounts(Vector caseCounts) {
+        this.caseCounts = caseCounts;
+    }
+
+    public int numHaplos(){
+        return haplotypes.length;
+    }
+
+    public double[] getDiscordantCounts(int i){
+        return (double[]) discordantCounts.get(i);
+    }
+}
diff --git a/edu/mit/wi/haploview/ExportDialog.java b/edu/mit/wi/haploview/ExportDialog.java
new file mode 100755
index 0000000..f8c8eb1
--- /dev/null
+++ b/edu/mit/wi/haploview/ExportDialog.java
@@ -0,0 +1,278 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import javax.swing.border.TitledBorder;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.*;
+
+public class ExportDialog extends JDialog implements ActionListener, Constants{
+    static final long serialVersionUID = -5757677926257427884L;
+
+    HaploView hv;
+    JRadioButton dpButton, hapButton, checkButton, taggerButton;
+    JRadioButton singleAssocButton, hapAssocButton, permAssocButton, custAssocButton;
+    JRadioButton txtButton, pngButton, svgButton;
+    JRadioButton allButton, someButton, adjButton;
+    JCheckBox compressCheckBox;
+    NumberTextField lowRange, upperRange;
+
+    public ExportDialog(HaploView h){
+        super(h, "Export Data");
+        hv = h;
+
+        JPanel contents = new JPanel();
+        contents.setLayout(new BoxLayout(contents, BoxLayout.Y_AXIS));
+
+        JPanel tabPanel = new JPanel();
+        int currTab = hv.tabs.getSelectedIndex();
+        tabPanel.setBorder(new TitledBorder("Tab to Export"));
+        tabPanel.setLayout(new BoxLayout(tabPanel,BoxLayout.Y_AXIS));
+        tabPanel.setAlignmentX(CENTER_ALIGNMENT);
+        ButtonGroup g1 = new ButtonGroup();
+        dpButton = new JRadioButton("LD");
+        dpButton.setActionCommand("ldtab");
+        dpButton.addActionListener(this);
+        g1.add(dpButton);
+        tabPanel.add(dpButton);
+        if (currTab == VIEW_D_NUM){
+            dpButton.setSelected(true);
+        }
+        hapButton = new JRadioButton("Haplotypes");
+        hapButton.setActionCommand("haptab");
+        hapButton.addActionListener(this);
+        g1.add(hapButton);
+        tabPanel.add(hapButton);
+        if (currTab == VIEW_HAP_NUM){
+            hapButton.setSelected(true);
+        }
+        if (hv.checkPanel != null){
+            checkButton = new JRadioButton("Data Checks");
+            checkButton.setActionCommand("checktab");
+            checkButton.addActionListener(this);
+            g1.add(checkButton);
+            tabPanel.add(checkButton);
+            if (currTab == VIEW_CHECK_NUM){
+                checkButton.setSelected(true);
+            }
+        }
+        if (Options.getAssocTest() != ASSOC_NONE){
+            singleAssocButton = new JRadioButton("Single Marker Association Tests");
+            singleAssocButton.setActionCommand("assoctab");
+            singleAssocButton.addActionListener(this);
+            g1.add(singleAssocButton);
+            tabPanel.add(singleAssocButton);
+
+            hapAssocButton = new JRadioButton("Haplotype Association Tests");
+            hapAssocButton.setActionCommand("assoctab");
+            hapAssocButton.addActionListener(this);
+            g1.add(hapAssocButton);
+            tabPanel.add(hapAssocButton);
+
+            if (hv.custAssocPanel != null){
+                custAssocButton = new JRadioButton("Custom Association Tests");
+                custAssocButton.setActionCommand("assoctab");
+                custAssocButton.addActionListener(this);
+                g1.add(custAssocButton);
+                tabPanel.add(custAssocButton);
+            }
+
+            permAssocButton = new JRadioButton("Permutation Results");
+            permAssocButton.setActionCommand("assoctab");
+            permAssocButton.addActionListener(this);
+            g1.add(permAssocButton);
+            tabPanel.add(permAssocButton);
+
+            if (currTab == VIEW_ASSOC_NUM){
+                Component c = ((JTabbedPane)((HaploviewTab)hv.tabs.getComponent(currTab)).getComponent(0)).getSelectedComponent();
+                if(c == hv.tdtPanel){
+                    singleAssocButton.setSelected(true);
+                }else if (c == hv.hapAssocPanel){
+                    hapAssocButton.setSelected(true);
+                }else if (c == hv.permutationPanel){
+                    permAssocButton.setSelected(true);
+                }else if (c == hv.custAssocPanel){
+                    custAssocButton.setSelected(true);
+                }
+            }
+        }
+        if (hv.taggerResultsPanel != null){
+            taggerButton = new JRadioButton("Tagger output");
+            taggerButton.setActionCommand("taggertab");
+            taggerButton.addActionListener(this);
+            g1.add(taggerButton);
+            tabPanel.add(taggerButton);
+        }
+
+        contents.add(tabPanel);
+
+        JPanel formatPanel = new JPanel();
+        formatPanel.setBorder(new TitledBorder("Output Format"));
+        ButtonGroup g2 = new ButtonGroup();
+        txtButton = new JRadioButton("Text");
+        txtButton.addActionListener(this);
+        formatPanel.add(txtButton);
+        g2.add(txtButton);
+        txtButton.setSelected(true);
+        pngButton = new JRadioButton("PNG Image");
+        pngButton.addActionListener(this);
+        formatPanel.add(pngButton);
+        g2.add(pngButton);
+        compressCheckBox = new JCheckBox("Compress image (smaller file)");
+        formatPanel.add(compressCheckBox);
+        compressCheckBox.setEnabled(false);
+        svgButton = new JRadioButton("SVG Image");
+        svgButton.addActionListener(this);
+        formatPanel.add(svgButton);
+        g2.add(svgButton);
+        if (currTab == VIEW_CHECK_NUM || currTab == VIEW_ASSOC_NUM){
+            pngButton.setEnabled(false);
+        }
+        contents.add(formatPanel);
+
+        JPanel rangePanel = new JPanel();
+        rangePanel.setBorder(new TitledBorder("Range"));
+        ButtonGroup g3 = new ButtonGroup();
+        allButton = new JRadioButton("All");
+        allButton.addActionListener(this);
+        rangePanel.add(allButton);
+        g3.add(allButton);
+        allButton.setSelected(true);
+        someButton = new JRadioButton("Marker ");
+        someButton.addActionListener(this);
+        rangePanel.add(someButton);
+        g3.add(someButton);
+        lowRange = new NumberTextField("",5,false,false);
+        rangePanel.add(lowRange);
+        rangePanel.add(new JLabel(" to "));
+        upperRange = new NumberTextField("",5,false,false);
+        rangePanel.add(upperRange);
+        upperRange.setEnabled(false);
+        lowRange.setEnabled(false);
+        adjButton = new JRadioButton("Adjacent markers only");
+        adjButton.addActionListener(this);
+        rangePanel.add(adjButton);
+        g3.add(adjButton);
+        if (currTab != VIEW_D_NUM){
+            someButton.setEnabled(false);
+            adjButton.setEnabled(false);
+        }
+        contents.add(rangePanel);
+
+        JPanel choicePanel = new JPanel();
+        JButton okButton = new JButton("OK");
+        okButton.addActionListener(this);
+        choicePanel.add(okButton);
+        JButton cancelButton = new JButton("Cancel");
+        cancelButton.addActionListener(this);
+        choicePanel.add(cancelButton);
+        contents.add(choicePanel);
+
+        this.setContentPane(contents);
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+    }
+
+    public void actionPerformed (ActionEvent e){
+        String command = e.getActionCommand();
+        if (dpButton.isSelected()){
+            if(txtButton.isSelected()){
+                adjButton.setEnabled(true);
+                compressCheckBox.setEnabled(false);
+                someButton.setEnabled(true);
+            }else if (pngButton.isSelected()){
+                compressCheckBox.setEnabled(true);
+                adjButton.setEnabled(false);
+                if (adjButton.isSelected()){
+                    allButton.setSelected(true);
+                }
+                someButton.setEnabled(true);
+            }else{
+                compressCheckBox.setEnabled(false);
+                adjButton.setEnabled(false);
+                allButton.setSelected(true);
+                someButton.setEnabled(false);
+            }
+        }else{
+            compressCheckBox.setEnabled(false);
+            someButton.setEnabled(false);
+            adjButton.setEnabled(false);
+            if (adjButton.isSelected() || someButton.isSelected()){
+                allButton.setSelected(true);
+            }
+        }
+
+        if (someButton.isSelected()){
+            upperRange.setEnabled(true);
+            lowRange.setEnabled(true);
+        }else{
+            upperRange.setEnabled(false);
+            lowRange.setEnabled(false);
+        }
+
+        if (command.equals("ldtab") || command.equals("haptab")){
+            pngButton.setEnabled(true);
+            svgButton.setEnabled(true);
+        }else if (command.equals("checktab") || command.equals("assoctab") || command.equals("taggertab")){
+            pngButton.setEnabled(false);
+            svgButton.setEnabled(false);
+            txtButton.setSelected(true);
+        }else if (command.equals("OK")){
+            int format;
+            if (pngButton.isSelected()){
+                if (compressCheckBox.isSelected()){
+                    format = COMPRESSED_PNG_MODE;
+                }else{
+                    format = PNG_MODE;
+                }
+            }else if (txtButton.isSelected()){
+                format = TXT_MODE;
+            }else{
+                format = SVG_MODE;
+            }
+
+            Component c = null;
+
+            if (dpButton.isSelected()){
+                c = hv.dPrimeDisplay;
+            }else if (hapButton.isSelected()){
+                c = hv.hapDisplay;
+            }else if (checkButton != null && checkButton.isSelected()){
+                c = hv.checkPanel;
+            }else if (singleAssocButton != null && singleAssocButton.isSelected()){
+                c = hv.tdtPanel;
+            }else if (hapAssocButton != null && hapAssocButton.isSelected()){
+                c = hv.hapAssocPanel;
+            }else if (permAssocButton != null && permAssocButton.isSelected()){
+                c = hv.permutationPanel;
+            }else if (custAssocButton != null && custAssocButton.isSelected()){
+                c = hv.custAssocPanel;
+            }else if (taggerButton != null && taggerButton.isSelected()){
+                c = hv.taggerResultsPanel;
+            }
+            this.dispose();
+
+            if (allButton.isSelected()){
+                hv.export(c,format,0,Chromosome.getUnfilteredSize());
+            }else if (someButton.isSelected()){
+                try{
+                    hv.export(c,format,Integer.parseInt(lowRange.getText())-1, Integer.parseInt(upperRange.getText()));
+                }catch (NumberFormatException nfe){
+                    JOptionPane.showMessageDialog(hv,
+                            "Invalid marker range: " + lowRange.getText() + " - " + upperRange.getText(),
+                            "Export Error",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+
+            }else{
+                hv.export(c,format,-1,-1);
+            }
+
+        }else if (command.equals("Cancel")){
+            this.dispose();
+        }
+    }
+
+
+}
diff --git a/edu/mit/wi/haploview/FilteredIndividualsDialog.java b/edu/mit/wi/haploview/FilteredIndividualsDialog.java
new file mode 100755
index 0000000..38fdc6a
--- /dev/null
+++ b/edu/mit/wi/haploview/FilteredIndividualsDialog.java
@@ -0,0 +1,136 @@
+package edu.mit.wi.haploview;
+
+import edu.mit.wi.pedfile.Individual;
+import javax.swing.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.*;
+import java.util.Vector;
+import java.io.File;
+import java.io.IOException;
+import java.io.FileWriter;
+
+
+public class FilteredIndividualsDialog extends JDialog implements ActionListener, Constants{
+    static final long serialVersionUID = 3561316987491286230L;
+    private BasicTableModel tableModel;
+
+    public FilteredIndividualsDialog(HaploView h, String title) {
+        super(h,title);
+
+        JTable table;
+        JPanel contents = new JPanel();
+
+        contents.setLayout(new BoxLayout(contents,BoxLayout.Y_AXIS));
+
+        Vector axedPeople = h.theData.getPedFile().getAxedPeople();
+
+        Vector colNames = new Vector();
+        colNames.add("FamilyID");
+        colNames.add("IndividualID");
+        colNames.add("Reason");
+        Vector data = new Vector();
+
+        for(int i=0;i<axedPeople.size();i++) {
+            Vector tmpVec = new Vector();
+            Individual currentInd = (Individual) axedPeople.get(i);
+            tmpVec.add(currentInd.getFamilyID());
+            tmpVec.add(currentInd.getIndividualID());
+            tmpVec.add(currentInd.getReasonImAxed());
+            data.add(tmpVec);
+        }
+
+        tableModel = new BasicTableModel(colNames,data);
+        TableSorter sorter = new TableSorter(tableModel);
+        table = new JTable(sorter);
+        sorter.setTableHeader(table.getTableHeader());
+        table.getColumnModel().getColumn(0).setPreferredWidth(120);
+        table.getColumnModel().getColumn(1).setPreferredWidth(120);
+        table.getColumnModel().getColumn(2).setPreferredWidth(240);
+
+        JScrollPane tableScroller = new JScrollPane(table);
+        int tableHeight = (table.getRowHeight()+table.getRowMargin())*(table.getRowCount()+2);
+        if (tableHeight > 300){
+            tableScroller.setPreferredSize(new Dimension(400, 300));
+        }else{
+            tableScroller.setPreferredSize(new Dimension(400, tableHeight));
+        }
+        tableScroller.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
+
+        contents.add(tableScroller);
+
+        JPanel buttonPanel = new JPanel();
+        JButton exportButton = new JButton("Export to File");
+        exportButton.addActionListener(this);
+        JButton okButton = new JButton("Close");
+        okButton.addActionListener(this);
+        buttonPanel.add(exportButton);
+        buttonPanel.add(okButton);
+        contents.add(buttonPanel);
+        setContentPane(contents);
+
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+    }
+
+    public void printTable(File outfile) throws IOException {
+        FileWriter checkWriter = null;
+        if (outfile != null){
+            checkWriter = new FileWriter(outfile);
+        }
+
+        int numCols = tableModel.getColumnCount();
+        StringBuffer header = new StringBuffer();
+        for (int i = 0; i < numCols; i++){
+            header.append(tableModel.getColumnName(i)).append("\t");
+        }
+        header.append("\n");
+
+        if (outfile != null){
+            checkWriter.write(header.toString());
+        }else{
+            System.out.print(header.toString());
+        }
+        for (int i = 0; i < tableModel.getRowCount(); i++){
+            StringBuffer sb = new StringBuffer();
+            for (int j = 0; j < numCols; j++){
+                sb.append(tableModel.getValueAt(i,j)).append("\t");
+            }
+            sb.append("\n");
+
+            if (outfile != null){
+                checkWriter.write(sb.toString());
+            }else{
+                System.out.print(sb.toString());
+            }
+        }
+        if (outfile != null){
+            checkWriter.close();
+        }
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if(command.equals("Close")) {
+            this.dispose();
+        }else if (command.equals("Export to File")){
+            HaploView.fc.setSelectedFile(new File(""));
+
+            if (HaploView.fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION){
+                File file = HaploView.fc.getSelectedFile();
+                try{
+                    printTable(file);
+                }catch(IOException ioe){
+                    JOptionPane.showMessageDialog(this,
+                            ioe.getMessage(),
+                            "Error",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+            }
+        }
+    }
+}
+
+
+
diff --git a/edu/mit/wi/haploview/FindBlocks.java b/edu/mit/wi/haploview/FindBlocks.java
new file mode 100755
index 0000000..c40ba1a
--- /dev/null
+++ b/edu/mit/wi/haploview/FindBlocks.java
@@ -0,0 +1,335 @@
+package edu.mit.wi.haploview;
+
+import java.util.*;
+
+public class FindBlocks {
+    static double cutHighCI = 0.98;
+    static double cutLowCI = 0.70;
+    static double mafThresh = 0.05;
+    static double[] cutLowCIVar = {0,0,0.80,0.50,0.50};
+    static double[] maxDist = {0,0,20000,30000,1000000};
+    static double recHighCI = 0.90;
+    static double informFrac = 0.95;
+    static double fourGameteCutoff = 0.01;
+    static double spineDP = 0.80;
+
+    static Vector do4Gamete(DPrimeTable dPrime){
+        Vector blocks = new Vector();
+        Vector strongPairs = new Vector();
+
+        //first make a list of marker pairs with < 4 gametes, sorted by distance apart
+        for (int x = 0; x < Chromosome.getSize()-1; x++){
+            for (int y = x+1; y < Chromosome.getSize(); y++){
+                PairwiseLinkage thisPair = dPrime.getLDStats(x,y);
+                if (thisPair == null) {
+                    continue;
+                }
+
+                double[] freqs = thisPair.getFreqs();
+                int numGam = 0;
+                for (int i = 0; i < freqs.length; i++){
+                    if (freqs[i] > fourGameteCutoff + 1E-8) numGam++;
+                }
+
+                if (numGam > 3){ continue; }
+
+                int sep = y - x - 1; //compute separation of two markers
+                strongPairs.add(new MarkerInfo(x,y,sep));
+            }
+        }
+
+        Collections.sort(strongPairs);
+        //sort from greatest to least
+        Collections.reverse(strongPairs);
+
+        //now take this list of pairs with 3 gametes and construct blocks
+        boolean[] usedInBlock = new boolean[Chromosome.getSize() + 1];
+        for (int v= 0; v< strongPairs.size();v++) {
+            boolean isABlock = true;
+            int first =((MarkerInfo)strongPairs.elementAt(v)).marker1;
+            int last =((MarkerInfo)strongPairs.elementAt(v)).marker2;
+            //first see if this block overlaps with another:
+            if (usedInBlock[first] || usedInBlock[last]) continue;
+            //test this block.
+            OUTER:
+            for (int y = first+1; y <= last; y++){
+                //loop over columns in row y
+                for (int x = first; x < y; x++){
+
+                    PairwiseLinkage thisPair = dPrime.getLDStats(x,y);
+                    if(thisPair == null){
+                        continue;
+                    }
+
+                    double[] freqs = thisPair.getFreqs();
+                    int numGam = 0;
+                    for (int i = 0; i < freqs.length; i++){
+                        if (freqs[i] > fourGameteCutoff + 1E-8) numGam++;
+                    }
+                    if (numGam > 3){
+                        isABlock = false;
+                        break OUTER;
+                    }
+                }
+            }
+            if (isABlock){
+                //add to the block list, but in order by first marker number:
+                if (blocks.size() == 0){ //put first block first
+                    blocks.add(first + " " + last);
+                }else{
+                    //sort by ascending separation of markers in each pair
+                    boolean placed = false;
+                    for (int b = 0; b < blocks.size(); b ++){
+                        StringTokenizer st = new StringTokenizer((String)blocks.elementAt(b));
+                        if (first < Integer.parseInt(st.nextToken())){
+                            blocks.insertElementAt(first + " " + last, b);
+                            placed = true;
+                            break;
+                        }
+                    }
+                    //make sure to put in blocks which fall on the tail end
+                    if (!placed) blocks.add(first + " " + last);
+                }
+                for (int used = first; used <= last; used++){
+                    usedInBlock[used] = true;
+                }
+            }
+        }
+        return stringVec2intVec(blocks);
+    }
+
+    static Vector doGabriel(DPrimeTable dPrime){
+        int numStrong = 0; int numRec = 0; int numInGroup = 0;
+        Vector blocks = new Vector();
+        Vector strongPairs = new Vector();
+
+        //first set up a filter of markers which fail the MAF threshhold
+        boolean[] skipMarker = new boolean[Chromosome.getSize()];
+        for (int x = 0; x < Chromosome.getSize(); x++){
+            if (Chromosome.getMarker(x).getMAF() < mafThresh){
+                skipMarker[x]=true;
+            }else{
+                skipMarker[x]=false;
+            }
+        }
+
+        //next make a list of marker pairs in "strong LD", sorted by distance apart
+        for (int x = 0; x < Chromosome.getSize()-1; x++){
+            for (int y = x+1; y < Chromosome.getSize(); y++){
+                PairwiseLinkage thisPair = dPrime.getLDStats(x,y);
+                if (thisPair == null){
+                        continue;
+                }
+                //get the right bits
+                double lod = thisPair.getLOD();
+                double lowCI = thisPair.getConfidenceLow();
+                double highCI = thisPair.getConfidenceHigh();
+
+                if (skipMarker[x] || skipMarker[y]) continue;
+                if (lod < -90) continue; //missing data
+                if (highCI < cutHighCI || lowCI < cutLowCI) continue; //must pass "strong LD" test
+
+
+                long sep;
+                //compute actual separation
+                sep = Math.abs(Chromosome.getMarker(y).getPosition() - Chromosome.getMarker(x).getPosition());
+               strongPairs.add(new MarkerInfo(x,y,sep));
+            }
+        }
+
+        Collections.sort(strongPairs);
+        Collections.reverse(strongPairs);
+
+        //now take this list of pairs with "strong LD" and construct blocks
+        boolean[] usedInBlock = new boolean[Chromosome.getSize() + 1];
+        Vector thisBlock;
+        int[] blockArray;
+
+        for(int v =0; v< strongPairs.size();v++) {
+            numStrong = 0; numRec = 0; numInGroup = 0;
+            thisBlock = new Vector();
+            int first =((MarkerInfo)strongPairs.elementAt(v)).marker1;
+            int last =((MarkerInfo)strongPairs.elementAt(v)).marker2;
+            long sep = ((MarkerInfo)strongPairs.elementAt(v)).sep.longValue();
+
+            //first see if this block overlaps with another:
+            if (usedInBlock[first] || usedInBlock[last]) continue;
+
+            //next, count the number of markers in the block.
+            for (int x = first; x <=last ; x++){
+                if(!skipMarker[x]) numInGroup++;
+            }
+
+            //skip it if it is too long in bases for it's size in markers
+            if (numInGroup < 4 && sep > maxDist[numInGroup]) continue;
+
+            thisBlock.add(new Integer(first));
+            //test this block. requires 95% of informative markers to be "strong"
+            for (int y = first+1; y <= last; y++){
+                if (skipMarker[y]) continue;
+                thisBlock.add(new Integer(y));
+                //loop over columns in row y
+                for (int x = first; x < y; x++){
+                    if (skipMarker[x]) continue;
+
+                    PairwiseLinkage thisPair = dPrime.getLDStats(x,y);
+                    if (thisPair == null){
+                        continue;
+                    }
+                    //get the right bits
+                    double lod = thisPair.getLOD();
+                    double lowCI = thisPair.getConfidenceLow();
+                    double highCI = thisPair.getConfidenceHigh();
+                    if (lod < -90) continue;   //monomorphic marker error
+                    if (lod == 0 && lowCI == 0 && highCI == 0) continue; //skip bad markers
+
+                    //for small blocks use different CI cutoffs
+                    if (numInGroup < 5){
+                        if (lowCI > cutLowCIVar[numInGroup] && highCI >= cutHighCI) numStrong++;
+                    }else{
+                        if (lowCI > cutLowCI && highCI >= cutHighCI) numStrong++; //strong LD
+                    }
+                    if (highCI < recHighCI) numRec++; //recombination
+                }
+            }
+
+            //change the definition somewhat for small blocks
+            if (numInGroup > 3){
+                if (numStrong + numRec < 6) continue;
+            }else if (numInGroup > 2){
+                if (numStrong + numRec < 3) continue;
+            }else{
+                if (numStrong + numRec < 1) continue;
+            }
+
+            blockArray = new int[thisBlock.size()];
+            for (int z = 0; z < thisBlock.size(); z++){
+                blockArray[z] = ((Integer)thisBlock.elementAt(z)).intValue();
+            }
+            //System.out.println(first + " " + last + " " + numStrong + " " + numRec);
+            if ((double)numStrong/(double)(numStrong + numRec) > informFrac){ //this qualifies as a block
+                //add to the block list, but in order by first marker number:
+                if (blocks.size() == 0){ //put first block first
+                    blocks.add(blockArray);
+                }else{
+                    //sort by ascending separation of markers in each pair
+                    boolean placed = false;
+                    for (int b = 0; b < blocks.size(); b ++){
+                        if (first < ((int[])blocks.elementAt(b))[0]){
+                            blocks.insertElementAt(blockArray, b);
+                            placed = true;
+                            break;
+                        }
+                    }
+                    //make sure to put in blocks which fall on the tail end
+                    if (!placed) blocks.add(blockArray);
+                }
+                for (int used = first; used <= last; used++){
+                    usedInBlock[used] = true;
+                }
+            }
+        }
+        return blocks;
+    }
+
+    static Vector  doSpine(DPrimeTable dPrime){
+        // find blocks by searching for stretches between two markers A,B where
+        // D prime is > a threshold for all informative combinations of A, (A+1...B)
+
+        int baddies;
+        int verticalExtent=0;
+        int horizontalExtent=0;
+        Vector blocks = new Vector();
+        for (int i = 0; i < Chromosome.getSize(); i++){
+            baddies=0;
+            //find how far LD from marker i extends
+            for (int j = i+1; j < i + dPrime.getLength(i); j++){
+                PairwiseLinkage thisPair = dPrime.getLDStats(i,j);
+                if (thisPair == null){
+                    continue;
+                }
+
+                //LD extends if D' > threshold
+                if (thisPair.getDPrime() < spineDP){
+                    //LD extends through one 'bad' marker
+                    if (baddies < 1){
+                        baddies++;
+                    } else {
+                        verticalExtent = j-1;
+                        break;
+                    }
+                }
+                verticalExtent=j;
+            }
+            //now we need to find a stretch of LD of all markers between i and j
+            //start with the longest possible block of LD and work backwards to find
+            //one which is good
+            for (int m = verticalExtent; m > i; m--){
+                for (int k = i; k < m; k++){
+
+                    PairwiseLinkage thisPair = dPrime.getLDStats(k,m);
+                    if (thisPair == null){
+                        continue;
+                    }
+
+                    if(thisPair.getDPrime() < spineDP){
+                        if (baddies < 1){
+                            baddies++;
+                        } else {
+                            break;
+                        }
+                    }
+                    horizontalExtent=k+1;
+                }
+                //is this a block of LD?
+
+                //previously, this algorithm was more complex and made some calls better
+                //but caused major problems in others. since the guessing is somewhat
+                //arbitrary, this new and simple method is fine.
+
+                if(horizontalExtent == m){
+                    blocks.add(i + " " + m);
+                    i=m;
+                }
+            }
+        }
+        return stringVec2intVec(blocks);
+    }
+
+    static Vector stringVec2intVec(Vector inVec){
+        //instead of strings with starting and ending positions convert blocks
+        //to int arrays
+
+        Vector outVec = new Vector();
+        for (int i = 0; i < inVec.size(); i++){
+            StringTokenizer st = new StringTokenizer((String)inVec.elementAt(i));
+            int start = Integer.parseInt(st.nextToken()); int fin = Integer.parseInt(st.nextToken());
+            int[] ma = new int[(fin-start)+1];
+            for (int j = start; j <= fin; j++){
+                ma[j-start]=j;
+            }
+            outVec.add(ma);
+        }
+        return outVec;
+    }
+
+    static class MarkerInfo implements Comparable{
+        int marker1;
+        int marker2;
+        Long sep;
+
+        public MarkerInfo(int x, int y, long s)  {
+            marker1 = x;
+            marker2 = y;
+            sep = new Long(s);
+        }
+
+        public int compareTo(Object o) {
+            if(!(o instanceof MarkerInfo)) {
+                throw new ClassCastException();
+            }
+            return sep.compareTo(((MarkerInfo)o).sep);
+        }
+    }
+}
+
diff --git a/edu/mit/wi/haploview/Functions.java b/edu/mit/wi/haploview/Functions.java
new file mode 100755
index 0000000..d2f52ac
--- /dev/null
+++ b/edu/mit/wi/haploview/Functions.java
@@ -0,0 +1,75 @@
+package edu.mit.wi.haploview;
+
+/** * @(#)Functions.java * * Copyright (c) 2000 by Sundar Dorai-Raj
+ * * @author Sundar Dorai-Raj
+ * * Email: sdoraira at vt.edu
+ * * 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,
+ * * provided that any use properly credits the author.
+ * * 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 at http://www.gnu.org * * */
+
+public class Functions {
+    public static double lnfgamma(double c) {
+        int j;
+        double x,y,tmp,ser;
+        double [] cof = {76.18009172947146     ,-86.50532032941677 ,
+                24.01409824083091     ,-1.231739572450155 ,
+                0.1208650973866179e-2,-0.5395239384953e-5};
+        y = x = c;
+        tmp = x + 5.5 - (x + 0.5) * Math.log(x + 5.5);
+        ser = 1.000000000190015;
+        for (j=0;j<=5;j++)
+            ser += (cof[j] / ++y);
+        return(Math.log(2.5066282746310005 * ser / x) - tmp);
+    }
+
+    public static double lnfbeta(double a,double b) {
+        return(lnfgamma(a)+lnfgamma(b)-lnfgamma(a+b));
+    }
+
+    public static double fbeta(double a,double b) {
+        return Math.exp(lnfbeta(a,b));
+    }
+
+    public static double fgamma(double c) {
+        return Math.exp(lnfgamma(c));
+    }
+
+    public static double fact (int n) {
+        return Math.exp(lnfgamma(n+1));
+    }
+
+    public static double lnfact(int n) {
+        return lnfgamma(n+1);
+    }
+
+    public static double nCr(int n,int r) {
+        return Math.exp(lnfact(n)-lnfact(r)-lnfact(n-r));
+    }
+
+    public static double nPr(int n,int r) {
+        return Math.exp(lnfact(n)-lnfact(r));
+    }
+
+    public static double[] sort(double[] x) {
+        int n=x.length,incr=n/2;
+        while (incr >= 1) {
+            for (int i=incr;i<n;i++) {
+                double temp=x[i];
+                int j=i;
+                while (j>=incr && temp<x[j-incr]) {
+                    x[j]=x[j-incr];
+                    j-=incr;
+                }
+                x[j]=temp;
+            }
+            incr/=2;
+        }
+        return(x);
+    }
+}
\ No newline at end of file
diff --git a/edu/mit/wi/haploview/GBrowseDialog.java b/edu/mit/wi/haploview/GBrowseDialog.java
new file mode 100755
index 0000000..15a03a5
--- /dev/null
+++ b/edu/mit/wi/haploview/GBrowseDialog.java
@@ -0,0 +1,123 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.*;
+
+
+public class GBrowseDialog extends JDialog implements ActionListener, Constants{
+    static final long serialVersionUID = -5609547352693579388L;
+
+    JComboBox cbox,buildBox;
+    JTextField minField, maxField;
+    HaploView hv;
+
+    public GBrowseDialog(HaploView h, String title){
+        super (h,title);
+
+        hv = h;
+
+        JPanel contents = new JPanel();
+        contents.setLayout(new BoxLayout(contents, BoxLayout.Y_AXIS));
+
+        JPanel chromPanel = new JPanel();
+        chromPanel.add(new JLabel("Chromosome"));
+        String[] c = {"1", "2", "3", "4", "5", "6", "7",
+                    "8", "9", "10", "11", "12", "13", "14",
+                    "15", "16", "17", "18", "19", "20", "21", "22", "X", "Y"};
+        cbox = new JComboBox(c);
+        if (Chromosome.getDataChrom() != null){
+            String which = Chromosome.getDataChrom().substring(3);
+            for (int i = 0; i < c.length; i++){
+                if (which.equalsIgnoreCase(c[i])){
+                    cbox.setSelectedIndex(i);
+                }
+            }
+        }
+
+        if (Chromosome.getDataChrom().equalsIgnoreCase("chrp")){
+            cbox.setSelectedIndex(22);
+        }
+        chromPanel.add(cbox);
+        contents.add(chromPanel);
+
+        JPanel boundsPanel = new JPanel();
+        boundsPanel.add(new JLabel("from"));
+        minField = new JTextField(Long.toString(Chromosome.getMarker(0).getPosition()), 9);
+        boundsPanel.add(minField);
+        boundsPanel.add(new JLabel("  to"));
+        maxField = new JTextField(Long.toString(Chromosome.getMarker(Chromosome.getSize()-1).getPosition()), 9);
+        boundsPanel.add(maxField);
+        contents.add(boundsPanel);
+
+        JPanel buildPanel = new JPanel();
+        buildPanel.add(new JLabel("Genome build: "));
+        String[]b ={"34","35","36"};
+        buildBox = new JComboBox(b);
+        buildBox.setSelectedIndex(2);
+        buildPanel.add(buildBox);
+        contents.add(buildPanel);
+
+        JPanel buttonPanel = new JPanel();
+        JButton cancelBut = new JButton("Cancel");
+        cancelBut.addActionListener(this);
+        JButton okBut = new JButton("OK");
+        okBut.addActionListener(this);
+        buttonPanel.add(okBut);
+        buttonPanel.add(cancelBut);
+        contents.add(buttonPanel);
+        this.getRootPane().setDefaultButton(okBut);
+
+        contents.add(new JLabel("(Note: this option requires an internet connection)"));
+
+        this.setContentPane(contents);
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if (command.equals("OK")){
+            try{
+                long minpos = Long.parseLong(minField.getText());
+                long maxpos = Long.parseLong(maxField.getText());
+                if (maxpos <= minpos){
+                    throw new HaploViewException("Boundary positions out of order.");
+                }
+                Chromosome.setDataChrom("chr"+cbox.getSelectedItem());
+                Options.setgBrowseLeft(minpos);
+                Options.setgBrowseRight(maxpos);
+                Chromosome.setDataBuild("ncbi_b"+buildBox.getSelectedItem());
+                Options.setShowGBrowse(true);
+                hv.gbEditItem.setEnabled(true);
+                this.dispose();
+
+                hv.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+                javax.swing.SwingUtilities.invokeLater(new Runnable() {
+                     public void run() {
+                         hv.dPrimeDisplay.computePreferredSize();
+                         if (hv.dPrimeDisplay != null && hv.tabs.getSelectedIndex() == VIEW_D_NUM){
+                             hv.dPrimeDisplay.repaint();
+                         }
+                         hv.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+                     }
+                 });
+            }catch (NumberFormatException nfe){
+                JOptionPane.showMessageDialog(this,
+                    "Boundary positions formatted incorrectly",
+                    "Input Error",
+                    JOptionPane.ERROR_MESSAGE);
+            }catch (HaploViewException hve){
+                JOptionPane.showMessageDialog(this,
+                    hve.getMessage(),
+                    "Input Error",
+                    JOptionPane.ERROR_MESSAGE);
+            }
+
+        }else if (command.equals("Cancel")){
+            this.dispose();
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/GBrowseOptionDialog.java b/edu/mit/wi/haploview/GBrowseOptionDialog.java
new file mode 100755
index 0000000..ba6f89c
--- /dev/null
+++ b/edu/mit/wi/haploview/GBrowseOptionDialog.java
@@ -0,0 +1,87 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.*;
+
+public class GBrowseOptionDialog extends JDialog implements ActionListener, Constants{
+    static final long serialVersionUID = 5957877094086811214L;
+    HaploView hv;
+    JCheckBox[] optCheckBoxes;
+
+    public GBrowseOptionDialog(HaploView h, String title){
+        super(h,title);
+        hv = h;
+
+        JPanel contents = new JPanel();
+        contents.setLayout(new BoxLayout(contents, BoxLayout.Y_AXIS));
+
+        contents.add(new JLabel("Select tracks to display in HapMap Info image:"));
+
+        String[] opts = GB_OPTS_NAMES;
+        String curOpts = Options.getgBrowseTypes();
+        optCheckBoxes = new JCheckBox[opts.length];
+        for (int i = 0; i < opts.length; i++){
+            optCheckBoxes[i] = new JCheckBox(opts[i]);
+            contents.add(optCheckBoxes[i]);
+            if (curOpts.indexOf(GB_TYPES[i]) != -1){
+                optCheckBoxes[i].setSelected(true);
+            }
+        }
+
+        JPanel buttonPanel = new JPanel();
+        JButton cancelBut = new JButton("Cancel");
+        cancelBut.addActionListener(this);
+        JButton okBut = new JButton("OK");
+        okBut.addActionListener(this);
+        buttonPanel.add(okBut);
+        buttonPanel.add(cancelBut);
+        contents.add(buttonPanel);
+        this.getRootPane().setDefaultButton(okBut);
+
+        this.setContentPane(contents);
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if (command.equals("OK")){
+            StringBuffer osb = new StringBuffer();
+            StringBuffer tsb = new StringBuffer();
+            for (int i = 0; i < optCheckBoxes.length; i++){
+                if (optCheckBoxes[i].isSelected()){
+                    osb.append(GB_OPTS[i]+"+");
+                    tsb.append(GB_TYPES[i]+"+");
+                }
+            }
+
+            //strip the trailing plus signs
+            while (osb.length() > 0 && osb.substring(osb.length()-1).equals("+")){
+                osb.deleteCharAt(osb.length()-1);
+            }
+            while (tsb.length() > 0 && tsb.substring(tsb.length()-1).equals("+")){
+                tsb.deleteCharAt(tsb.length()-1);
+            }
+
+            Options.setgBrowseOpts(osb.toString());
+            Options.setgBrowseTypes(tsb.toString());
+            this.dispose();
+
+            hv.setCursor(new Cursor(Cursor.WAIT_CURSOR));
+            javax.swing.SwingUtilities.invokeLater(new Runnable() {
+                public void run() {
+                    hv.dPrimeDisplay.computePreferredSize();
+                    if (hv.dPrimeDisplay != null && hv.tabs.getSelectedIndex() == VIEW_D_NUM){
+                        hv.dPrimeDisplay.repaint();
+                    }
+                    hv.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+                }
+            });
+        }else if (command.equals("Cancel")){
+            this.dispose();
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/GeneCruiser.java b/edu/mit/wi/haploview/GeneCruiser.java
new file mode 100755
index 0000000..1e1d97b
--- /dev/null
+++ b/edu/mit/wi/haploview/GeneCruiser.java
@@ -0,0 +1,190 @@
+package edu.mit.wi.haploview;
+
+import org.apache.axiom.om.OMElement;
+import org.apache.axiom.om.OMNode;
+import org.apache.axiom.om.impl.llom.OMNavigator;
+import org.apache.axiom.om.impl.builder.StAXOMBuilder;
+import org.apache.log4j.Logger;
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.Level;
+
+import java.io.*;
+import java.util.Iterator;
+import java.net.URL;
+import javax.xml.stream.*;
+
+/**
+ * Connects with the Genecruiser server via HTTP request for obtaining an XML file with relevant data. Also parses said XML data.
+ * @author Jesse Whitworth
+ */
+
+public class GeneCruiser {
+
+    //Default settings
+    int chromMark, startMark, endMark;
+    boolean chromCollected, startCollected, endCollected;
+
+    String origNamespace = "http://service.genecruiser.org/xsd";
+    String dataNamespace = "service.genecruiser.org";
+    String host = "genecruiser.broad.mit.edu/genecruiser3_services";
+    String firstResult = "0";
+    String email = "haploview at broad.mit.edu";
+    Logger logger = Logger.getRootLogger();
+
+
+    /**
+     * Gets the GeneCruiser data and returns it as an array of ints.
+     * @param selectType Type of query, validated with if statements
+     * @param inputId String literal of the user's query
+     * @return Int[] containing [chromosome, start position, end position]
+     * @throws HaploViewException If there are problems with data collection
+     */
+
+    public int[] getData(int selectType, String inputId)throws HaploViewException {
+
+        BasicConfigurator.configure();
+        logger.setLevel(Level.OFF);
+        String address;
+
+        //Make sure that the user has put in at least some request.
+        if (inputId.length() > 0){
+
+                if (selectType == 0){//For ENSMBL Searches        ENSG00000114784
+                    address = "http://" + host + "/rest/variation/byGenomicId?idType=ensembl_gene_stable_id&id=" + inputId + "&firstResult=" + firstResult + "&email=" + email;
+
+                }else if (selectType == 1){  //For HUGO Searches      1100
+                    address = "http://" + host + "/rest/variation/byGenomicId?idType=HUGO&id=" + inputId + "&firstResult=" + firstResult + "&email=" + email;
+
+                }else if (selectType ==2){//For SNP Searches    rs5004340
+                    address = "http://" + host + "/rest/variation/byName?name=" + inputId + "&firstResult=" + firstResult + "&email=" + email;
+
+                }else{
+                    throw new HaploViewException("Please select an Id Type for Genecruiser");
+                }
+            //System.out.println(address);
+            try {
+
+                //Initialize the URL connection
+                URL inURL = new URL(address);
+                inURL.openConnection();
+
+                //Load the XML file into the parser
+                XMLStreamReader parser =
+                        XMLInputFactory.newInstance().createXMLStreamReader(new InputStreamReader(inURL.openStream()));
+                StAXOMBuilder builder = new StAXOMBuilder(parser);
+
+                //Create the Document and iterate it.
+                OMElement docElement =  builder.getDocumentElement();
+
+                OMNavigator navigator = new OMNavigator(docElement);
+                OMNode node;
+                OMElement nodeElements;
+
+                while (navigator.isNavigable()) {
+
+                    node = navigator.next();
+                    //Check to make sure that the node can be converted to an element
+                    if (node.getType() == 1){
+                        nodeElements = (OMElement)node;
+                        Iterator iter = nodeElements.getChildren();
+                        NamespaceNav(iter);
+                    }
+                }
+            }catch(IOException ioe){
+                throw new HaploViewException("Error connecting to GeneCruiser database.");
+            }catch(XMLStreamException xmls){
+                throw new HaploViewException("Error reading GeneCruiser data.");
+            }
+
+            //Check to make sure that everything has been located
+            if (chromCollected &&
+                    startCollected &&
+                    endCollected){
+                return new int[]{chromMark, startMark, endMark};
+            }
+        }else{
+            throw new HaploViewException("Please enter a search query");
+        }
+        throw new HaploViewException("Could not locate the requested information");
+    }
+
+    /**
+     * Navigates through the XML tree, allowing for long XML files to be handled correctly
+     * Created to be a recursive function, very large XML docs may take some time.
+     * @param iter A series of nodes that need to be navigated and validated
+     * @throws HaploViewException If there are problems with data collection
+     */
+
+    public void NamespaceNav(Iterator iter) throws HaploViewException{
+
+        OMElement tempNode;
+        OMNode tempNoder;
+        Iterator tempIter;
+        String childValue;
+
+        while (iter.hasNext()) {
+
+            tempNoder = (OMNode)iter.next();
+
+            //Validate that the node is of a useable type, if not 1 then the tree is not fully broken down
+            if(tempNoder.getType()==1){
+                tempNode = (OMElement) tempNoder;
+                tempIter = tempNode.getChildElements();
+
+                int tempInt;
+
+                //Check to see that this is section comes from GeneCrusier
+                if (tempNode.getNamespace().getNamespaceURI().equalsIgnoreCase(dataNamespace)){
+
+                    childValue = tempNode.getText();
+
+                    //Check the node to learn what data it contains
+                    if (tempNode.getLocalName().equalsIgnoreCase("chromosome")){
+
+                        if (childValue.equalsIgnoreCase("X")){
+                            chromMark = 23;
+                        }else if (childValue.equalsIgnoreCase("Y")){
+                            chromMark = 24;
+                        }else{
+                            try{
+                                chromMark = Integer.parseInt(childValue.trim());
+                            }catch (NumberFormatException nfe){
+                               throw new HaploViewException("Error reading GeneCruiser data.");
+                            }
+                        }
+
+                        chromCollected = true;
+
+                    }else if (tempNode.getLocalName().equalsIgnoreCase("start")){
+                        try{
+                            tempInt = Integer.parseInt(childValue.trim());
+                        }catch (NumberFormatException nfe){
+                            throw new HaploViewException("Error reading GeneCruiser data.");
+                        }
+                        if (tempInt < startMark){
+                            startMark = tempInt;
+                        }
+                        if(startMark == 0){
+                            startMark = tempInt;
+                        }
+                        startCollected = true;
+
+                    }else if (tempNode.getLocalName().equalsIgnoreCase("end")){
+                        try{
+                            tempInt = Integer.parseInt(childValue.trim());
+                        }catch (NumberFormatException nfe){
+                            throw new HaploViewException("Error reading GeneCruiser data.");
+                        }
+                        if (tempInt > endMark){
+                            endMark = tempInt;
+                        }
+                        endCollected = true;
+                    }
+                }
+                while (tempIter.hasNext()){
+                    NamespaceNav(tempIter);
+                }
+            }
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/HVWrap.java b/edu/mit/wi/haploview/HVWrap.java
new file mode 100755
index 0000000..ae65495
--- /dev/null
+++ b/edu/mit/wi/haploview/HVWrap.java
@@ -0,0 +1,187 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import java.io.*;
+
+class StreamGobbler extends Thread{
+    InputStream is;
+
+    StreamGobbler(InputStream is){
+        this.is = is;
+    }
+
+    public void run(){
+        try{
+            InputStreamReader isr = new InputStreamReader(is);
+            BufferedReader br = new BufferedReader(isr);
+            String line;
+            while ( (line = br.readLine()) != null)
+                System.out.println(line);
+        } catch (IOException ioe){
+            ioe.printStackTrace();
+        }
+    }
+}
+
+public class HVWrap {
+    private static JTextArea errorTextArea;
+    private static JFrame contentFrame;
+
+
+    HVWrap() {}
+
+    public static void main(String[] args) {
+
+        int exitValue = 0;
+        //String dir = System.getProperty("user.dir");
+        //String ver = System.getProperty("java.version");
+        String sep = System.getProperty("file.separator");
+        String javaHome = System.getProperty("java.home");
+
+        //TODO:do some version checking and bitch at people with old JVMs
+        /*StringTokenizer st = new StringTokenizer(ver, ".");
+        while (st.hasMoreTokens()){
+            System.out.println(st.nextToken());
+        } */
+
+        //ugh windows sucks and we need to put quotes around path in case it contains spaces
+        //on the other hand, linux seems displeased with the quoted classpath. sigh.
+        String jarfile;
+        if (System.getProperty("java.class.path").indexOf(" ") > 0 ){
+            jarfile = ("\"");
+            jarfile += System.getProperty("java.class.path");
+            jarfile += "\"";
+        }else{
+            jarfile = System.getProperty("java.class.path");
+        }
+
+
+        String xmxArg = "512";
+
+        String argsToBePassed = "";
+        boolean headless = false;
+        for (int a = 0; a < args.length; a++){
+            if (args[a].equalsIgnoreCase("-memory")){
+                a++;
+                xmxArg = args[a];
+            }else{
+                //put quotes around the argument if it contains spaces
+                if (args[a].indexOf(" ") > 0){
+                    args[a] = "\"" + args[a] + "\"";
+                }
+                argsToBePassed = argsToBePassed.concat(" " + args[a]);
+            }
+            if (args[a].equalsIgnoreCase("-n") || args[a].equalsIgnoreCase("-nogui")){
+                headless=true;
+            }
+
+        }
+
+        try {
+            //if the nogui flag is present we force it into headless mode
+            String runString = javaHome + sep + "bin" + sep +
+                    "java -Dsun.java2d.noddraw=true -Xmx" + xmxArg + "m -classpath " + jarfile;
+            if (headless){
+                runString += " -Djava.awt.headless=true";
+            }
+            runString += " edu.mit.wi.haploview.HaploView"+argsToBePassed;
+            Process child = Runtime.getRuntime().exec(runString);
+
+            //start up a thread to simply pump out all messages to stdout
+            StreamGobbler isg = new StreamGobbler(child.getInputStream());
+            isg.start();
+
+            //while the child is alive we wait for error messages
+            boolean dead = false;
+            StringBuffer errorMsg = new StringBuffer("Fatal Error:\n");
+            BufferedReader besr = new BufferedReader(new InputStreamReader(child.getErrorStream()));
+            String line;
+
+            while ( !dead  && (line = besr.readLine()) != null) {
+                if(line.lastIndexOf("Memory") != -1) {
+                    errorMsg.append(line);
+                    //if the child generated an "Out of Memory" error message, kill it
+                    child.destroy();
+                    dead = true;
+                }else {
+                    //for any other errors we show them to the user
+                    if(headless) {
+                        //if were in headless (command line) mode, then print the error text to command line
+                        System.err.println(line);
+                    } else {
+                        //otherwise print it to the error textarea
+                        if(errorTextArea == null) {
+                            //if this is the first error line then we need to create the JFrame with the
+                            //text area
+                            javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
+                                public void run() {
+                                    createAndShowGUI();
+                                }
+                            });
+                        }
+                        //if the user closed the contentFrame, then we want to reopen it when theres error text
+                        if(!contentFrame.isVisible()) {
+                            contentFrame.setVisible(true);
+                        }
+
+                        errorTextArea.append(line + "\n");
+                        errorTextArea.setCaretPosition(errorTextArea.getDocument().getLength());
+                    }
+                }
+            }
+            final String realErrorMsg = errorMsg.toString();
+
+            //if the child died painfully throw up R.I.P. dialog
+            if (dead){
+                if (headless){
+                    System.err.println(errorMsg);
+                }else{
+                    Runnable showRip = new Runnable() {
+                        public void run() {
+                            JFrame jf = new JFrame();
+                            JOptionPane.showMessageDialog(jf, realErrorMsg, null, JOptionPane.ERROR_MESSAGE);}
+                    };
+                    SwingUtilities.invokeAndWait(showRip);
+                }
+                exitValue = -1;
+            }
+        } catch (Exception e) {
+            if (headless){
+                System.err.println("Error:\nUnable to launch Haploview.\n"+e.getMessage());
+            }else{
+                JFrame jf = new JFrame();
+                JOptionPane.showMessageDialog(jf, "Error:\nUnable to launch Haploview.\n"+e.getMessage(), null, JOptionPane.ERROR_MESSAGE);
+            }
+        }
+        System.exit(exitValue);
+    }
+
+
+    private static void createAndShowGUI() {
+
+        contentFrame = new JFrame("Haploview Error Log");
+        contentFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
+
+        JComponent newContentPane = new JPanel();
+        newContentPane.setOpaque(true); //content panes must be opaque
+
+        errorTextArea = new JTextArea(15,50);
+        errorTextArea.setEditable(false);
+        errorTextArea.setLineWrap(true);
+        JScrollPane scrollPane = new JScrollPane(errorTextArea,
+                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+
+        errorTextArea.append("*************************\n"+
+                "If you are reporting a problem please include the contents of this log.\n"+
+                Constants.EMAIL_STRING+"\n"+
+                "*************************\n");
+
+        newContentPane.add(scrollPane);
+        contentFrame.setContentPane(newContentPane);
+        contentFrame.pack();
+        contentFrame.setVisible(true);
+    }
+
+
+}
diff --git a/edu/mit/wi/haploview/HaploData.java b/edu/mit/wi/haploview/HaploData.java
new file mode 100755
index 0000000..ef573e7
--- /dev/null
+++ b/edu/mit/wi/haploview/HaploData.java
@@ -0,0 +1,2273 @@
+package edu.mit.wi.haploview;
+
+
+import edu.mit.wi.pedfile.*;
+import java.io.*;
+import java.util.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.text.NumberFormat;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+
+
+public class HaploData implements Constants{
+
+    private Vector chromosomes;
+    private Vector extraTrioChromosomes;
+    private Haplotype[][] haplotypes;
+    private Haplotype[][] rawHaplotypes;
+    Vector blocks;
+    boolean[] isInBlock;
+    boolean infoKnown = false;
+    boolean blocksChanged = false;
+    public DPrimeTable dpTable;
+    private PedFile pedFile;
+    public boolean finished = false;
+    private double[] percentBadGenotypes;
+    XYSeriesCollection analysisTracks = new XYSeriesCollection();
+    boolean trackExists = false;
+    boolean dupsToBeFlagged = false, dupNames = false;
+
+    //stuff for computing d prime
+    private int AA = 0;
+    private int AB = 1;
+    private int BA = 2;
+    private int BB = 3;
+    private double TOLERANCE = 0.00000001;
+    private double LN10 = Math.log(10.0);
+    int unknownDH=-1;
+    int total_chroms=-1;
+    double const_prob=-1.0;
+    double[] known = new double[5];
+    double[] numHaps = new double[4];
+    double[] probHaps = new double[4];
+
+    //These are iterators for the progress bar.
+    int dPrimeTotalCount = -1;
+    int dPrimeCount;
+
+    private static boolean phasedData = false;
+
+    public int numTrios, numSingletons,numPeds;
+
+    public PedFile getPedFile(){
+        return this.pedFile;
+    }
+
+    void prepareMarkerInput(InputStream inStream, String[][] hapmapGoodies) throws IOException, HaploViewException{
+        //this method is called to gather data about the markers used.
+        //It is assumed that the input file is two columns, the first being
+        //the name and the second the absolute position. the maxdist is
+        //used to determine beyond what distance comparisons will not be
+        //made. if the infile param is null, loads up "dummy info" for
+        //situation where no info file exists
+        //An optional third column is supported which is designed to hold
+        //association study data.  If there is a third column there will be
+        //a visual indicator in the D' display that there is additional data
+        //and the detailed data can be viewed with a mouse press.
+
+        Vector names = new Vector();
+        HashSet nameSearch = new HashSet();
+        HashSet dupCheck = new HashSet();
+        Vector positions = new Vector();
+        Vector extras = new Vector();
+        boolean infoProblem = false;
+
+        dupsToBeFlagged = false;
+        dupNames = false;
+        try{
+            if (inStream != null){
+                //read the input file:
+                BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
+                String currentLine;
+                long prevloc = -1000000000;
+
+
+                int lineCount = 0;
+                while ((currentLine = in.readLine()) != null){
+                    StringTokenizer st = new StringTokenizer(currentLine);
+                    if (st.countTokens() > 1){
+                        lineCount++;
+                    }else if (st.countTokens() == 1){
+                        //complain if only one field found
+                        throw new HaploViewException("Info file format error on line "+lineCount+
+                                ":\n Info file must be of format: <markername> <markerposition>");
+                    }else{
+                        //skip blank lines
+                        continue;
+                    }
+
+                    String name = st.nextToken();
+                    String l = st.nextToken();
+                    String extra = null;
+                    if (st.hasMoreTokens()) extra = st.nextToken();
+                    long loc;
+                    try{
+                        loc = Long.parseLong(l);
+                    }catch (NumberFormatException nfe){
+                        infoProblem = true;
+                        throw new HaploViewException("Info file format error on line "+lineCount+
+                                ":\n\"" + l + "\" should be of type long." +
+                                "\n Info file must be of format: <markername> <markerposition>");
+                    }
+
+                    //basically if anyone is crazy enough to load a dataset, then go back and load
+                    //an out-of-order info file we tell them to bugger off and start over.
+                    if (loc < prevloc && Chromosome.markers != null){
+                        infoProblem = true;
+                        throw new HaploViewException("Info file out of order with preloaded dataset:\n"+
+                                name + "\nPlease reload data file and info file together.");
+                    }
+                    prevloc = loc;
+
+                    if (nameSearch.contains(name)){
+                        dupCheck.add(name);
+                    }
+                    names.add(name);
+                    nameSearch.add(name);
+                    positions.add(l);
+                    extras.add(extra);
+                }
+
+                if (lineCount > Chromosome.getUnfilteredSize()){
+                    infoProblem = true;
+                    throw(new HaploViewException("Info file error:\nMarker number mismatch: too many\nmarkers in info file compared to data file."));
+                }
+                if (lineCount < Chromosome.getUnfilteredSize()){
+                    infoProblem = true;
+                    throw(new HaploViewException("Info file error:\nMarker number mismatch: too few\nmarkers in info file compared to data file."));
+                }
+                infoKnown=true;
+            }
+
+            if (hapmapGoodies != null){
+                //we know some stuff from the hapmap so we'll add it here
+                for (int x=0; x < hapmapGoodies.length; x++){
+                    if (nameSearch.contains(hapmapGoodies[x][0])){
+                        dupCheck.add(hapmapGoodies[x][0]);
+                    }
+                    names.add(hapmapGoodies[x][0]);
+                    nameSearch.add(hapmapGoodies[x][0]);
+                    positions.add(hapmapGoodies[x][1]);
+                    extras.add(null);
+
+                }
+                infoKnown = true;
+            }
+
+
+
+            if(dupCheck.size() > 0) {
+                int nameCount = names.size();
+                Hashtable dupCounts = new Hashtable();
+                for(int i=0;i<nameCount;i++) {
+                    if(dupCheck.contains(names.get(i))){
+                        String n = (String) names.get(i);
+                        if(dupCounts.containsKey(n)){
+                            int numDups = ((Integer) dupCounts.get(n)).intValue();
+                            String newName = n + "."  + numDups;
+                            while (nameSearch.contains(newName)){
+                                numDups++;
+                                newName = n + "." + numDups;
+                            }
+                            names.setElementAt(newName,i);
+                            nameSearch.add(newName);
+                            dupCounts.put(n,new Integer(numDups)) ;
+                        }else {
+                            //we leave the first instance with its original name
+                            dupCounts.put(n,new Integer(1));
+                        }
+                        dupNames = true;
+                    }
+                }
+            }
+
+            //sort the  markers
+            int numLines = names.size();
+
+            class SortingHelper implements Comparable{
+                long pos;
+                int orderInFile;
+
+                public SortingHelper(long pos, int order){
+                    this.pos = pos;
+                    this.orderInFile = order;
+                }
+
+                public int compareTo(Object o) {
+                    SortingHelper sh = (SortingHelper)o;
+                    if (sh.pos > pos){
+                        return -1;
+                    }else if (sh.pos < pos){
+                        return 1;
+                    }else{
+                        return 0;
+                    }
+                }
+            }
+
+            boolean needSort = false;
+            Vector sortHelpers = new Vector();
+
+            for (int k = 0; k < (numLines); k++){
+                sortHelpers.add(new SortingHelper(Long.parseLong((String)positions.get(k)),k));
+            }
+
+            //loop through and check if any markers are out of order
+            for (int k = 1; k < (numLines); k++){
+                if(((SortingHelper)sortHelpers.get(k)).compareTo(sortHelpers.get(k-1)) < 0) {
+                    needSort = true;
+                    break;
+                }
+            }
+            //if any were out of order, then we need to put them in order
+            if(needSort){
+                //throw new HaploViewException("unsorted files not supported at present");
+                //sort the positions
+                Collections.sort(sortHelpers);
+                Vector newNames = new Vector();
+                Vector newExtras = new Vector();
+                Vector newPositions = new Vector();
+                int[] realPos = new int[numLines];
+
+                //reorder the vectors names and extras so that they have the same order as the sorted markers
+                for (int i = 0; i < sortHelpers.size(); i++){
+                    realPos[i] = ((SortingHelper)sortHelpers.get(i)).orderInFile;
+                    newNames.add(names.get(realPos[i]));
+                    newPositions.add(positions.get(realPos[i]));
+                    newExtras.add(extras.get(realPos[i]));
+                }
+
+                names = newNames;
+                extras = newExtras;
+                positions = newPositions;
+
+                byte[] tempGenotype = new byte[sortHelpers.size()];
+                //now we reorder all the individuals genotypes according to the sorted marker order
+                for(int j=0;j<chromosomes.size();j++){
+                    Chromosome tempChrom = (Chromosome)chromosomes.elementAt(j);
+                    for(int i =0;i<sortHelpers.size();i++){
+                        tempGenotype[i] = tempChrom.getUnfilteredGenotype(realPos[i]);
+                    }
+                    for(int i=0;i<sortHelpers.size();i++){
+                        tempChrom.setGenotype(tempGenotype[i],i);
+                    }
+                }
+
+                for(int j=0;j<extraTrioChromosomes.size();j++) {
+                    Chromosome tempChrom = (Chromosome)extraTrioChromosomes.elementAt(j);
+                    for(int i =0;i<sortHelpers.size();i++){
+                        tempGenotype[i] = tempChrom.getUnfilteredGenotype(realPos[i]);
+                    }
+                    for(int i=0;i<sortHelpers.size();i++){
+                        tempChrom.setGenotype(tempGenotype[i],i);
+                    }
+                }
+
+                //sort pedfile objects
+                //todo: this should really be done before pedfile is subjected to any processing.
+                //todo: that would require altering some order of operations in dealing with inputs
+
+                //todo: this will fry an out-of-order haps file...grr
+                Vector unsortedRes = pedFile.getResults();
+                Vector sortedRes = new Vector();
+                for (int i = 0; i < realPos.length; i++){
+                    sortedRes.add(unsortedRes.elementAt(realPos[i]));
+                }
+                pedFile.setResults(sortedRes);
+                Vector o = pedFile.getAllIndividuals();
+                for (int i = 0; i < o.size(); i++){
+                    Individual ind = (Individual) o.get(i);
+                    byte[] sortedMarkersa = new byte[ind.getNumMarkers()];
+                    byte[] sortedMarkersb = new byte[ind.getNumMarkers()];
+                    boolean[] unsortedZeroed = ind.getZeroedArray();
+                    boolean[] sortedZeroed = new boolean[unsortedZeroed.length];
+                    for (int j = 0; j < ind.getNumMarkers(); j++){
+                        sortedMarkersa[j] = ind.getAllele(realPos[j],0);
+                        sortedMarkersb[j] = ind.getAllele(realPos[j],1);
+                        sortedZeroed[j] = unsortedZeroed[realPos[j]];
+                    }
+                    ind.setMarkers(sortedMarkersa, sortedMarkersb);
+                    ind.setZeroedArray(sortedZeroed);
+                }
+            }
+
+        }catch (HaploViewException e){
+            throw(e);
+        }finally{
+            double numChroms = chromosomes.size();
+            Vector markerInfo = new Vector();
+            double[] numBadGenotypes = new double[Chromosome.getUnfilteredSize()];
+            percentBadGenotypes = new double[Chromosome.getUnfilteredSize()];
+            Vector results = null;
+            if (pedFile != null){
+                results = pedFile.getResults();
+            }
+            long prevPosition = Long.MIN_VALUE;
+            SNP prevMarker = null;
+            MarkerResult pmr = null;
+            for (int i = 0; i < Chromosome.getUnfilteredSize(); i++){
+                MarkerResult mr = null;
+                if (results != null){
+                    mr = (MarkerResult)results.elementAt(i);
+                }
+                //to compute minor/major alleles, browse chrom list and count instances of each allele
+                byte a1 = 0; byte a2 = 0;
+                double numa1 = 0; double numa2 = 0;
+                for (int j = 0; j < chromosomes.size(); j++){
+                    //if there is a data point for this marker on this chromosome
+                    byte thisAllele = ((Chromosome)chromosomes.elementAt(j)).getUnfilteredGenotype(i);
+                    if (!(thisAllele == 0)){
+                        if (thisAllele >= 5){
+                            numa1+=0.5; numa2+=0.5;
+                            if (thisAllele < 9){
+                                if (a1==0){
+                                    a1 = (byte)(thisAllele-4);
+                                }else if (a2 == 0){
+                                    if (!(thisAllele-4 == a1)){
+                                        a2 = (byte)(thisAllele-4);
+                                    }
+                                }
+                            }
+                        }else if (a1 == 0){
+                            a1 = thisAllele; numa1++;
+                        }else if (thisAllele == a1){
+                            numa1++;
+                        }else{
+                            numa2++;
+                            a2 = thisAllele;
+                        }
+                    }
+                    else {
+                        numBadGenotypes[i]++;
+                    }
+                }
+                if (numa2 >= numa1){
+                    byte temp = a1;
+                    double tempnum = numa1;
+                    numa1 = numa2;
+                    a1 = a2;
+                    numa2 = tempnum;
+                    a2 = temp;
+                }
+
+                double maf;
+                if (mr != null){
+                    maf = Util.roundDouble(mr.getMAF(),3);
+                }else{
+                    maf = Util.roundDouble((numa2/(numa1+numa2)),3);
+                }
+
+                if (Chromosome.markers == null || !infoProblem){
+                    if (infoKnown){
+                        long pos = Long.parseLong((String)positions.elementAt(i));
+
+                        SNP thisMarker = (new SNP((String)names.elementAt(i),
+                                pos, maf, a1, a2,
+                                (String)extras.elementAt(i)));
+                        markerInfo.add(thisMarker);
+
+                        if (mr != null){
+                            double genoPC = mr.getGenoPercent();
+                            //check to make sure adjacent SNPs do not have identical positions
+                            if (prevPosition != Long.MIN_VALUE){
+                                //only do this for markers 2..N, since we're comparing to the previous location
+                                if (pos == prevPosition){
+                                    dupsToBeFlagged = true;
+                                    if (genoPC >= pmr.getGenoPercent()){
+                                        //use this one because it has more genotypes
+                                        thisMarker.setDup(1);
+                                        prevMarker.setDup(2);
+                                    }else{
+                                        //use the other one because it has more genotypes
+                                        thisMarker.setDup(2);
+                                        prevMarker.setDup(1);
+                                    }
+                                }
+                            }
+                            prevPosition = pos;
+                            prevMarker = thisMarker;
+                            pmr = mr;
+                        }
+                    }else{
+                        markerInfo.add(new SNP(null,i+1,maf,a1,a2));
+                    }
+                    percentBadGenotypes[i] = numBadGenotypes[i]/numChroms;
+                }
+            }
+            if (Chromosome.markers == null || !infoProblem){
+                Chromosome.markers = markerInfo;
+            }
+        }
+    }
+
+    public Vector prepareHapsInput(String name) throws IOException, HaploViewException, PedFileException {
+        //this method is called to suck in data from a file (its only argument)
+        //of genotypes and sets up the Chromosome objects.
+        Vector chroms = new Vector();
+        Vector hapsFileStrings = new Vector();
+        BufferedReader reader;
+
+        HaploData.setPhasedData(false);
+
+        try{
+            URL inURL = new URL(name);
+            reader = new BufferedReader(new InputStreamReader(inURL.openStream()));
+        }catch(MalformedURLException mfe){
+            File inFile = new File(name);
+            if (inFile.length() < 1){
+                throw new HaploViewException("Genotype file is empty or nonexistent: " + inFile.getName());
+            }
+            reader = new BufferedReader(new FileReader(inFile));
+        }catch(IOException ioe){
+            throw new HaploViewException("Could not connect to " + name);
+        }
+
+        String line;
+        while((line = reader.readLine())!=null){
+            if (line.length() == 0){
+                //skip blank lines
+                continue;
+            }
+            if (line.startsWith("#")){
+                //skip comments
+                continue;
+            }
+            hapsFileStrings.add(line);
+        }
+        pedFile = new PedFile();
+        pedFile.parseHapsFile(hapsFileStrings);
+        Vector result = pedFile.check();
+        Vector indList = pedFile.getUnrelatedIndividuals();
+        numSingletons = 0;
+        Individual currentInd;
+        int numMarkers = 0;
+        for (int x=0; x<indList.size(); x++){
+            currentInd = (Individual)indList.elementAt(x);
+            numMarkers = currentInd.getNumMarkers();
+            byte[] chrom1 = new byte[numMarkers];
+            byte[] chrom2 = new byte[numMarkers];
+            for (int i = 0; i < numMarkers; i++){
+                /*byte[] thisMarker;
+                thisMarker = currentInd.getMarker(i);
+                chrom1[i] = thisMarker[0];
+                chrom2[i] = thisMarker[1];
+                */
+                chrom1[i] = currentInd.getAllele(i,0);
+                chrom2[i] = currentInd.getAllele(i,1);
+            }
+            chroms.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),chrom1,currentInd.getAffectedStatus(),0, false));
+            chroms.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),chrom2,currentInd.getAffectedStatus(),0, false));
+            numSingletons++;
+        }
+
+        chromosomes = chroms;
+
+        //wipe clean any existing marker info so we know we're starting clean with a new file
+        Chromosome.markers = null;
+        return result;
+    }
+
+    public Vector linkageToChrom(String name, int type)
+            throws IllegalArgumentException, HaploViewException, PedFileException, IOException{
+
+        Vector pedFileStrings = new Vector();
+        Vector hapsDataStrings = new Vector();
+
+        HaploData.setPhasedData(false);
+
+        BufferedReader reader;
+        try{
+            URL inURL = new URL(name);
+            reader = new BufferedReader(new InputStreamReader(inURL.openStream()));
+        }catch(MalformedURLException mfe){
+            File inFile = new File(name);
+            if (inFile.length() < 1){
+                throw new HaploViewException("Genotype file is empty or nonexistent: " + inFile.getName());
+            }
+            reader = new BufferedReader(new FileReader(inFile));
+        }catch(IOException ioe){
+            throw new HaploViewException("Could not connect to " + name);
+        }
+        String line;
+        while((line = reader.readLine())!=null){
+            if (line.length() == 0){
+                //skip blank lines
+                continue;
+            }
+            if (line.startsWith("#@")){
+                hapsDataStrings.add(line.substring(2));
+                continue;
+            }
+            if (line.startsWith("#")){
+                //skip comments
+                continue;
+            }
+            pedFileStrings.add(line);
+        }
+        pedFile = new PedFile();
+
+        if (type == PED_FILE){
+            pedFile.parseLinkage(pedFileStrings);
+        }else{
+            pedFile.parseHapMap(pedFileStrings, hapsDataStrings);
+        }
+
+        Vector result = pedFile.check();
+        Vector allInds = pedFile.getAllIndividuals();
+        Vector unrelatedInds = pedFile.getUnrelatedIndividuals();
+        HashSet unrelatedHash = new HashSet(unrelatedInds);
+        HashSet indsInTrio = new HashSet();
+        int numMarkers = 0;
+        numSingletons = 0;
+        numTrios = 0;
+        numPeds = pedFile.getNumFamilies();
+        extraTrioChromosomes = new Vector();
+        Individual currentInd;
+        Family currentFamily;
+        Vector chrom = new Vector();
+
+        //first time through we deal with trios.
+        for(int x=0; x < allInds.size(); x++){
+
+            currentInd = (Individual)allInds.get(x);
+            boolean haploid = ((currentInd.getGender() == 1) && Chromosome.getDataChrom().equalsIgnoreCase("chrx"));
+            currentFamily = pedFile.getFamily(currentInd.getFamilyID());
+            if (currentFamily.containsMember(currentInd.getMomID()) &&
+                    currentFamily.containsMember(currentInd.getDadID())){
+                //if indiv has both parents
+                Individual mom = currentFamily.getMember(currentInd.getMomID());
+                Individual dad = currentFamily.getMember(currentInd.getDadID());
+                //if (unrelatedInds.contains(mom) && unrelatedInds.contains(dad)){
+                numMarkers = currentInd.getNumMarkers();
+                byte[] dadT = new byte[numMarkers];
+                byte[] dadU = new byte[numMarkers];
+                byte[] momT = new byte[numMarkers];
+                byte[] momU = new byte[numMarkers];
+
+                for (int i = 0; i < numMarkers; i++){
+                    byte kid1, kid2;
+                    if (currentInd.getZeroed(i)){
+                        kid1 = 0;
+                        kid2 = 0;
+                    }else{
+                        kid1 = currentInd.getAllele(i,0);
+                        kid2 = currentInd.getAllele(i,1);
+                    }
+
+                    byte mom1,mom2;
+                    if (currentFamily.getMember(currentInd.getMomID()).getZeroed(i)){
+                        mom1 = 0;
+                        mom2 = 0;
+                    }else{
+                        mom1 = (currentFamily.getMember(currentInd.getMomID())).getAllele(i,0);
+                        mom2 = (currentFamily.getMember(currentInd.getMomID())).getAllele(i,1);
+                    }
+
+                    byte dad1,dad2;
+                    if (currentFamily.getMember(currentInd.getDadID()).getZeroed(i)){
+                        dad1 = 0;
+                        dad2 = 0;
+                    }else{
+                        dad1 = (currentFamily.getMember(currentInd.getDadID())).getAllele(i,0);
+                        dad2 = (currentFamily.getMember(currentInd.getDadID())).getAllele(i,1);
+                    }
+
+
+                    if(haploid) {
+                        if(kid1 == 0) {
+                            //kid missing
+                            dadU[i] = dad1;
+                            if (mom1 == mom2) {
+                                momT[i] = mom1;
+                                momU[i] = mom1;
+                            } else if (mom1 != 0 && mom2 != 0){
+                                momT[i] = (byte)(4+mom1);
+                                momU[i] = (byte)(4+mom2);
+                            }
+                        } else {
+                            dadU[i] = dad1;
+                            if (mom1 == 0) {
+                                momT[i] = kid1;
+                                momU[i] = 0;
+                            } else if (mom1 == kid1) {
+                                momT[i] = mom1;
+                                momU[i] = mom2;
+                            } else {
+                                momT[i] = mom2;
+                                momU[i] = mom1;
+                            }
+                        }
+
+
+                    }else {
+
+                        if (kid1 == 0 || kid2 == 0) {
+                            //kid missing
+                            if (dad1 == dad2) {
+                                dadT[i] = dad1;
+                                dadU[i] = dad1;
+                            } else if (dad1 != 0 && dad2 != 0) {
+                                dadT[i] = (byte)(4+dad1);
+                                dadU[i] = (byte)(4+dad2);
+                            }
+                            if (mom1 == mom2) {
+                                momT[i] = mom1;
+                                momU[i] = mom1;
+                            } else if (mom1 != 0 && mom2 != 0){
+                                momT[i] = (byte)(4+mom1);
+                                momU[i] = (byte)(4+mom2);
+                            }
+                        } else if (kid1 == kid2) {
+                            //kid homozygous
+                            if (dad1 == 0) {
+                                dadT[i] = kid1;
+                                dadU[i] = 0;
+                            } else if (dad1 == kid1) {
+                                dadT[i] = dad1;
+                                dadU[i] = dad2;
+                            } else {
+                                dadT[i] = dad2;
+                                dadU[i] = dad1;
+                            }
+
+                            if (mom1 == 0) {
+                                momT[i] = kid1;
+                                momU[i] = 0;
+                            } else if (mom1 == kid1) {
+                                momT[i] = mom1;
+                                momU[i] = mom2;
+                            } else {
+                                momT[i] = mom2;
+                                momU[i] = mom1;
+                            }
+                        } else {
+                            //kid heterozygous and this if tree's a bitch
+                            if (dad1 == 0 && mom1 == 0) {
+                                //both missing
+                                dadT[i] = 0;
+                                dadU[i] = 0;
+                                momT[i] = 0;
+                                momU[i] = 0;
+                            } else if (dad1 == 0 && mom1 != mom2) {
+                                //dad missing mom het
+                                dadT[i] = 0;
+                                dadU[i] = 0;
+                                momT[i] = (byte)(4+mom1);
+                                momU[i] = (byte)(4+mom2);
+                            } else if (mom1 == 0 && dad1 != dad2) {
+                                //dad het mom missing
+                                dadT[i] = (byte)(4+dad1);
+                                dadU[i] = (byte)(4+dad2);
+                                momT[i] = 0;
+                                momU[i] = 0;
+                            } else if (dad1 == 0 && mom1 == mom2) {
+                                //dad missing mom hom
+                                momT[i] = mom1;
+                                momU[i] = mom1;
+                                dadU[i] = 0;
+                                if (kid1 == mom1) {
+                                    dadT[i] = kid2;
+                                } else {
+                                    dadT[i] = kid1;
+                                }
+                            } else if (mom1 == 0 && dad1 == dad2) {
+                                //mom missing dad hom
+                                dadT[i] = dad1;
+                                dadU[i] = dad1;
+                                momU[i] = 0;
+                                if (kid1 == dad1) {
+                                    momT[i] = kid2;
+                                } else {
+                                    momT[i] = kid1;
+                                }
+                            } else if (dad1 == dad2 && mom1 != mom2) {
+                                //dad hom mom het
+                                dadT[i] = dad1;
+                                dadU[i] = dad2;
+                                if (kid1 == dad1) {
+                                    momT[i] = kid2;
+                                    momU[i] = kid1;
+                                } else {
+                                    momT[i] = kid1;
+                                    momU[i] = kid2;
+                                }
+                            } else if (mom1 == mom2 && dad1 != dad2) {
+                                //dad het mom hom
+                                momT[i] = mom1;
+                                momU[i] = mom2;
+                                if (kid1 == mom1) {
+                                    dadT[i] = kid2;
+                                    dadU[i] = kid1;
+                                } else {
+                                    dadT[i] = kid1;
+                                    dadU[i] = kid2;
+                                }
+                            } else if (dad1 == dad2 && mom1 == mom2) {
+                                //mom & dad hom
+                                dadT[i] = dad1;
+                                dadU[i] = dad1;
+                                momT[i] = mom1;
+                                momU[i] = mom1;
+                            } else {
+                                //everybody het
+                                dadT[i] = (byte)(4+dad1);
+                                dadU[i] = (byte)(4+dad2);
+                                momT[i] = (byte)(4+mom1);
+                                momU[i] = (byte)(4+mom2);
+                            }
+                        }
+                    }
+                }
+                if(unrelatedHash.contains(mom) && unrelatedHash.contains(dad) && unrelatedHash.contains(currentInd)){
+                    chrom.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),momT, mom.getAffectedStatus(),currentInd.getAffectedStatus(), false));
+                    chrom.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),momU, mom.getAffectedStatus(),currentInd.getAffectedStatus(), false));
+
+                    if(haploid) {
+                        chrom.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),dadU, dad.getAffectedStatus(),currentInd.getAffectedStatus(), false));
+                        ((Chromosome)chrom.lastElement()).setHaploid(true);
+                    }else if(Chromosome.getDataChrom().equalsIgnoreCase("chrx")){
+                        chrom.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),dadT, dad.getAffectedStatus(), currentInd.getAffectedStatus(), false));
+                        ((Chromosome)chrom.lastElement()).setHaploid(true);
+                    }else {
+                        chrom.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),dadT, dad.getAffectedStatus(), currentInd.getAffectedStatus(), false));
+                        chrom.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),dadU, dad.getAffectedStatus(), currentInd.getAffectedStatus(), false));
+                    }
+
+
+                    numTrios++;
+                    indsInTrio.add(mom);
+                    indsInTrio.add(dad);
+                    indsInTrio.add(currentInd);
+
+                    //}
+                }else{
+                    extraTrioChromosomes.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),momT, mom.getAffectedStatus(),currentInd.getAffectedStatus(), false));
+                    extraTrioChromosomes.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),momU, mom.getAffectedStatus(),currentInd.getAffectedStatus(), false));
+
+                    if(haploid) {
+                        extraTrioChromosomes.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),dadU, dad.getAffectedStatus(),currentInd.getAffectedStatus(), false));
+                        ((Chromosome)extraTrioChromosomes.lastElement()).setHaploid(true);
+                    }else if(Chromosome.getDataChrom().equalsIgnoreCase("chrx")){
+                        extraTrioChromosomes.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),dadT, dad.getAffectedStatus(), currentInd.getAffectedStatus(), false));
+                        ((Chromosome)extraTrioChromosomes.lastElement()).setHaploid(true);
+                    }else {
+                        extraTrioChromosomes.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),dadT, dad.getAffectedStatus(), currentInd.getAffectedStatus(), false));
+                        extraTrioChromosomes.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),dadU, dad.getAffectedStatus(), currentInd.getAffectedStatus(), false));
+                    }
+                }
+            }
+        }
+        for (int x=0; x<unrelatedInds.size(); x++){
+            currentInd = (Individual)unrelatedInds.get(x);
+            boolean haploid = ((currentInd.getGender() == 1) && Chromosome.getDataChrom().equalsIgnoreCase("chrx"));
+            if (!indsInTrio.contains(currentInd)){
+                //ind has no parents or kids -- he's a singleton
+                numMarkers = currentInd.getNumMarkers();
+                byte[] chrom1 = new byte[numMarkers];
+                byte[] chrom2 = new byte[numMarkers];
+                for (int i = 0; i < numMarkers; i++){
+                    byte thisMarkerA, thisMarkerB;
+                    if (currentInd.getZeroed(i)){
+                        thisMarkerA = 0;
+                        thisMarkerB = 0;
+                    }else{
+                        thisMarkerA = currentInd.getAllele(i,0);
+                        thisMarkerB = currentInd.getAllele(i,1);
+                    }
+                    if (thisMarkerA == thisMarkerB || thisMarkerA == 0 || thisMarkerB == 0){
+                        chrom1[i] = thisMarkerA;
+                        chrom2[i] = thisMarkerB;
+                    }else{
+                        chrom1[i] = (byte)(4+thisMarkerA);
+                        chrom2[i] = (byte)(4+thisMarkerB);
+                    }
+                }
+                chrom.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),chrom1, currentInd.getAffectedStatus(), -1, false));
+                if(!haploid){
+                    chrom.add(new Chromosome(currentInd.getFamilyID(),currentInd.getIndividualID(),chrom2,currentInd.getAffectedStatus(), -1, false));
+                }else{
+                    ((Chromosome)chrom.lastElement()).setHaploid(true);
+                }
+                numSingletons++;
+            }
+        }
+        chromosomes = chrom;
+        //wipe clean any existing marker info so we know we're starting with a new file
+        Chromosome.markers = null;
+        return result;
+    }
+
+    public Vector phasedToChrom(String[] info, int type)
+            throws IllegalArgumentException, HaploViewException, PedFileException, IOException{
+
+        infoKnown = true;
+        pedFile = new PedFile();
+        if (type == HMPDL_FILE){
+            pedFile.parsePhasedDownload(info);
+        }else if (type == PHASEHMP_FILE){
+            pedFile.parseHapMapPhase(info);
+        }/*else if (type == FASTPHASE_FILE){
+            pedFile.parseFastPhase(info);
+        }*/
+
+        HaploData.setPhasedData(true);
+
+        Vector result = pedFile.check();
+        Vector indList = pedFile.getUnrelatedIndividuals();
+        Vector chroms = new Vector();
+        Individual currentInd;
+        int numMarkers;
+        byte thisMarkerA, thisMarkerB;
+        numSingletons = 0;
+
+        for (int x=0; x < indList.size(); x++){
+            currentInd = (Individual)indList.get(x);
+            numMarkers = currentInd.getNumMarkers();
+            byte[] chrom1 = new byte[numMarkers];
+            byte[] chrom2 = new byte[numMarkers];
+            for (int i=0; i < numMarkers; i++){
+                thisMarkerA = currentInd.getAllele(i,0);
+                thisMarkerB = currentInd.getAllele(i,1);
+                chrom1[i] = thisMarkerA;
+                chrom2[i] = thisMarkerB;
+            }
+            if(Chromosome.getDataChrom().equalsIgnoreCase("chrx") && currentInd.getGender() == Individual.MALE){
+                chroms.add(new Chromosome(currentInd.getFamilyID(), currentInd.getIndividualID(), chrom1, currentInd.getAffectedStatus(), 0, true));
+                ((Chromosome)chroms.lastElement()).setHaploid(true);
+            }else{
+                chroms.add(new Chromosome(currentInd.getFamilyID(), currentInd.getIndividualID(), chrom1, currentInd.getAffectedStatus(), 0, true));
+                chroms.add(new Chromosome(currentInd.getFamilyID(), currentInd.getIndividualID(), chrom2, currentInd.getAffectedStatus(), 0, true));
+            }
+            numSingletons++;
+        }
+
+        chromosomes = chroms;
+
+        //wipe clean any existing marker info so we know we're starting clean with a new file
+        Chromosome.markers = null;
+        return result;
+    }
+
+    void generateDPrimeTable(){
+        //calculating D prime requires the number of each possible 2 marker
+        //haplotype in the dataset
+
+        dpTable = new DPrimeTable(Chromosome.getUnfilteredSize());
+
+        int maxdist = Options.getMaxDistance();
+        dPrimeTotalCount = ((Chromosome.getUnfilteredSize()-1)*(Chromosome.getUnfilteredSize()-1))/2;
+        dPrimeCount = 0;
+        //loop through all marker pairs
+        for (int pos1 = 0; pos1 < Chromosome.getUnfilteredSize()-1; pos1++){
+            Vector dpTemp= new Vector();
+            for (int pos2 = pos1 + 1; pos2 < Chromosome.getUnfilteredSize(); pos2++){
+                //if the markers are too far apart don't try to compare them
+                long sep = Chromosome.getUnfilteredMarker(pos2).getPosition() - Chromosome.getUnfilteredMarker(pos1).getPosition();
+                if (maxdist > 0){
+                    if (sep <= maxdist){
+                        dpTemp.add(computeDPrime(pos1,pos2));
+                    }
+                }else{
+                    //maxdist==0 is the convention used to force us to compare all the markers
+                    dpTemp.add(computeDPrime(pos1,pos2));
+                }
+                dPrimeCount++;
+            }
+            dpTable.addMarker(dpTemp,pos1);
+        }
+    }
+
+    public Haplotype[][] generateBlockHaplotypes(Vector blocks) throws HaploViewException{
+        Haplotype[][] rawHaplotypes = generateHaplotypes(blocks, true);
+        Haplotype[][] tempHaplotypes = new Haplotype[rawHaplotypes.length][];
+
+        for (int i = 0; i < rawHaplotypes.length; i++) {
+            Vector orderedHaps = new Vector();
+            //step through each haplotype in this block
+            for (int hapCount = 0; hapCount < rawHaplotypes[i].length; hapCount++) {
+                if (orderedHaps.size() == 0) {
+                    orderedHaps.add(rawHaplotypes[i][hapCount]);
+                } else {
+                    for (int j = 0; j < orderedHaps.size(); j++) {
+                        if (((Haplotype)(orderedHaps.elementAt(j))).getPercentage() <
+                                rawHaplotypes[i][hapCount].getPercentage()) {
+                            orderedHaps.add(j, rawHaplotypes[i][hapCount]);
+                            break;
+                        }
+                        if ((j+1) == orderedHaps.size()) {
+                            orderedHaps.add(rawHaplotypes[i][hapCount]);
+                            break;
+                        }
+                    }
+                }
+            }
+            tempHaplotypes[i] = new Haplotype[orderedHaps.size()];
+            orderedHaps.copyInto(tempHaplotypes[i]);
+        }
+        tempHaplotypes = generateCrossovers(tempHaplotypes);
+        haplotypes = tempHaplotypes;
+        this.rawHaplotypes = rawHaplotypes;
+        return tempHaplotypes;
+    }
+
+    public Haplotype[][] generateHaplotypes(Vector blocks, boolean storeEM) throws HaploViewException{
+        Haplotype[][] rawHaplotypes = new Haplotype[blocks.size()][];
+
+        for (int k = 0; k < blocks.size(); k++){
+            int[] preFiltBlock = (int[])blocks.elementAt(k);
+            int[] theBlock;
+
+            int[] selectedMarkers = new int[0];
+            int[] equivClass = new int[0];
+            if (preFiltBlock.length > 30){
+                equivClass = new int[preFiltBlock.length];
+                int classCounter = 0;
+                for (int x = 0; x < preFiltBlock.length; x++){
+                    int marker1 = preFiltBlock[x];
+
+                    //already been lumped into an equivalency class
+                    if (equivClass[x] != 0){
+                        continue;
+                    }
+
+                    //start a new equivalency class for this SNP
+                    classCounter ++;
+                    equivClass[x] = classCounter;
+
+                    for (int y = x+1; y < preFiltBlock.length; y++){
+                        int marker2 = preFiltBlock[y];
+                        if (marker1 > marker2){
+                            int tmp = marker1; marker1 = marker2; marker2 = tmp;
+                        }
+                        if ( dpTable.getLDStats(marker1,marker2) != null
+                                && dpTable.getLDStats(marker1,marker2).getRSquared() == 1.0){
+                            //these two SNPs are redundant
+                            equivClass[y] = classCounter;
+                        }
+                    }
+                }
+
+                //parse equivalency classes
+                selectedMarkers = new int[classCounter];
+                for (int x = 0; x < selectedMarkers.length; x++){
+                    selectedMarkers[x] = -1;
+                }
+                for (int x = 0; x < classCounter; x++){
+                    double genoPC = 1.0;
+                    for (int y = 0; y < equivClass.length; y++){
+                        if (equivClass[y] == x+1){
+                            if (percentBadGenotypes[Chromosome.realIndex[preFiltBlock[y]]] <= genoPC){
+                                selectedMarkers[x] = preFiltBlock[y];
+                                genoPC = percentBadGenotypes[Chromosome.realIndex[preFiltBlock[y]]];
+                            }
+                        }
+                    }
+                }
+
+                theBlock = selectedMarkers;
+            }else{
+                theBlock = preFiltBlock;
+            }
+
+            //kirby patch
+            EM theEM = new EM(chromosomes,numTrios, extraTrioChromosomes);
+            theEM.doEM(theBlock);
+
+            Haplotype[] tempArray = new Haplotype[theEM.numHaplos()];
+            int[][] returnedHaplos = theEM.getHaplotypes();
+            double[] returnedFreqs = theEM.getFrequencies();
+            for (int i = 0; i < theEM.numHaplos(); i++){
+                int[] genos = new int[returnedHaplos[i].length];
+                for (int j = 0; j < genos.length; j++){
+                    if (returnedHaplos[i][j] == 1){
+                        genos[j] = Chromosome.getMarker(theBlock[j]).getMajor();
+                    }else{
+                        if (Chromosome.getMarker(theBlock[j]).getMinor() == 0){
+                            genos[j] = 8;
+                        }else{
+                            genos[j] = Chromosome.getMarker(theBlock[j]).getMinor();
+                        }
+                    }
+                }
+
+                if (selectedMarkers.length > 0){
+                    //we need to reassemble the haplotypes
+                    Hashtable hapsHash = new Hashtable();
+                    //add to hash all the genotypes we phased
+                    for (int q = 0; q < genos.length; q++){
+                        hapsHash.put(new Integer(theBlock[q]), new Integer(genos[q]));
+                    }
+                    //now add all the genotypes we didn't bother phasing, based on
+                    //which marker they are identical to
+                    for (int q = 0; q < equivClass.length; q++){
+                        int currentClass = equivClass[q]-1;
+                        if (selectedMarkers[currentClass] == preFiltBlock[q]){
+                            //we alredy added the phased genotypes above
+                            continue;
+                        }
+                        int indexIntoBlock=0;
+                        for (int x = 0; x < theBlock.length; x++){
+                            if (theBlock[x] == selectedMarkers[currentClass]){
+                                indexIntoBlock = x;
+                                break;
+                            }
+                        }
+                        //this (somewhat laboriously) reconstructs whether to add the minor or major allele
+                        //for markers with MAF close to 0.50 we can't use major/minor alleles to match
+                        //'em up 'cause these might change given missing data
+                        boolean success = false;
+                        if (Chromosome.getMarker(selectedMarkers[currentClass]).getMAF() > 0.4){
+                            for (int z = 0; z < chromosomes.size(); z++){
+                                Chromosome thisChrom = (Chromosome)chromosomes.elementAt(z);
+                                Chromosome nextChrom = (Chromosome)chromosomes.elementAt(++z);
+                                int theGeno = thisChrom.getGenotype(selectedMarkers[currentClass]);
+                                int nextGeno = nextChrom.getGenotype(selectedMarkers[currentClass]);
+                                if (theGeno == nextGeno && theGeno == genos[indexIntoBlock]
+                                        && thisChrom.getGenotype(preFiltBlock[q]) != 0){
+                                    hapsHash.put(new Integer(preFiltBlock[q]),
+                                            new Integer(thisChrom.getGenotype(preFiltBlock[q])));
+                                    success = true;
+                                    break;
+                                }
+                            }
+                        }
+
+                        //either we didn't use careful counting or it didn't work due to missing data
+                        if(!success){
+                            if (Chromosome.getMarker(selectedMarkers[currentClass]).getMajor() ==
+                                    genos[indexIntoBlock]){
+                                hapsHash.put(new Integer(preFiltBlock[q]),
+                                        new Integer(Chromosome.getMarker(preFiltBlock[q]).getMajor()));
+                            }else{
+                                hapsHash.put(new Integer(preFiltBlock[q]),
+                                        new Integer(Chromosome.getMarker(preFiltBlock[q]).getMinor()));
+                            }
+                        }
+                    }
+                    genos = new int[preFiltBlock.length];
+                    for (int q = 0; q < preFiltBlock.length; q++){
+                        genos[q] = ((Integer)hapsHash.get(new Integer(preFiltBlock[q]))).intValue();
+                    }
+                }
+
+                if(storeEM) {
+                    tempArray[i] = new Haplotype(genos, returnedFreqs[i], preFiltBlock, theEM);
+                }else {
+                    tempArray[i] = new Haplotype(genos, returnedFreqs[i], preFiltBlock, null);
+                }
+                //if we are performing association tests, then store the rawHaplotypes
+                if (Options.getAssocTest() == ASSOC_TRIO){
+                    tempArray[i].setTransCount(theEM.getTransCount(i));
+                    tempArray[i].setUntransCount(theEM.getUntransCount(i));
+                    if(Options.getTdtType() == TDT_PAREN) {
+                        tempArray[i].setDiscordantAlleleCounts(theEM.getDiscordantCounts(i));
+                    }
+                }else if (Options.getAssocTest() == ASSOC_CC){
+                    tempArray[i].setCaseCount(theEM.getCaseCount(i));
+                    tempArray[i].setControlCount(theEM.getControlCount(i));
+                }
+            }
+            //make the rawHaplotypes array only large enough to hold haps
+            //which pass threshold above
+            rawHaplotypes[k] = new Haplotype[theEM.numHaplos()];
+            for (int z = 0; z < theEM.numHaplos(); z++){
+                rawHaplotypes[k][z] = tempArray[z];
+            }
+        }
+
+        return rawHaplotypes;
+    }
+
+    public double[] computeMultiDprime(Haplotype[][] haplos){
+        double[] multidprimeArray = new double[haplos.length];
+        for (int gap = 0; gap < haplos.length - 1; gap++){
+            double[][] multilocusTable = new double[haplos[gap].length][];
+            double[] rowSum = new double[haplos[gap].length];
+            double[] colSum = new double[haplos[gap+1].length];
+            double multilocusTotal = 0;
+            for (int i = 0; i < haplos[gap].length; i++){
+                multilocusTable[i] = haplos[gap][i].getCrossovers();
+            }
+            //compute multilocus D'
+            for (int i = 0; i < rowSum.length; i++){
+                for (int j = 0; j < colSum.length; j++){
+                    rowSum[i] += multilocusTable[i][j];
+                    colSum[j] += multilocusTable[i][j];
+                    multilocusTotal += multilocusTable[i][j];
+                    if (rowSum[i] == 0) rowSum[i] = 0.0001;
+                    if (colSum[j] == 0) colSum[j] = 0.0001;
+                }
+            }
+            double multidprime = 0;
+            boolean noDivByZero = false;
+            for (int i = 0; i < rowSum.length; i++){
+                for (int j = 0; j < colSum.length; j++){
+                    double num = (multilocusTable[i][j]/multilocusTotal) - (rowSum[i]/multilocusTotal)*(colSum[j]/multilocusTotal);
+                    double denom;
+                    if (num < 0){
+                        double denom1 = (rowSum[i]/multilocusTotal)*(colSum[j]/multilocusTotal);
+                        double denom2 = (1.0 - (rowSum[i]/multilocusTotal))*(1.0 - (colSum[j]/multilocusTotal));
+                        if (denom1 < denom2) {
+                            denom = denom1;
+                        }else{
+                            denom = denom2;
+                        }
+                    }else{
+                        double denom1 = (rowSum[i]/multilocusTotal)*(1.0 -(colSum[j]/multilocusTotal));
+                        double denom2 = (1.0 - (rowSum[i]/multilocusTotal))*(colSum[j]/multilocusTotal);
+                        if (denom1 < denom2){
+                            denom = denom1;
+                        }else{
+                            denom = denom2;
+                        }
+                    }
+                    if (denom != 0){
+                        noDivByZero = true;
+                        multidprime += (rowSum[i]/multilocusTotal)*(colSum[j]/multilocusTotal)*Math.abs(num/denom);
+                    }
+                }
+            }
+            if (noDivByZero && multidprime <= 1.00){
+                multidprimeArray[gap] = multidprime;
+            }else{
+                multidprimeArray[gap] = 1.00;
+            }
+        }
+        return multidprimeArray;
+    }
+
+    public void pickTags(Haplotype[][] haplos){
+        for (int i = 0; i < haplos.length; i++){
+            //first clear the tags for this block
+            haplos[i][0].clearTags();
+
+
+            //next pick the tagSNPs
+            Vector theBestSubset = getBestSubset(haplos[i]);
+            for (int j = 0; j < theBestSubset.size(); j++){
+                haplos[i][0].addTag(((Integer)theBestSubset.elementAt(j)).intValue());
+            }
+
+            for (int k = 1; k < haplos[i].length; k++){
+                //so the tags should be a property of the block, but there's no object to represent it right now
+                //so we just make sure we copy the tags into all the haps in this block...sorry, I suck.
+                haplos[i][k].setTags(haplos[i][0].getTags());
+            }
+        }
+    }
+
+    public Haplotype[][] orderByCrossing(Haplotype[][] haplos){
+        //seed first block with ordering numbers
+        for (int u = 0; u < haplos[0].length; u++){
+            haplos[0][u].setListOrder(u);
+        }
+
+        for (int gap = 0; gap < haplos.length - 1; gap++){
+            //sort based on "straight line" crossings
+            int hilimit;
+            int lolimit;
+            if (haplos[gap+1].length > haplos[gap].length) {
+                hilimit = haplos[gap+1].length;
+                lolimit = haplos[gap].length;
+            }else{
+                hilimit = haplos[gap].length;
+                lolimit = haplos[gap+1].length;
+            }
+            boolean[] unavailable = new boolean[hilimit];
+            int[] prevBlockLocs = new int[haplos[gap].length];
+            for (int q = 0; q < prevBlockLocs.length; q++){
+                prevBlockLocs[haplos[gap][q].getListOrder()] = q;
+            }
+
+            for (int u = 0; u < haplos[gap+1].length; u++){
+                double currentBestVal = 0;
+                int currentBestLoc = -1;
+                for (int v = 0; v < lolimit; v++){
+                    if (!(unavailable[v])){
+                        if (haplos[gap][prevBlockLocs[v]].getCrossover(u) >= currentBestVal) {
+                            currentBestLoc = haplos[gap][prevBlockLocs[v]].getListOrder();
+                            currentBestVal = haplos[gap][prevBlockLocs[v]].getCrossover(u);
+                        }
+                    }
+                }
+                //it didn't get lined up with any of the previous block's markers
+                //put it at the end of the list
+                if (currentBestLoc == -1){
+                    for (int v = 0; v < unavailable.length; v++){
+                        if (!(unavailable[v])){
+                            currentBestLoc = v;
+                            break;
+                        }
+                    }
+                }
+
+                haplos[gap+1][u].setListOrder(currentBestLoc);
+                unavailable[currentBestLoc] = true;
+            }
+        }
+        return haplos;
+    }
+
+    Haplotype[][] generateCrossovers(Haplotype[][] haplos) throws HaploViewException{
+        Vector crossBlock = new Vector();
+        double CROSSOVER_THRESHOLD = 0.001;   //to what percentage do we want to consider crossings?
+
+        if (haplos.length == 0) return null;
+
+        for (int gap = 0; gap < haplos.length - 1; gap++){         //compute crossovers for each inter-block gap
+            Vector preGapSubset = getBestSubset(haplos[gap]);
+            Vector postGapSubset = getBestSubset(haplos[gap+1]);
+            int[] preMarkerID = haplos[gap][0].getMarkers();       //index haplos to markers in whole dataset
+            int[] postMarkerID = haplos[gap+1][0].getMarkers();
+
+            crossBlock.clear();                 //make a "block" of the markers which id the pre- and post- gap haps
+            for (int i = 0; i < preGapSubset.size(); i++){
+                crossBlock.add(new Integer(preMarkerID[((Integer)preGapSubset.elementAt(i)).intValue()]));
+            }
+            for (int i = 0; i < postGapSubset.size(); i++){
+                crossBlock.add(new Integer(postMarkerID[((Integer)postGapSubset.elementAt(i)).intValue()]));
+            }
+
+            Vector inputVector = new Vector();
+            int[] intArray = new int[crossBlock.size()];
+            for (int i = 0; i < crossBlock.size(); i++){      //input format for hap generating routine
+                intArray[i] = ((Integer)crossBlock.elementAt(i)).intValue();
+            }
+            inputVector.add(intArray);
+
+            Haplotype[] crossHaplos = generateHaplotypes(inputVector, true)[0];  //get haplos of gap
+
+
+            for (int i = 0; i < haplos[gap].length; i++){
+                double[] crossPercentages = new double[haplos[gap+1].length];
+                StringBuffer firstHapCodeB = new StringBuffer(preGapSubset.size());
+                for (int j = 0; j < preGapSubset.size(); j++){   //make a string out of uniquely identifying genotypes for this hap
+                    firstHapCodeB.append(haplos[gap][i].getGeno()[((Integer)preGapSubset.elementAt(j)).intValue()]);
+                }
+                String firstHapCode = firstHapCodeB.toString();
+                for (int gapHaplo = 0; gapHaplo < crossHaplos.length; gapHaplo++){  //look at each crossover hap
+                    if (crossHaplos[gapHaplo].getPercentage() > CROSSOVER_THRESHOLD){
+                        StringBuffer gapBeginHapCodeB = new StringBuffer(preGapSubset.size());
+                        for (int j = 0; j < preGapSubset.size(); j++){     //make a string as above
+                            gapBeginHapCodeB.append(crossHaplos[gapHaplo].getGeno()[j]);
+                        }
+                        String gapBeginHapCode = gapBeginHapCodeB.toString();
+                        if (gapBeginHapCode.equals(firstHapCode)){    //if this crossover hap corresponds to this pregap hap
+                            StringBuffer gapEndHapCodeB = new StringBuffer(preGapSubset.size());
+                            for (int j = preGapSubset.size(); j < crossHaplos[gapHaplo].getGeno().length; j++){
+                                gapEndHapCodeB.append(crossHaplos[gapHaplo].getGeno()[j]);
+                            }
+                            String gapEndHapCode = gapEndHapCodeB.toString();
+                            for (int j = 0; j < haplos[gap+1].length; j++){
+                                StringBuffer endHapCodeB = new StringBuffer();
+                                for (int k = 0; k < postGapSubset.size(); k++){
+                                    endHapCodeB.append(haplos[gap+1][j].getGeno()[((Integer)postGapSubset.elementAt(k)).intValue()]);
+                                }
+                                String endHapCode = endHapCodeB.toString();
+                                if (gapEndHapCode.equals(endHapCode)){
+                                    crossPercentages[j] = crossHaplos[gapHaplo].getPercentage();
+                                }
+                            }
+                        }
+                    }
+                }
+                //thought i needed to fix these percentages, but the raw values are just as good.
+                /*		double percentageSum = 0;
+                double[] fixedCross = new double[crossPercentages.length];
+                for (int y = 0; y < crossPercentages.length; y++){
+                percentageSum += crossPercentages[y];
+                }
+                for (int y = 0; y < crossPercentages.length; y++){
+                fixedCross[y] = crossPercentages[y]/percentageSum;
+                }*/
+                haplos[gap][i].addCrossovers(crossPercentages);
+            }
+        }
+        return haplos;
+    }
+
+    Vector getBestSubset(Haplotype[] thisBlock){    //from a block of haps, find marker subset which uniquely id's all haps
+        Vector bestSubset = new Vector();
+        //first make an array with markers ranked by genotyping success rate
+        Vector genoSuccessRank = new Vector();
+        Vector genoNumberRank = new Vector();
+        int[] myMarkers = thisBlock[0].getMarkers();
+        genoSuccessRank.add(new Double(percentBadGenotypes[Chromosome.realIndex[myMarkers[0]]]));
+        genoNumberRank.add(new Integer(0));
+        for (int i = 1; i < myMarkers.length; i++){
+            boolean inserted = false;
+            for (int j = 0; j < genoSuccessRank.size(); j++){
+                if (percentBadGenotypes[Chromosome.realIndex[myMarkers[i]]] < ((Double)(genoSuccessRank.elementAt(j))).doubleValue()){
+                    genoSuccessRank.insertElementAt(new Double(percentBadGenotypes[Chromosome.realIndex[myMarkers[i]]]), j);
+                    genoNumberRank.insertElementAt(new Integer(i), j);
+                    inserted = true;
+                    break;
+                }
+            }
+            if (!(inserted)) {
+                genoNumberRank.add(new Integer(i));
+                genoSuccessRank.add(new Double(percentBadGenotypes[Chromosome.realIndex[myMarkers[i]]]));
+            }
+        }
+
+        for (int i = 0; i < thisBlock.length-1; i++){
+            int[] firstHap = thisBlock[i].getGeno();
+            for (int j = i+1; j < thisBlock.length; j++){
+                int[] secondHap = thisBlock[j].getGeno();
+                for (int y = 0; y < firstHap.length; y++){
+                    int x = ((Integer)(genoNumberRank.elementAt(y))).intValue();
+                    if (firstHap[x] != secondHap[x]){
+                        if (!(bestSubset.contains(new Integer(x)))){
+                            bestSubset.add(new Integer(x));
+                            break;
+                        } else {
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return bestSubset;
+    }
+
+    void guessBlocks(int method){
+        guessBlocks(method, blocks);
+    }
+
+    void guessBlocks(int method, Vector custVec){
+        Vector returnVec = new Vector();
+        switch(method){
+            case BLOX_GABRIEL: returnVec = FindBlocks.doGabriel(dpTable); break;
+            case BLOX_4GAM: returnVec = FindBlocks.do4Gamete(dpTable); break;
+            case BLOX_SPINE: returnVec = FindBlocks.doSpine(dpTable); break;
+            case BLOX_CUSTOM: returnVec = custVec; break;
+                //todo: bad! doesn't check if vector is out of bounds and stuff or blocks out of order
+            default: returnVec = new Vector(); break;
+        }
+        blocks = returnVec;
+        blocksChanged = true;
+
+        //keep track of which markers are in a block
+        isInBlock = new boolean[Chromosome.getSize()];
+        for (int i = 0; i < isInBlock.length; i++){
+            isInBlock[i] = false;
+        }
+        for (int i = 0; i < blocks.size(); i++){
+            int[] markers = (int[])blocks.elementAt(i);
+            for (int j = 0; j < markers.length; j++){
+                isInBlock[markers[j]] = true;
+            }
+        }
+    }
+
+    public void removeFromBlock(int markerNum) {
+        if (blocks != null){
+            OUTER: for (int i = 0; i < blocks.size(); i ++){
+                int thisBlock[] = (int[])blocks.elementAt(i);
+                int newBlock[] = new int[thisBlock.length-1];
+                int count = 0;
+                for (int j = 0; j < thisBlock.length; j++){
+                    if(markerNum == thisBlock[j]){
+                        blocksChanged = true;
+                        if (newBlock.length < 1){
+                            blocks.removeElementAt(i);
+                            for (int k = 0; k < thisBlock.length; k++){
+                                this.isInBlock[thisBlock[k]] = false;
+                            }
+                            break OUTER;
+                        }
+                        this.isInBlock[markerNum] = false;
+                        for (int k = 0; k < thisBlock.length; k++){
+                            if (!(k==j)){
+                                newBlock[count] = thisBlock[k];
+                                count++;
+                            }
+                        }
+                        blocks.setElementAt(newBlock, i);
+                        break OUTER;
+                    }
+                }
+            }
+        }
+    }
+
+    public void addMarkerIntoSurroundingBlock(int markerNum) {
+        if (blocks != null){
+            OUTER: for (int i = 0; i < blocks.size(); i ++){
+                int thisBlock[] = (int[])blocks.elementAt(i);
+                int newBlock[] = new int[thisBlock.length+1];
+                int count = 0;
+                if(markerNum > thisBlock[0] && markerNum < thisBlock[thisBlock.length-1]){
+                    blocksChanged = true;
+                    this.isInBlock[markerNum] = true;
+                    for (int j = 0; j < thisBlock.length; j++){
+                        newBlock[count] = thisBlock[j];
+                        count++;
+                        if (thisBlock[j] < markerNum && thisBlock[j+1] > markerNum){
+                            newBlock[count] = markerNum;
+                            count++;
+                        }
+                    }
+                    blocks.setElementAt(newBlock, i);
+                    break OUTER;
+                }
+            }
+        }
+    }
+
+    public void addBlock(int firstMarker, int lastMarker) {
+        if (firstMarker < 0){
+            firstMarker = 0;
+        }
+        if (lastMarker >= Chromosome.realIndex.length){
+            lastMarker = Chromosome.realIndex.length-1;
+        }
+        if (lastMarker - firstMarker < 0){
+            //something wonky going on
+            return;
+        }
+
+        int inArray[] = new int[lastMarker-firstMarker+1];
+        blocksChanged = true;
+        if (blocks.size() != 0){
+            boolean placed = false;
+            for (int i = 0; i < blocks.size(); i++){
+                int currentBlock[] = (int[])blocks.elementAt(i);
+                //trim out any blocks that are overlapped
+                if ((lastMarker >= currentBlock[0] && firstMarker <= currentBlock[currentBlock.length-1]) ||
+                        firstMarker <= currentBlock[currentBlock.length-1] && firstMarker >= currentBlock[0]){
+                    for (int j = 0; j < currentBlock.length; j++){
+                        isInBlock[currentBlock[j]] = false;
+                    }
+                    blocks.removeElementAt(i);
+                    i--;
+                }
+            }
+            for (int i = 0; i < blocks.size(); i++){
+                int currentBlock[] = (int[])blocks.elementAt(i);
+                if (firstMarker <= currentBlock[0] && !placed){
+                    blocks.insertElementAt(inArray,i);
+                    placed = true;
+                }
+            }
+            if (!placed){
+                blocks.add(inArray);
+            }
+        }else{
+            blocks.add(inArray);
+        }
+        for (int i = 0; i < inArray.length; i++){
+            inArray[i] = firstMarker+i;
+            this.isInBlock[firstMarker+i] = true;
+        }
+    }
+
+    //this method computes the dPrime value for the pair of markers pos1,pos2.
+    //the method assumes that the given pair are not too far apart (ie the distance
+    //between them is less than maximum distance).
+    //NOTE: the values of pos1,pos2 should be unfiltered marker numbers.
+    public PairwiseLinkage computeDPrime(int pos1, int pos2){
+        int doublehet = 0;
+        int[][] twoMarkerHaplos = new int[3][3];
+
+        for (int i = 0; i < twoMarkerHaplos.length; i++){
+            for (int j = 0; j < twoMarkerHaplos[i].length; j++){
+                twoMarkerHaplos[i][j] = 0;
+            }
+        }
+
+        //check for non-polymorphic markers
+        if (Chromosome.getUnfilteredMarker(pos1).getMAF() == 0 || Chromosome.getUnfilteredMarker(pos2).getMAF() == 0){
+            return null;
+        }
+
+        int[] marker1num = new int[5]; int[] marker2num = new int[5];
+
+        marker1num[0]=0;
+        marker1num[Chromosome.getUnfilteredMarker(pos1).getMajor()]=1;
+        marker1num[Chromosome.getUnfilteredMarker(pos1).getMinor()]=2;
+        marker2num[0]=0;
+        marker2num[Chromosome.getUnfilteredMarker(pos2).getMajor()]=1;
+        marker2num[Chromosome.getUnfilteredMarker(pos2).getMinor()]=2;
+
+        byte a1,a2,b1,b2;
+        //iterate through all chromosomes in dataset
+        for (int i = 0; i < chromosomes.size(); i++){
+            //assign alleles for each of a pair of chromosomes at a marker to four variables
+
+            if(!((Chromosome)chromosomes.elementAt(i)).isHaploid()){
+
+                a1 = ((Chromosome) chromosomes.elementAt(i)).genotypes[pos1];
+                a2 = ((Chromosome) chromosomes.elementAt(i)).genotypes[pos2];
+                b1 = ((Chromosome) chromosomes.elementAt(++i)).genotypes[pos1];
+                b2 = ((Chromosome) chromosomes.elementAt(i)).genotypes[pos2];
+
+                if (a1 == 0 || a2 == 0 || b1 == 0 || b2 == 0){
+                    //skip missing data
+                } else if (((a1 >= 5 || b1 >= 5) && (a2 >= 5 || b2 >= 5)) || (a1 >= 5 && !(a2 == b2)) || (a2 >= 5 && !(a1 == b1))){
+                    doublehet++;
+                    //find doublehets and resolved haplotypes
+                } else if (a1 >= 5 || b1 >= 5){
+                    twoMarkerHaplos[1][marker2num[a2]]++;
+                    twoMarkerHaplos[2][marker2num[a2]]++;
+                } else if (a2 >= 5 || b2 >= 5){
+                    twoMarkerHaplos[marker1num[a1]][1]++;
+                    twoMarkerHaplos[marker1num[a1]][2]++;
+                } else {
+                    twoMarkerHaplos[marker1num[a1]][marker2num[a2]]++;
+                    twoMarkerHaplos[marker1num[b1]][marker2num[b2]]++;
+                }
+            }else {
+                //haploid (x chrom)
+                a1 = ((Chromosome) chromosomes.elementAt(i)).genotypes[pos1];
+                a2 = ((Chromosome) chromosomes.elementAt(i)).genotypes[pos2];
+
+                if(a1 != 0 && a2 != 0) {
+                    twoMarkerHaplos[marker1num[a1]][marker2num[a2]]++;
+                }
+
+            }
+        }
+        //another monomorphic marker check
+        int r1, r2, c1, c2;
+        r1 = twoMarkerHaplos[1][1] + twoMarkerHaplos[1][2];
+        r2 = twoMarkerHaplos[2][1] + twoMarkerHaplos[2][2];
+        c1 = twoMarkerHaplos[1][1] + twoMarkerHaplos[2][1];
+        c2 = twoMarkerHaplos[1][2] + twoMarkerHaplos[2][2];
+        if ( (r1==0 || r2==0 || c1==0 || c2==0) && doublehet == 0){
+            return  new PairwiseLinkage(1,0,0,0,0,new double[0]);
+        }
+
+        //compute D Prime for this pair of markers.
+        //return is a tab delimited string of d', lod, r^2, CI(low), CI(high)
+
+        int i,count;
+        //int j,k,itmp;
+        int low_i = 0;
+        int high_i = 0;
+        double loglike, oldloglike;// meand, mean2d, sd;
+        double tmp;//g,h,m,tmp,r;
+        double num, denom1, denom2, denom, dprime;//, real_dprime;
+        double pA1, pB1, pA2, pB2, loglike1, loglike0, rsq;
+        double tmpAA, tmpAB, tmpBA, tmpBB, dpr;// tmp2AA, tmp2AB, tmp2BA, tmp2BB;
+        double total_prob, sum_prob;
+        double lsurface[] = new double[101];
+
+        /* store arguments in externals and compute allele frequencies */
+
+        known[AA]=twoMarkerHaplos[1][1];
+        known[AB]=twoMarkerHaplos[1][2];
+        known[BA]=twoMarkerHaplos[2][1];
+        known[BB]=twoMarkerHaplos[2][2];
+        unknownDH=doublehet;
+        total_chroms= (int)(known[AA]+known[AB]+known[BA]+known[BB]+(2*unknownDH));
+        pA1 = (known[AA]+known[AB]+unknownDH) / (double) total_chroms;
+        pB1 = 1.0-pA1;
+        pA2 = (known[AA]+known[BA]+unknownDH) / (double) total_chroms;
+        pB2 = 1.0-pA2;
+        const_prob = 0.1;
+
+        /* set initial conditions */
+
+        if (const_prob < 0.00) {
+            probHaps[AA]=pA1*pA2;
+            probHaps[AB]=pA1*pB2;
+            probHaps[BA]=pB1*pA2;
+            probHaps[BB]=pB1*pB2;
+        } else {
+            probHaps[AA]=const_prob;
+            probHaps[AB]=const_prob;
+            probHaps[BA]=const_prob;
+            probHaps[BB]=const_prob;;
+
+            /* so that the first count step will produce an
+            initial estimate without inferences (this should
+            be closer and therefore speedier than assuming
+            they are all at equal frequency) */
+
+            count_haps(0);
+            estimate_p();
+        }
+
+        /* now we have an initial reasonable guess at p we can
+        start the EM - let the fun begin */
+
+        const_prob=0.0;
+        count=1; loglike=-999999999.0;
+
+        do {
+            oldloglike=loglike;
+            count_haps(count);
+            loglike = (known[AA]*Math.log(probHaps[AA]) + known[AB]*Math.log(probHaps[AB]) + known[BA]*Math.log(probHaps[BA]) + known[BB]*Math.log(probHaps[BB]))/LN10 + ((double)unknownDH*Math.log(probHaps[AA]*probHaps[BB] + probHaps[AB]*probHaps[BA]))/LN10;
+            if (Math.abs(loglike-oldloglike) < TOLERANCE) break;
+            estimate_p();
+            count++;
+        } while(count < 1000);
+        /* in reality I've never seen it need more than 10 or so iterations
+        to converge so this is really here just to keep it from running off into eternity */
+
+        loglike1 = (known[AA]*Math.log(probHaps[AA]) + known[AB]*Math.log(probHaps[AB]) + known[BA]*Math.log(probHaps[BA]) + known[BB]*Math.log(probHaps[BB]) + (double)unknownDH*Math.log(probHaps[AA]*probHaps[BB] + probHaps[AB]*probHaps[BA]))/LN10;
+        loglike0 = (known[AA]*Math.log(pA1*pA2) + known[AB]*Math.log(pA1*pB2) + known[BA]*Math.log(pB1*pA2) + known[BB]*Math.log(pB1*pB2) + (double)unknownDH*Math.log(2*pA1*pA2*pB1*pB2))/LN10;
+
+        num = probHaps[AA]*probHaps[BB] - probHaps[AB]*probHaps[BA];
+
+        if (num < 0) {
+            /* flip matrix so we get the positive D' */
+            /* flip AA with AB and BA with BB */
+            tmp=probHaps[AA]; probHaps[AA]=probHaps[AB]; probHaps[AB]=tmp;
+            tmp=probHaps[BB]; probHaps[BB]=probHaps[BA]; probHaps[BA]=tmp;
+            /* flip frequency of second allele */
+            //done in this slightly asinine way because of a compiler bugz0r in the dec-alpha version of java
+            //which causes it to try to parallelize the swapping operations and mis-schedules them
+            pA2 = pA2 + pB2;
+            pB2 = pA2 - pB2;
+            pA2 = pA2 - pB2;
+            //pA2=pB2;pB2=temp;
+            /* flip counts in the same fashion as p's */
+            tmp=numHaps[AA]; numHaps[AA]=numHaps[AB]; numHaps[AB]=tmp;
+            tmp=numHaps[BB]; numHaps[BB]=numHaps[BA]; numHaps[BA]=tmp;
+            /* num has now undergone a sign change */
+            num = probHaps[AA]*probHaps[BB] - probHaps[AB]*probHaps[BA];
+            /* flip known array for likelihood computation */
+            tmp=known[AA]; known[AA]=known[AB]; known[AB]=tmp;
+            tmp=known[BB]; known[BB]=known[BA]; known[BA]=tmp;
+        }
+
+        denom1 = (probHaps[AA]+probHaps[BA])*(probHaps[BA]+probHaps[BB]);
+        denom2 = (probHaps[AA]+probHaps[AB])*(probHaps[AB]+probHaps[BB]);
+        if (denom1 < denom2) { denom = denom1; }
+        else { denom = denom2; }
+        dprime = num/denom;
+
+        /* add computation of r^2 = (D^2)/p(1-p)q(1-q) */
+        rsq = num*num/(pA1*pB1*pA2*pB2);
+
+        //real_dprime=dprime;
+
+        for (i=0; i<=100; i++) {
+            dpr = (double)i*0.01;
+            tmpAA = dpr*denom + pA1*pA2;
+            tmpAB = pA1-tmpAA;
+            tmpBA = pA2-tmpAA;
+            tmpBB = pB1-tmpBA;
+            if (i==100) {
+                /* one value will be 0 */
+                if (tmpAA < 1e-10) tmpAA=1e-10;
+                if (tmpAB < 1e-10) tmpAB=1e-10;
+                if (tmpBA < 1e-10) tmpBA=1e-10;
+                if (tmpBB < 1e-10) tmpBB=1e-10;
+            }
+            lsurface[i] = (known[AA]*Math.log(tmpAA) + known[AB]*Math.log(tmpAB) + known[BA]*Math.log(tmpBA) + known[BB]*Math.log(tmpBB) + (double)unknownDH*Math.log(tmpAA*tmpBB + tmpAB*tmpBA))/LN10;
+        }
+
+        /* Confidence bounds #2 - used in Gabriel et al (2002) - translate into posterior dist of D' -
+        assumes a flat prior dist. of D' - someday we may be able to make
+        this even more clever by adjusting given the distribution of observed
+        D' values for any given distance after some large scale studies are complete */
+
+        total_prob=sum_prob=0.0;
+
+        for (i=0; i<=100; i++) {
+            lsurface[i] -= loglike1;
+            lsurface[i] = Math.pow(10.0,lsurface[i]);
+            total_prob += lsurface[i];
+        }
+
+        for (i=0; i<=100; i++) {
+            sum_prob += lsurface[i];
+            if (sum_prob > 0.05*total_prob &&
+                    sum_prob-lsurface[i] < 0.05*total_prob) {
+                low_i = i-1;
+                break;
+            }
+        }
+
+        sum_prob=0.0;
+        for (i=100; i>=0; i--) {
+            sum_prob += lsurface[i];
+            if (sum_prob > 0.05*total_prob &&
+                    sum_prob-lsurface[i] < 0.05*total_prob) {
+                high_i = i+1;
+                break;
+            }
+        }
+        if (high_i > 100){ high_i = 100; }
+
+
+
+        double[] freqarray = {probHaps[AA], probHaps[AB], probHaps[BB], probHaps[BA]};
+
+        return new PairwiseLinkage(Util.roundDouble(dprime,3), Util.roundDouble((loglike1-loglike0),2),
+                Util.roundDouble(rsq,3), ((double)low_i/100.0), ((double)high_i/100.0), freqarray);
+    }
+
+    public void count_haps(int em_round){
+        /* only the double heterozygote [AB][AB] results in
+        ambiguous reconstruction, so we'll count the obligates
+        then tack on the [AB][AB] for clarity */
+
+        numHaps[AA] = known[AA];
+        numHaps[AB] = known[AB];
+        numHaps[BA] = known[BA];
+        numHaps[BB] = known[BB];
+        if (em_round > 0) {
+            numHaps[AA] += unknownDH* (probHaps[AA]*probHaps[BB])/((probHaps[AA]*probHaps[BB])+(probHaps[AB]*probHaps[BA]));
+            numHaps[BB] += unknownDH* (probHaps[AA]*probHaps[BB])/((probHaps[AA]*probHaps[BB])+(probHaps[AB]*probHaps[BA]));
+            numHaps[AB] += unknownDH* (probHaps[AB]*probHaps[BA])/((probHaps[AA]*probHaps[BB])+(probHaps[AB]*probHaps[BA]));
+            numHaps[BA] += unknownDH* (probHaps[AB]*probHaps[BA])/((probHaps[AA]*probHaps[BB])+(probHaps[AB]*probHaps[BA]));
+        }
+    }
+
+    public void estimate_p() {
+        double total= numHaps[AA]+numHaps[AB]+numHaps[BA]+numHaps[BB]+(4.0*const_prob);
+        probHaps[AA]=(numHaps[AA]+const_prob)/total; if (probHaps[AA] < 1e-10) probHaps[AA]=1e-10;
+        probHaps[AB]=(numHaps[AB]+const_prob)/total; if (probHaps[AB] < 1e-10) probHaps[AB]=1e-10;
+        probHaps[BA]=(numHaps[BA]+const_prob)/total; if (probHaps[BA] < 1e-10) probHaps[BA]=1e-10;
+        probHaps[BB]=(numHaps[BB]+const_prob)/total; if (probHaps[BB] < 1e-10) probHaps[BB]=1e-10;
+    }
+
+    public void saveHapsToText(Haplotype[][] finishedHaplos, double[] multidprime,
+                               File saveHapsFile) throws IOException{
+
+        if (finishedHaplos == null) return;
+
+        NumberFormat nf = NumberFormat.getInstance(Locale.US);
+        nf.setGroupingUsed(false);
+        nf.setMinimumFractionDigits(3);
+        nf.setMaximumFractionDigits(3);
+
+        //open file for saving haps text
+        FileWriter saveHapsWriter = new FileWriter(saveHapsFile);
+
+        //go through each block and print haplos
+        for (int i = 0; i < finishedHaplos.length; i++){
+            //write block header
+            int[] markerNums = finishedHaplos[i][0].getMarkers();
+
+            saveHapsWriter.write("BLOCK " + (i+1) + ".  MARKERS:");
+            boolean[] tags = finishedHaplos[i][0].getTags();
+            for (int j = 0; j < markerNums.length; j++){
+                saveHapsWriter.write(" " + (Chromosome.realIndex[markerNums[j]]+1));
+                if (Options.isShowBlockTags()){
+                    if (tags[j]) saveHapsWriter.write("!");
+                }
+            }
+            saveHapsWriter.write("\n");
+            //write haps and crossover percentages
+            for (int j = 0; j < finishedHaplos[i].length; j++){
+                if((finishedHaplos[i][j].getPercentage()) >= Options.getHaplotypeDisplayThreshold()) {
+                    int[] theGeno = finishedHaplos[i][j].getGeno();
+                    StringBuffer theHap = new StringBuffer(theGeno.length);
+                    for (int k = 0; k < theGeno.length; k++){
+                        theHap.append(theGeno[k]);
+                    }
+                    saveHapsWriter.write(theHap.toString() + " (" + nf.format(finishedHaplos[i][j].getPercentage()) + ")");
+                    if (i < finishedHaplos.length-1){
+                        saveHapsWriter.write("\t|");
+                        boolean writeTab = false;
+                        for (int crossCount = 0; crossCount < finishedHaplos[i+1].length; crossCount++){
+                            if((finishedHaplos[i+1][crossCount].getPercentage()) >= Options.getHaplotypeDisplayThreshold() ) {
+                                if (crossCount != 0 && writeTab) saveHapsWriter.write("\t");
+                                saveHapsWriter.write(nf.format(finishedHaplos[i][j].getCrossover(crossCount)));
+                                writeTab = true;
+                            }
+                        }
+                        saveHapsWriter.write("|");
+                    }
+                    saveHapsWriter.write("\n");
+                }
+            }
+            if (i < finishedHaplos.length - 1){
+                saveHapsWriter.write("Multiallelic Dprime: " + nf.format(multidprime[i]) + "\n");
+            }
+
+        }
+
+
+        saveHapsWriter.close();
+    }
+
+    public void saveDprimeToText(File dumpDprimeFile, int source, int start, int stop) throws IOException{
+        FileWriter saveDprimeWriter = new FileWriter(dumpDprimeFile);
+        //we use this LinkedList to store the dprime computations for a window of 5 markers
+        //in either direction in the for loop farther down.
+        //a tInt value is calculated for each marker which requires the dprime calculations
+        //for the 5 markers before and 5 after the current marker, so we store these values while we need
+        //them to avoid unnecesary recomputation.
+        LinkedList savedDPrimes = new LinkedList();
+        long dist;
+        if (infoKnown){
+            saveDprimeWriter.write("L1\tL2\tD'\tLOD\tr^2\tCIlow\tCIhi\tDist\tT-int\n");
+        }else{
+            saveDprimeWriter.write("L1\tL2\tD'\tLOD\tr^2\tCIlow\tCIhi\tT-int\n");
+        }
+
+        boolean adj = false;
+        if (start == -1 && stop == -1){
+            //user selected "adjacent markers" option
+            start = 0; stop = Chromosome.getSize();
+            adj = true;
+        }
+
+        if (start < 0){
+            start = 0;
+        }
+        if (stop > Chromosome.getSize()){
+            stop = Chromosome.getSize();
+        }
+
+        PairwiseLinkage currComp = null;
+
+        //initialize the savedDPrimes linkedlist with 10 empty arrays of size 10
+        PairwiseLinkage[] pwlArray = new PairwiseLinkage[10];
+        savedDPrimes.add(pwlArray);
+        for(int k=0;k<4;k++) {
+            savedDPrimes.add(pwlArray.clone());
+        }
+
+        PairwiseLinkage[] tempArray;
+        if(source != TABLE_TYPE) {
+            //savedDPrimes is a linkedlist which stores 5 arrays at all times.
+            //it temporarily stores a set of computeDPrime() results so that we do not
+            //do them over and over
+            //each array contains 10 PairwiseLinkage objects.
+            //each array contains the PairwiseLinkage objects for a marker being compared to the 10 previous markers.
+            //if marker1 is some marker number, and tempArray is one of the arrays in savedDPrimes, then:
+            //      tempArray[0] = computeDPrime(marker1 - 10, marker1)
+            //      tempArray[9] = computeDPrime(marker1 - 1, marker1)
+            //if the value of marker1 is less than 10, then the array is filled with nulls up the first valid comparison
+            //that is, if marker1 = 5, then:
+            //      tempArray[0] through tempArray[4] are null
+            //      tempArray[5] = computeDPrime(marker1-5,marker1)
+            for(int m=1;m<6;m++) {
+                tempArray = (PairwiseLinkage[]) savedDPrimes.get(m-1);
+
+                for(int n=1;n<11;n++){
+                    if( (start+m) >= Chromosome.getSize() || (start+m-n)<0) {
+                        tempArray[10-n] = null;
+                    }
+                    else {
+                        //the next line used to have Chromosome.getUnfilteredMarker(Chromosome.realIndex[])
+                        //but it seems like this should do the same thing
+                        long sep = Chromosome.getMarker(start+m).getPosition()
+                                - Chromosome.getMarker(start+m-n).getPosition();
+                        if(Options.getMaxDistance() == 0 || sep < Options.getMaxDistance() )  {
+                            tempArray[10-n] = this.computeDPrime(Chromosome.realIndex[start+m-n],Chromosome.realIndex[start+m]);
+                            //tempArray[10-n] = "" + (start+m-n) + "," + (start+m) ;
+                        }
+                    }
+                }
+            }
+        }
+
+
+        for (int i = start; i < stop; i++){
+
+            if(source != TABLE_TYPE && i != start) {
+                //here the first element of savedDPrimes is discarded.
+                //the array of results for the marker (i+5) is then computed and added to the end of savedDPrimes
+
+                tempArray = (PairwiseLinkage[]) savedDPrimes.removeFirst();
+
+                for(int m=0;m<10;m++) {
+                    tempArray[m] = null;
+                }
+                for(int n=1;n<11;n++){
+                    if(i+5 < Chromosome.getSize() && (i+5-n) >=0) {
+                        long sep = Chromosome.getMarker(i+5).getPosition()
+                                - Chromosome.getMarker(i+5-n).getPosition();
+                        if(Options.getMaxDistance() == 0 || sep < Options.getMaxDistance() )  {
+                            tempArray[10-n] = this.computeDPrime(Chromosome.realIndex[i+5-n],Chromosome.realIndex[i+5]);
+                        }
+                    }
+                }
+                savedDPrimes.addLast(tempArray);
+            }
+
+
+            for (int j = i+1; j < stop; j++){
+                if (adj){
+                    if (!(i == j-1)){
+                        continue;
+                    }
+                }
+                if (source == TABLE_TYPE){
+
+                    currComp = dpTable.getLDStats(i,j);
+                }else{
+                    long sep = Chromosome.getMarker(i).getPosition()
+                            - Chromosome.getMarker(j).getPosition();
+                    if(Options.getMaxDistance() == 0 || Math.abs(sep) < Options.getMaxDistance() )  {
+                        currComp = this.computeDPrime(Chromosome.realIndex[i],Chromosome.realIndex[j]);
+                    } else {
+                        currComp = null;
+                    }
+                }
+                if(currComp != null) {
+                    double LODSum = 0;
+                    String tInt = "-";
+                    if (i == j-1){
+                        //these are adjacent markers so we'll put in the t-int stat
+                        for (int x = 0; x < 5; x++){
+                            for (int y = 1; y < 6; y++){
+                                if (i-x < 0 || i+y >= Chromosome.getSize()){
+                                    continue;
+                                }
+                                tempArray = (PairwiseLinkage[]) savedDPrimes.get(y-1);
+                                PairwiseLinkage tintPair = null;
+                                if (source == TABLE_TYPE){
+                                    tintPair = dpTable.getLDStats(i-x,i+y);
+                                }else{
+                                    long sep = Chromosome.getUnfilteredMarker(Chromosome.realIndex[i-x]).getPosition()
+                                            - Chromosome.getUnfilteredMarker(Chromosome.realIndex[i+y]).getPosition();
+                                    if(Options.getMaxDistance() == 0 || Math.abs(sep) < Options.getMaxDistance() )  {
+
+                                        tintPair = tempArray[9-x-y+1];
+                                        /*tintPair = this.computeDPrime(Chromosome.realIndex[i-x],
+                                                Chromosome.realIndex[i+y]);
+                                        if(tempArray[9-x-y+1] == null) {
+                                            System.out.println("storage is null ");
+                                        }
+                                        if(tintPair == null) {
+                                            System.out.println("tintPair is null");
+                                        }
+                                        if(tempArray[9-x-y+1] == tintPair ||
+                                                (tintPair != null && tempArray[9-x-y+1] != null && tintPair.getLOD() == tempArray[9-x-y+1].getLOD())) {
+                                                System.out.println("match");
+                                        }
+                                        else {
+                                            System.out.println("dont match");
+                                        }*/
+                                    }
+                                }
+                                if (tintPair != null){
+                                    LODSum += tintPair.getLOD();
+                                }
+                            }
+                        }
+                        tInt = String.valueOf(Util.roundDouble(LODSum,2));
+                    }
+                    if (infoKnown){
+                        dist = (Chromosome.getMarker(j)).getPosition() - (Chromosome.getMarker(i)).getPosition();
+                        saveDprimeWriter.write(Chromosome.getMarker(i).getName() +
+                                "\t" + Chromosome.getMarker(j).getName() +
+                                "\t" + currComp.toString() + "\t" + dist + "\t" + tInt +"\n");
+                    }else{
+                        saveDprimeWriter.write((Chromosome.realIndex[i]+1) + "\t" + (Chromosome.realIndex[j]+1) +
+                                "\t" + currComp.toString() + "\t" + tInt + "\n");
+                    }
+                }
+            }
+        }
+        saveDprimeWriter.close();
+    }
+
+    public void readAnalysisTrack(InputStream inStream) throws HaploViewException, IOException{
+        //clear out the vector of old values
+
+        if (inStream == null){
+            throw new HaploViewException("Custom analysis track file doesn't exist!");
+        }
+
+        XYSeries xys = new XYSeries(new Integer(analysisTracks.getSeriesCount()));
+        BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
+        String currentLine;
+        int lineCount = 0;
+        while ((currentLine = in.readLine()) != null){
+            lineCount ++;
+            StringTokenizer st = new StringTokenizer(currentLine);
+
+            if (st.countTokens() == 1){
+                //complain if we have only one col
+                throw new HaploViewException("File error on line " + lineCount + " in the custom analysis track file");
+            }else if (st.countTokens() == 0){
+                //skip blank lines
+                continue;
+            }
+            Double pos, val;
+            try{
+                pos = new Double(st.nextToken());
+                val = new Double(st.nextToken());
+            }catch (NumberFormatException nfe) {
+                throw new HaploViewException("Format error on line " + lineCount + " in the custom analysis track file");
+            }
+            xys.add(pos,val);
+        }
+        analysisTracks.addSeries(xys);
+        trackExists = true;
+    }
+
+    public Vector readBlocks(InputStream inStream) throws HaploViewException, IOException{
+        if (inStream == null){
+            throw new HaploViewException("Blocks file doesn't exist!");
+        }
+
+        Vector cust = new Vector();
+        BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
+        String currentLine;
+        int lineCount = 0;
+        int highestYet = -1;
+        while ((currentLine = in.readLine()) != null){
+            lineCount ++;
+            StringTokenizer st = new StringTokenizer(currentLine);
+
+            if (st.countTokens() == 0){
+                //skip blank lines
+                continue;
+            }
+            try{
+                Vector goodies = new Vector();
+                while (st.hasMoreTokens()){
+                    Integer nextInLine = new Integer(st.nextToken());
+                    for (int y = 0; y < Chromosome.realIndex.length; y++){
+                        //we only keep markers from the input file that are "good" from checkdata
+                        //we also realign the input file to the current "good" subset since input file is
+                        //indexed of all possible markers in the dataset
+                        if (Chromosome.realIndex[y] == nextInLine.intValue() - 1){
+                            goodies.add(new Integer(y));
+                        }
+                    }
+                }
+                int thisBlock[] = new int[goodies.size()];
+                for (int x = 0; x < goodies.size(); x++){
+                    thisBlock[x] = ((Integer)goodies.elementAt(x)).intValue();
+                    if (thisBlock[x] > Chromosome.getUnfilteredSize() || thisBlock[x] < 0){
+                        throw new HaploViewException("Error, marker in block out of bounds: " + thisBlock[x] +
+                                "\non line " + lineCount);
+                    }
+                    if (thisBlock[x] <= highestYet){
+                        throw new HaploViewException("Error, markers/blocks out of order or overlap:\n" +
+                                "on line " + lineCount);
+                    }
+                    highestYet = thisBlock[x];
+                }
+                if (thisBlock.length > 0){
+                    cust.add(thisBlock);
+                }
+            }catch (NumberFormatException nfe) {
+                throw new HaploViewException("Format error on line " + lineCount + " in the blocks file");
+            }
+        }
+        return cust;
+    }
+
+    public Haplotype[][] getHaplotypes() {
+        return haplotypes;
+    }
+
+    public Vector getChromosomes() {
+        return chromosomes;
+    }
+
+    public Haplotype[][] getRawHaplotypes() {
+        return rawHaplotypes;
+    }
+
+    public class RSquared {
+        private double[] rsquareds;
+        private double[] conditionalProbs;
+
+        public RSquared(double[] rsquareds, double[] conditionalProbs) {
+            this.rsquareds = rsquareds;
+            this.conditionalProbs = conditionalProbs;
+        }
+
+        public double[] getRsquareds() {
+            return rsquareds;
+        }
+
+        public double[] getConditionalProbs() {
+            return conditionalProbs;
+        }
+    }
+
+    public RSquared getPhasedRSquared(int snp, int[] block){
+
+        double rsquareds[] = null;
+        double conditionalProbs[] = null;
+        double alleleCounts[][] = null;
+        int maxIndex =0;
+        int[] multiMarkerHaplos = null;
+        boolean monomorphic = false;
+
+        if(block.length == 2){
+            multiMarkerHaplos = new int[8];
+
+            int pos1 = snp;
+            int pos2 = block[0];
+            int pos3 = block[1];
+
+            int[] marker1num = new int[5]; int[] marker2num = new int[5]; int[] marker3num = new int[5];
+
+            marker1num[Chromosome.getMarker(pos1).getMajor()]=0;
+            marker1num[Chromosome.getMarker(pos1).getMinor()]=4;
+            marker2num[Chromosome.getMarker(pos2).getMajor()]=0;
+            marker2num[Chromosome.getMarker(pos2).getMinor()]=2;
+            marker3num[Chromosome.getMarker(pos3).getMajor()]=0;
+            marker3num[Chromosome.getMarker(pos3).getMinor()]=1;
+
+            alleleCounts = new double[3][5];
+
+            byte a1,a2,a3,b1,b2,b3;
+            //iterate through all chromosomes in dataset
+            for (int i = 0; i < chromosomes.size(); i++){
+
+                if(!((Chromosome)chromosomes.elementAt(i)).isHaploid()){
+
+                    a1 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos1);
+                    a2 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos2);
+                    a3 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos3);
+                    b1 = ((Chromosome) chromosomes.elementAt(++i)).getGenotype(pos1);
+                    b2 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos2);
+                    b3 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos3);
+
+                    multiMarkerHaplos[marker1num[a1] + marker2num[a2] + marker3num[a3]]++;
+                    multiMarkerHaplos[marker1num[b1] + marker2num[b2] + marker3num[b3]]++;
+                    alleleCounts[0][a1]++;
+                    alleleCounts[0][b1]++;
+                    alleleCounts[1][a2]++;
+                    alleleCounts[1][b2]++;
+                    alleleCounts[2][a3]++;
+                    alleleCounts[2][b3]++;
+                }else {
+                    //haploid
+                    a1 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos1);
+                    a2 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos2);
+                    a3 =  ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos3);
+
+                    multiMarkerHaplos[marker1num[a1] +  marker2num[a2] + marker3num[a3]]++;
+                }
+            }
+            //check for any monomorphic SNPs
+            if(alleleCounts[0][Chromosome.getMarker(pos1).getMajor()] == 0 || alleleCounts[0][Chromosome.getMarker(pos1).getMinor()] == 0
+                    || alleleCounts[1][Chromosome.getMarker(pos2).getMajor()] == 0 || alleleCounts[1][Chromosome.getMarker(pos2).getMinor()] == 0
+                    || alleleCounts[2][Chromosome.getMarker(pos3).getMajor()] == 0 || alleleCounts[2][Chromosome.getMarker(pos3).getMinor()] == 0){
+                monomorphic = true;
+            }
+            maxIndex = 4;
+        }else if (block.length == 3){
+            multiMarkerHaplos = new int[16];
+
+              int pos1 = snp;
+            int pos2 = block[0];
+            int pos3 = block[1];
+            int pos4 = block[2];
+
+            int[] marker1num = new int[5];
+            int[] marker2num = new int[5];
+            int[] marker3num = new int[5];
+            int[] marker4num = new int[5];
+
+            marker1num[Chromosome.getMarker(pos1).getMinor()]=8;
+            marker2num[Chromosome.getMarker(pos2).getMinor()]=4;
+            marker3num[Chromosome.getMarker(pos3).getMinor()]=2;
+            marker4num[Chromosome.getMarker(pos4).getMinor()]=1;
+
+            alleleCounts = new double[4][5];
+
+            byte a1,a2,a3,a4,b1,b2,b3,b4;
+            //iterate through all chromosomes in dataset
+            for (int i = 0; i < chromosomes.size(); i++){
+
+                if(!((Chromosome)chromosomes.elementAt(i)).isHaploid()){
+
+                    a1 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos1);
+                    a2 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos2);
+                    a3 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos3);
+                    a4 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos4);
+                    b1 = ((Chromosome) chromosomes.elementAt(++i)).getGenotype(pos1);
+                    b2 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos2);
+                    b3 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos3);
+                    b4 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos4);
+
+                    multiMarkerHaplos[marker1num[a1] + marker2num[a2] + marker3num[a3] + marker4num[a4]]++;
+                    multiMarkerHaplos[marker1num[b1] + marker2num[b2] + marker3num[b3] + marker4num[b4]]++;
+
+                    alleleCounts[0][a1]++;
+                    alleleCounts[0][b1]++;
+                    alleleCounts[1][a2]++;
+                    alleleCounts[1][b2]++;
+                    alleleCounts[2][a3]++;
+                    alleleCounts[2][b3]++;
+                    alleleCounts[3][a4]++;
+                    alleleCounts[3][b4]++;
+                }else {
+                    //haploid
+                    a1 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos1);
+                    a2 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos2);
+                    a3 =  ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos3);
+                    a4 = ((Chromosome) chromosomes.elementAt(i)).getGenotype(pos4);
+
+                    multiMarkerHaplos[marker1num[a1] +  marker2num[a2] + marker3num[a3] + marker4num[a4]]++;
+                }
+            }
+            if(alleleCounts[0][Chromosome.getMarker(pos1).getMajor()] == 0 || alleleCounts[0][Chromosome.getMarker(pos1).getMinor()] == 0
+                    || alleleCounts[1][Chromosome.getMarker(pos2).getMajor()] == 0 || alleleCounts[1][Chromosome.getMarker(pos2).getMinor()] == 0
+                    || alleleCounts[2][Chromosome.getMarker(pos3).getMajor()] == 0 || alleleCounts[2][Chromosome.getMarker(pos3).getMinor()] == 0
+                    || alleleCounts[3][Chromosome.getMarker(pos4).getMajor()] == 0 || alleleCounts[3][Chromosome.getMarker(pos4).getMinor()] == 0){
+                monomorphic = true;
+            }
+            maxIndex =8;
+        }
+        //the rest of the code is the same for 2 and 3 marker blocks
+        rsquareds = new double[maxIndex];
+        conditionalProbs = new double[maxIndex];
+
+        if(monomorphic){
+            Arrays.fill(rsquareds,0);
+            Arrays.fill(conditionalProbs,0);
+            return new RSquared(rsquareds,conditionalProbs);
+        }
+
+        int totalChroms=0;
+
+        for(int i = 0;i < multiMarkerHaplos.length;i++){
+            totalChroms += multiMarkerHaplos[i];
+        }
+
+        double[] freqs = new double[multiMarkerHaplos.length];
+        for(int i=0;i<freqs.length;i++){
+            freqs[i] = multiMarkerHaplos[i]/(double)totalChroms;
+        }
+
+        double p=0;
+        for(int i=0;i< maxIndex; i++){
+            p += freqs[i];
+        }
+
+        for(int i =0;i< maxIndex;i++){
+            //calculate r^2
+            double aa = freqs[i];
+            double ab = p - freqs[i];
+            double ba = freqs[i+maxIndex];
+            double bb = (1-p) - freqs[i+maxIndex];
+
+            double q = ba + aa;
+            double c = aa*bb - ab*ba;
+            rsquareds[i] = Util.roundDouble((c*c)/(p*(1-p)*q*(1-q)),3);
+
+            //calculate conditional prob (ie P(snp | hap))
+            conditionalProbs[i] = freqs[i]/(freqs[i] + freqs[i+maxIndex]);
+        }
+
+        return new RSquared(rsquareds,conditionalProbs);
+    }
+
+
+    public static boolean isPhasedData() {
+        return phasedData;
+    }
+
+    public static void setPhasedData(boolean phasedData) {
+        HaploData.phasedData = phasedData;
+    }
+}
diff --git a/edu/mit/wi/haploview/HaploText.java b/edu/mit/wi/haploview/HaploText.java
new file mode 100755
index 0000000..29ff07e
--- /dev/null
+++ b/edu/mit/wi/haploview/HaploText.java
@@ -0,0 +1,1879 @@
+package edu.mit.wi.haploview;
+
+import edu.mit.wi.pedfile.MarkerResult;
+import edu.mit.wi.pedfile.PedFileException;
+import edu.mit.wi.pedfile.CheckData;
+import edu.mit.wi.haploview.association.*;
+import edu.mit.wi.haploview.tagger.TaggerController;
+import edu.mit.wi.tagger.Tagger;
+import edu.mit.wi.tagger.TaggerException;
+
+import java.io.*;
+import java.util.*;
+import java.awt.image.BufferedImage;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import com.sun.jimi.core.Jimi;
+import com.sun.jimi.core.JimiException;
+import org.apache.log4j.*;
+import org.apache.log4j.varia.DenyAllFilter;
+import org.apache.batik.svggen.SVGGraphics2D;
+
+public class HaploText implements Constants{
+    private boolean nogui = false;
+    private String outputRootName;
+    private String batchFileName;
+    private String hapsFileName;
+    private String infoFileName;
+    private String pedFileName;
+    private String hapmapFileName;
+    private String phasedhmpdataFileName;
+    private String phasedhmpsampleFileName;
+    private String phasedhmplegendFileName;
+    //private String fastphaseFileName;
+    private String plinkFileName;
+    private String mapFileName;
+    private boolean phasedhapmapDownload = false;
+    private boolean SNPBased = true;
+    private String selectCols;
+    private String blockName;
+    private String trackName;
+    private String customAssocTestsFileName;
+    private boolean skipCheck = false;
+    private Vector excludedMarkers = new Vector();
+    private boolean quietMode = false;
+    private int blockOutputType;
+    private boolean outputCheck;
+    private boolean individualCheck;
+    private boolean mendel;
+    private boolean malehets;
+    private boolean outputDprime;
+    private boolean outputPNG;
+    private boolean outputCompressedPNG;
+    private boolean outputSVG;
+    private boolean infoTrack;
+    private boolean doPermutationTest;
+    private boolean findTags;
+    private boolean aggressiveTagging;
+    private boolean outputConditionalHaps;
+    private boolean randomizeAffection = false;
+    private int permutationCount;
+    private int tagging;
+    private int maxNumTags;
+    private int aggressiveNumMarkers = 0;
+    private double tagRSquaredCutOff = -1;
+    private Vector forceIncludeTags;
+    private String forceIncludeName;
+    private Vector forceExcludeTags;
+    private String forceExcludeName;
+    private Vector captureAlleleTags;
+    private String captureAllelesName;
+    private Hashtable designScores;
+    private String designScoresName;
+    private String minTagDistance;
+    private Vector argHandlerMessages;
+    private String chromosomeArg;
+    private String[] phasedHapMapInfo;
+    private String panelArg, startPos, endPos, release;
+    private String logFileName, debugFileName;
+    private boolean commandLineError;
+
+    public static Logger logger = Logger.getLogger("logger");
+    public static Logger commandLogger = Logger.getLogger("logger.command");
+
+    public boolean isNogui() {
+        return nogui;
+    }
+
+    public String getBatchMode() {
+        return batchFileName;
+    }
+
+    public String getHapsFileName() {
+        return hapsFileName;
+    }
+
+    public String getPedFileName() {
+        return pedFileName;
+    }
+
+    public String getInfoFileName(){
+        return infoFileName;
+    }
+
+    public String getHapmapFileName(){
+        return hapmapFileName;
+    }
+
+    public String getPhasedHmpDataName(){
+        return phasedhmpdataFileName;
+    }
+
+    public String getPhasedHmpSampleName(){
+        return phasedhmpsampleFileName;
+    }
+
+    public String getPhasedHmpLegendName(){
+        return phasedhmplegendFileName;
+    }
+
+    /*public String getFastphaseFileName(){
+        return fastphaseFileName;
+    }*/
+
+    public boolean getPhasedHmpDownload(){
+        return phasedhapmapDownload;
+    }
+
+    public String getChromosome(){
+        return chromosomeArg;
+    }
+
+    public String getPanel(){
+        return panelArg;
+    }
+
+    public String getStartPos(){
+        return startPos;
+    }
+
+    public String getEndPos(){
+        return endPos;
+    }
+
+    public String getRelease(){
+        return release;
+    }
+
+    public String getPlinkFileName(){
+        return plinkFileName;
+    }
+
+    public String getMapFileName(){
+        return mapFileName;
+    }
+
+    public String getSelectCols(){
+        return selectCols;
+    }
+
+    public int getBlockOutputType() {
+        return blockOutputType;
+    }
+
+    public boolean getCommandLineError(){
+        return commandLineError;
+    }
+
+    private double getDoubleArg(String[] args, int valueIndex, double min, double max) {
+        double argument = 0;
+        String argName = args[valueIndex-1];
+        if(valueIndex>=args.length || ((args[valueIndex].charAt(0)) == '-')) {
+            die( argName + " requires a value between " + min + " and " + max);
+        }
+        try {
+            argument = Double.parseDouble(args[valueIndex]);
+            if(argument<min || argument>max) {
+                die(argName + " requires a value between " + min + " and " + max);
+            }
+        }catch(NumberFormatException nfe) {
+            die(argName + " requires a value between " + min + " and " + max);
+        }
+        return argument;
+    }
+
+    private int getIntegerArg(String[] args, int valueIndex){
+        int argument = 0;
+        String argName = args[valueIndex-1];
+        if(valueIndex>=args.length || ((args[valueIndex].charAt(0)) == '-')){
+            die(argName + " requires an integer argument");
+        }
+        else {
+            try {
+                argument = Integer.parseInt(args[valueIndex]);
+                if(argument<0){
+                    die(argName + " argument must be a positive integer");
+                }
+            } catch(NumberFormatException nfe) {
+                die(argName + " argument must be a positive integer");
+            }
+        }
+        return argument;
+    }
+
+    public HaploText(String[] args) {
+        this.argHandler(args);
+
+        if(this.batchFileName != null) {
+            commandLogger.warn("*****************************************************");
+            commandLogger.warn(TITLE_STRING + "\tJava Version: " + JAVA_VERSION);
+            commandLogger.warn("*****************************************************\n\n");
+            StringBuffer buffer = new StringBuffer();
+            for (int i = 0; i < args.length; i++){
+                buffer.append(args[i]).append("\t");
+            }
+            String arguments = buffer.toString();
+
+            commandLogger.warn("Arguments:\t" + arguments + "\n\n");
+            for (int i = 0; i < argHandlerMessages.size(); i++){
+                commandLogger.warn(argHandlerMessages.get(i));
+            }
+            this.doBatch();
+        }
+
+        if(!(this.pedFileName== null) || !(this.hapsFileName== null) || !(this.hapmapFileName== null) || !(this.phasedhmpdataFileName== null) /*|| !(this.fastphaseFileName == null)*/ || phasedhapmapDownload){
+            if(nogui){
+                commandLogger.warn("*****************************************************");
+                commandLogger.warn(TITLE_STRING + "\tJava Version: " + JAVA_VERSION);
+                commandLogger.warn("*****************************************************\n\n");
+                StringBuffer buffer = new StringBuffer();
+                for (int i = 0; i < args.length; i++){
+                    buffer.append(args[i]).append("\t");
+                }
+                String arguments = buffer.toString();
+
+                commandLogger.info("Arguments:\t" + arguments + "\n\n");
+                for (int i = 0; i < argHandlerMessages.size(); i++){
+                    commandLogger.warn(argHandlerMessages.get(i));
+                }
+                processTextOnly();
+            }
+        }
+
+    }
+
+    private void argHandler(String[] args){
+
+        argHandlerMessages = new Vector();
+        int maxDistance = -1;
+        //this means that user didn't specify any output type if it doesn't get changed below
+        blockOutputType = -1;
+        double hapThresh = -1;
+        double minimumMAF=-1;
+        double spacingThresh = -1;
+        double minimumGenoPercent = -1;
+        double hwCutoff = -1;
+        double missingCutoff = -1;
+        int maxMendel = -1;
+        boolean assocTDT = false;
+        boolean assocCC = false;
+        permutationCount = 0;
+        tagging = Tagger.NONE;
+        maxNumTags = Tagger.DEFAULT_MAXNUMTAGS;
+        findTags = true;
+
+        double cutHighCI = -1;
+        double cutLowCI = -1;
+        double mafThresh = -1;
+        double recHighCI = -1;
+        double informFrac = -1;
+        double fourGameteCutoff = -1;
+        double spineDP = -1;
+
+
+        for(int i =0; i < args.length; i++) {
+            if(args[i].equalsIgnoreCase("-help") || args[i].equalsIgnoreCase("-h")) {
+                System.out.println(HELP_OUTPUT);
+                System.exit(0);
+            }
+            else if(args[i].equalsIgnoreCase("-n") || args[i].equalsIgnoreCase("-nogui")) {
+                nogui = true;
+            }
+            else if(args[i].equalsIgnoreCase("-log")){
+                i++;
+                if (i >= args.length || args[i].charAt(0) == '-'){
+                    logFileName = "haploview.log";
+                    i--;
+                }else{
+                    logFileName = args[i];
+                }
+            }
+            else if(args[i].equalsIgnoreCase("-debug")){
+                i++;
+                if (i >= args.length || args[i].charAt(0) == '-'){
+                    debugFileName = "";
+                    i--;
+                }else{
+                    debugFileName = args[i];
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-out")){
+                i++;
+                if( i>=args.length || (args[i].charAt(0) == '-')){
+                    die(args[i-1] + " requires a fileroot");
+                }
+                else{
+                    if(outputRootName != null){
+                        argHandlerMessages.add("multiple "+args[i-1] + " arguments found. only last fileroot listed will be used");
+                    }
+                    outputRootName = args[i];
+                }
+            }
+            else if(args[i].equalsIgnoreCase("-p") || args[i].equalsIgnoreCase("-pedfile")) {
+                i++;
+                if( i>=args.length || (args[i].charAt(0) == '-')){
+                    die(args[i-1] + " requires a filename");
+                }
+                else{
+                    if(pedFileName != null){
+                        argHandlerMessages.add("multiple "+args[i-1] + " arguments found. only last pedfile listed will be used");
+                    }
+                    pedFileName = args[i];
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-pcloadletter")){
+                die("PC LOADLETTER?! What the fuck does that mean?!");
+            }
+            else if (args[i].equalsIgnoreCase("-skipcheck") || args[i].equalsIgnoreCase("--skipcheck")){
+                skipCheck = true;
+            }
+            else if (args[i].equalsIgnoreCase("-excludeMarkers")){
+                i++;
+                if(i>=args.length || (args[i].charAt(0) == '-')){
+                    die("-excludeMarkers requires a list of markers");
+                }
+                else {
+                    StringTokenizer str = new StringTokenizer(args[i],",");
+                    try {
+                        StringBuffer sb = new StringBuffer();
+                        if (!quietMode) sb.append("Excluding markers: ");
+                        while(str.hasMoreTokens()) {
+                            String token = str.nextToken();
+                            if(token.indexOf("..") != -1) {
+                                int lastIndex = token.indexOf("..");
+                                int rangeStart = Integer.parseInt(token.substring(0,lastIndex));
+                                int rangeEnd = Integer.parseInt(token.substring(lastIndex+2,token.length()));
+                                for(int j=rangeStart;j<=rangeEnd;j++) {
+                                    if (!quietMode) sb.append(j).append(" ");
+                                    excludedMarkers.add(new Integer(j));
+                                }
+                            } else {
+                                if (!quietMode) sb.append(token).append(" ");
+                                excludedMarkers.add(new Integer(token));
+                            }
+                        }
+                        argHandlerMessages.add(sb.toString());
+                    } catch(NumberFormatException nfe) {
+                        die("-excludeMarkers argument should be of the format: 1,3,5..8,12");
+                    }
+                }
+            }
+            else if(args[i].equalsIgnoreCase("-ha") || args[i].equalsIgnoreCase("-haps")) {
+                i++;
+                if(i>=args.length || ((args[i].charAt(0)) == '-')){
+                    die(args[i-1] + " requires a filename");
+                }
+                else{
+                    if(hapsFileName != null){
+                        argHandlerMessages.add("multiple "+args[i-1] + " arguments found. only last haps file listed will be used");
+                    }
+                    hapsFileName = args[i];
+                }
+            }
+            else if(args[i].equalsIgnoreCase("-i") || args[i].equalsIgnoreCase("-info")) {
+                i++;
+                if(i>=args.length || ((args[i].charAt(0)) == '-')){
+                    die(args[i-1] + " requires a filename");
+                }
+                else{
+                    if(infoFileName != null){
+                        argHandlerMessages.add("multiple "+args[i-1] + " arguments found. only last info file listed will be used");
+                    }
+                    infoFileName = args[i];
+                }
+            } else if (args[i].equalsIgnoreCase("-a") || args[i].equalsIgnoreCase("-hapmap")){
+                i++;
+                if(i>=args.length || ((args[i].charAt(0)) == '-')){
+                    die(args[i-1] + " requires a filename");
+                }
+                else{
+                    if(hapmapFileName != null){
+                        argHandlerMessages.add("multiple "+args[i-1] + " arguments found. only last hapmap file listed will be used");
+                    }
+                    hapmapFileName = args[i];
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-phasedhmpdata")){
+                i++;
+                if(i>=args.length || ((args[i].charAt(0)) == '-')){
+                    die(args[i-1] + " requires a filename");
+                }
+                else{
+                    if(phasedhmpdataFileName != null){
+                        argHandlerMessages.add("multiple "+args[i-1] + " arguments found. only last phased hapmap data file listed will be used");
+                    }
+                    phasedhmpdataFileName = args[i];
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-phasedhmpsample")){
+                i++;
+                if(i>=args.length || ((args[i].charAt(0)) == '-')){
+                    die(args[i-1] + " requires a filename");
+                }
+                else{
+                    if(phasedhmpsampleFileName != null){
+                        argHandlerMessages.add("multiple "+args[i-1] + " arguments found. only last phased hapmap sample file listed will be used");
+                    }
+                    phasedhmpsampleFileName = args[i];
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-phasedhmplegend")){
+                i++;
+                if(i>=args.length || ((args[i].charAt(0)) == '-')){
+                    die(args[i-1] + " requires a filename");
+                }
+                else{
+                    if(phasedhmplegendFileName != null){
+                        argHandlerMessages.add("multiple "+args[i-1] + " arguments found. only last phased hapmap legend file listed will be used");
+                    }
+                    phasedhmplegendFileName = args[i];
+                }
+            }
+          /*  else if (args[i].equalsIgnoreCase("-fastphase")){
+                i++;
+                if(i>=args.length || ((args[i].charAt(0)) == '-')){
+                    die(args[i-1] + " requires a filename");
+                }
+                else{
+                    if(fastphaseFileName != null){
+                        argHandlerMessages.add("multiple "+args[i-1] + " arguments found. only last phased hapmap data file listed will be used");
+                    }
+                    fastphaseFileName = args[i];
+                }
+            }*/
+            else if (args[i].equalsIgnoreCase("-hapmapDownload")){
+                phasedhapmapDownload = true;
+            }
+            else if (args[i].equalsIgnoreCase("-plink")){
+                i++;
+                if(i>=args.length || ((args[i].charAt(0)) == '-')){
+                    die(args[i-1] + " requires a filename");
+                }
+                else{
+                    if(plinkFileName != null){
+                        argHandlerMessages.add("multiple "+args[i-1] + " arguments found. only last PLINK file listed will be used");
+                    }
+                    plinkFileName = args[i];
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-map")){
+                i++;
+                if(i>=args.length || ((args[i].charAt(0)) == '-')){
+                    die(args[i-1] + " requires a filename");
+                }
+                else{
+                    if(mapFileName != null){
+                        argHandlerMessages.add("multiple "+args[i-1] + " arguments found. only last map file listed will be used");
+                    }
+                    mapFileName = args[i];
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-nonSNP")){
+                SNPBased = false;
+            }
+            else if (args[i].equalsIgnoreCase("-selectCols")){
+                selectCols = "Y";
+            }
+            else if(args[i].equalsIgnoreCase("-k") || args[i].equalsIgnoreCase("-blocks")) {
+                i++;
+                if (!(i>=args.length) && !((args[i].charAt(0)) == '-')){
+                    blockName = args[i];
+                    blockOutputType = BLOX_CUSTOM;
+                }else{
+                    die(args[i-1] + " requires a filename");
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-png")){
+                outputPNG = true;
+            }
+            else if (args[i].equalsIgnoreCase("-smallpng") || args[i].equalsIgnoreCase("-compressedPNG")){
+                outputCompressedPNG = true;
+            }
+            else if (args[i].equalsIgnoreCase("-svg")){
+                outputSVG = true;
+            }
+            else if (args[i].equalsIgnoreCase("-infoTrack")){
+                infoTrack = true;
+            }
+            else if (args[i].equalsIgnoreCase("-track")){
+                i++;
+                if (!(i>=args.length) && !((args[i].charAt(0)) == '-')){
+                    trackName = args[i];
+                }else{
+                    die("-track requires a filename");
+                }
+            }
+            else if(args[i].equalsIgnoreCase("-o") || args[i].equalsIgnoreCase("-output") || args[i].equalsIgnoreCase("-blockoutput")) {
+                i++;
+                if(!(i>=args.length) && !((args[i].charAt(0)) == '-')){
+                    if(blockOutputType != -1){
+                        die("Only one block output type argument is allowed.");
+                    }
+                    if(args[i].equalsIgnoreCase("SFS") || args[i].equalsIgnoreCase("GAB")){
+                        blockOutputType = BLOX_GABRIEL;
+                    }
+                    else if(args[i].equalsIgnoreCase("GAM")){
+                        blockOutputType = BLOX_4GAM;
+                    }
+                    else if(args[i].equalsIgnoreCase("MJD") || args[i].equalsIgnoreCase("SPI")){
+                        blockOutputType = BLOX_SPINE;
+                    }
+                    else if(args[i].equalsIgnoreCase("ALL")) {
+                        blockOutputType = BLOX_ALL;
+                    }
+                }
+                else {
+                    //defaults to SFS output
+                    blockOutputType = BLOX_GABRIEL;
+                    i--;
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-showBlockTags")){ //This option is undocumented to discourage its use
+                Options.setShowBlockTags(true);
+            }
+            else if(args[i].equalsIgnoreCase("-d") || args[i].equalsIgnoreCase("--dprime") || args[i].equalsIgnoreCase("-dprime")) {
+                outputDprime = true;
+            }
+            else if (args[i].equalsIgnoreCase("-c") || args[i].equalsIgnoreCase("-check")){
+                outputCheck = true;
+            }
+            else if (args[i].equalsIgnoreCase("-indcheck")){
+                individualCheck = true;
+            }
+            else if (args[i].equalsIgnoreCase("-mendel")){
+                mendel = true;
+            }
+            else if (args[i].equalsIgnoreCase("-malehets")){
+                malehets = true;
+            }
+            else if(args[i].equalsIgnoreCase("-m") || args[i].equalsIgnoreCase("-maxdistance")) {
+                i++;
+                maxDistance = getIntegerArg(args,i);
+            }
+            else if(args[i].equalsIgnoreCase("-b") || args[i].equalsIgnoreCase("-batch")) {
+                //batch mode
+                i++;
+                if(i>=args.length || ((args[i].charAt(0)) == '-')){
+                    die(args[i-1] + " requires a filename");
+                }
+                else{
+                    if(batchFileName != null){
+                        argHandlerMessages.add("multiple " + args[i-1] +  " arguments found. only last batch file listed will be used");
+                    }
+                    batchFileName = args[i];
+                }
+            }
+            else if(args[i].equalsIgnoreCase("-hapthresh")) {
+                i++;
+                hapThresh = getDoubleArg(args,i,0,1);
+            }
+            else if(args[i].equalsIgnoreCase("-spacing")) {
+                i++;
+                spacingThresh = getDoubleArg(args,i,0,1);
+            }
+            else if(args[i].equalsIgnoreCase("-minMAF")) {
+                i++;
+                minimumMAF = getDoubleArg(args,i,0,0.5);
+            }
+            else if(args[i].equalsIgnoreCase("-minGeno") || args[i].equalsIgnoreCase("-minGenoPercent")) {
+                i++;
+                minimumGenoPercent = getDoubleArg(args,i,0,1);
+            }
+            else if(args[i].equalsIgnoreCase("-hwcutoff")) {
+                i++;
+                hwCutoff = getDoubleArg(args,i,0,1);
+            }
+            else if(args[i].equalsIgnoreCase("-maxMendel") ) {
+                i++;
+                maxMendel = getIntegerArg(args,i);
+            }
+            else if(args[i].equalsIgnoreCase("-missingcutoff")) {
+                i++;
+                missingCutoff = getDoubleArg(args,i,0,1);
+            }
+            else if(args[i].equalsIgnoreCase("-assoctdt")) {
+                assocTDT = true;
+            }
+            else if(args[i].equalsIgnoreCase("-assoccc")) {
+                assocCC = true;
+            }
+            else if(args[i].equalsIgnoreCase("-randomcc")){
+                assocCC = true;
+                randomizeAffection = true;
+            }
+            else if(args[i].equalsIgnoreCase("-ldcolorscheme")) {
+                i++;
+                if(!(i>=args.length) && !((args[i].charAt(0)) == '-')){
+                    if(args[i].equalsIgnoreCase("default")){
+                        Options.setLDColorScheme(STD_SCHEME);
+                    }
+                    else if(args[i].equalsIgnoreCase("RSQ")){
+                        Options.setLDColorScheme(RSQ_SCHEME);
+                    }
+                    else if(args[i].equalsIgnoreCase("DPALT") ){
+                        Options.setLDColorScheme(WMF_SCHEME);
+                    }
+                    else if(args[i].equalsIgnoreCase("GAB")) {
+                        Options.setLDColorScheme(GAB_SCHEME);
+                    }
+                    else if(args[i].equalsIgnoreCase("GAM")) {
+                        Options.setLDColorScheme(GAM_SCHEME);
+                    }
+                    else if(args[i].equalsIgnoreCase("GOLD")) {
+                        Options.setLDColorScheme(GOLD_SCHEME);
+                    }
+                }
+                else {
+                    //defaults to STD color scheme
+                    Options.setLDColorScheme(STD_SCHEME);
+                    i--;
+                }
+            }
+            else if(args[i].equalsIgnoreCase("-ldvalues")) {
+                i++;
+                if(!(i>=args.length) && !((args[i].charAt(0)) == '-')){
+                    if (args[i].equalsIgnoreCase("RSQ")){
+                        Options.setPrintWhat(R_SQ);
+                    }else if (args[i].equalsIgnoreCase("DPRIME")){
+                        Options.setPrintWhat(D_PRIME);
+                    }else if (args[i].equalsIgnoreCase("NONE")){
+                        Options.setPrintWhat(LD_NONE);
+                    }
+                }else {
+                    //defaults to printing DPRIME
+                    Options.setPrintWhat(D_PRIME);
+                    i--;
+                }
+            }
+            else if(args[i].equalsIgnoreCase("-blockCutHighCI")) {
+                i++;
+                cutHighCI = getDoubleArg(args,i,0,1);
+            }
+            else if(args[i].equalsIgnoreCase("-blockCutLowCI")) {
+                i++;
+                cutLowCI = getDoubleArg(args,i,0,1);
+            }
+            else if(args[i].equalsIgnoreCase("-blockMafThresh")) {
+                i++;
+                mafThresh = getDoubleArg(args,i,0,1);
+            }
+            else if(args[i].equalsIgnoreCase("-blockRecHighCI")) {
+                i++;
+                recHighCI = getDoubleArg(args,i,0,1);
+            }
+            else if(args[i].equalsIgnoreCase("-blockInformFrac")) {
+                i++;
+                informFrac = getDoubleArg(args,i,0,1);
+            }
+            else if(args[i].equalsIgnoreCase("-block4GamCut")) {
+                i++;
+                fourGameteCutoff = getDoubleArg(args,i,0,1);
+            }
+            else if(args[i].equalsIgnoreCase("-blockSpineDP")) {
+                i++;
+                spineDP = getDoubleArg(args,i,0,1);
+            }
+            else if(args[i].equalsIgnoreCase("-permtests")) {
+                i++;
+                doPermutationTest = true;
+                permutationCount = getIntegerArg(args,i);
+            }
+            else if(args[i].equalsIgnoreCase("-customassoc")) {
+                i++;
+                if (!(i>=args.length) && !((args[i].charAt(0)) == '-')){
+                    customAssocTestsFileName = args[i];
+                }else{
+                    die(args[i-1] + " requires a filename");
+                }
+            }
+            else if(args[i].equalsIgnoreCase("-aggressiveTagging")) {
+                //tagging = Tagger.AGGRESSIVE_TRIPLE;
+                aggressiveTagging = true;
+            }
+            else if (args[i].equalsIgnoreCase("-aggressiveNumMarkers")){
+                i++;
+                aggressiveNumMarkers = getIntegerArg(args,i);
+                if (aggressiveNumMarkers != 2 && aggressiveNumMarkers != 3){
+                    die (args[i-1] + " requires a value of either 2 or 3");
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-pairwiseTagging")){
+                tagging = Tagger.PAIRWISE_ONLY;
+            }
+            else if (args[i].equalsIgnoreCase("-printalltags")){
+                Options.setPrintAllTags(true);
+            }
+            else if(args[i].equalsIgnoreCase("-maxNumTags")){
+                i++;
+                maxNumTags = getIntegerArg(args,i);
+            }
+            else if(args[i].equalsIgnoreCase("-tagrSqCutoff")) {
+                i++;
+                tagRSquaredCutOff = getDoubleArg(args,i,0,1);
+            }
+            else if (args[i].equalsIgnoreCase("-dontaddtags")){
+                findTags = false;
+            }
+            else if(args[i].equalsIgnoreCase("-tagLODCutoff")) {
+                i++;
+                Options.setTaggerLODCutoff(getDoubleArg(args,i,0,100000));
+            }
+            else if(args[i].equalsIgnoreCase("-includeTags")) {
+                i++;
+                if(i>=args.length || args[i].charAt(0) == '-') {
+                    die(args[i-1] + " requires a list of marker names.");
+                }
+                StringTokenizer str = new StringTokenizer(args[i],",");
+                forceIncludeTags = new Vector();
+                while(str.hasMoreTokens()) {
+                    forceIncludeTags.add(str.nextToken());
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-includeTagsFile")) {
+                i++;
+                if(!(i>=args.length) && !(args[i].charAt(0) == '-')) {
+                    forceIncludeName =args[i];
+                }else {
+                    die(args[i-1] + " requires a filename");
+                }
+            }
+            else if(args[i].equalsIgnoreCase("-excludeTags")) {
+                i++;
+                if(i>=args.length || args[i].charAt(0) == '-') {
+                    die("-excludeTags requires a list of marker names.");
+                }
+                StringTokenizer str = new StringTokenizer(args[i],",");
+                forceExcludeTags = new Vector();
+                while(str.hasMoreTokens()) {
+                    forceExcludeTags.add(str.nextToken());
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-excludeTagsFile")) {
+                i++;
+                if(!(i>=args.length) && !(args[i].charAt(0) == '-')) {
+                    forceExcludeName =args[i];
+                }else {
+                    die(args[i-1] + " requires a filename");
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-captureAlleles")){
+                i++;
+                if(!(i>=args.length) && !(args[i].charAt(0) == '-')) {
+                    captureAllelesName =args[i];
+                }else {
+                    die(args[i-1] + " requires a filename");
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-designScores")){
+                i++;
+                if(!(i>=args.length) && !(args[i].charAt(0) == '-')) {
+                    designScoresName =args[i];
+                }else {
+                    die(args[i-1] + " requires a filename");
+                }
+            }
+            else if (args[i].equalsIgnoreCase("-mindesignscores")){
+                i++;
+                Options.setTaggerMinDesignScore(getDoubleArg(args,i,0,Double.MAX_VALUE));
+            }
+            else if (args[i].equalsIgnoreCase("-mintagdistance")){
+                i++;
+                minTagDistance = args[i];
+            }
+            else if (args[i].equalsIgnoreCase("-tagrsqcounts")){
+                outputConditionalHaps = true;
+            }
+            else if(args[i].equalsIgnoreCase("-chromosome") || args[i].equalsIgnoreCase("-chr")) {
+                i++;
+                if(!(i>=args.length) && !(args[i].charAt(0) == '-')) {
+                    chromosomeArg =args[i];
+                }else {
+                    die(args[i-1] + " requires a chromosome name");
+                }
+
+                if(!(chromosomeArg.equalsIgnoreCase("X")) && !(chromosomeArg.equalsIgnoreCase("Y"))){
+                    try{
+                        if (Integer.parseInt(chromosomeArg) > 22){
+                            die("-chromosome requires a chromosome name of 1-22, X, or Y");
+                        }
+                    }catch(NumberFormatException nfe){
+                        die("-chromosome requires a chromosome name of 1-22, X, or Y");
+                    }
+                }
+
+            }
+            else if(args[i].equalsIgnoreCase("-panel")){
+                i++;
+                if(!(i>=args.length) && !(args[i].charAt(0)== '-')) {
+                    panelArg = args[i];
+                }else {
+                    die(args[i-1] + "requires an analysis panel name");
+                }
+            }
+            else if(args[i].equalsIgnoreCase("-startpos")){
+                i++;
+                startPos = args[i];
+            }
+            else if(args[i].equalsIgnoreCase("-endPos")){
+                i++;
+                endPos = args[i];
+            }
+            else if(args[i].equalsIgnoreCase("-release")){
+                i++;
+                release = args[i];
+            }
+            else if(args[i].equalsIgnoreCase("-q") || args[i].equalsIgnoreCase("-quiet")) {
+                quietMode = true;
+            }
+            else if(args[i].equalsIgnoreCase("-gzip")){
+                Options.setGzip(true);
+            }
+            else {
+                die("invalid parameter specified: " + args[i]);
+            }
+        }
+
+        ConsoleAppender nullAppender = new ConsoleAppender();
+        nullAppender.addFilter(new DenyAllFilter());
+        if (debugFileName != null){
+            if (logFileName != null){
+                System.err.println("You may specify either -log or -debug but not both, ignoring -log.");
+            }
+            if (debugFileName.equals("")){
+                logger.addAppender(new ConsoleAppender(new PatternLayout()));
+                logger.setLevel(Level.DEBUG);
+                commandLogger.addAppender(nullAppender);
+            }else{
+                try{
+                    logger.addAppender(new FileAppender(new PatternLayout(),debugFileName,false));
+                }catch (IOException ioe){
+                    System.err.println("An error occurred while writing to the debug file.");
+                }
+                logger.setLevel(Level.DEBUG);
+                commandLogger.addAppender(new ConsoleAppender(new PatternLayout()));
+                commandLogger.setLevel(Level.INFO);
+            }
+        }else if (logFileName != null){
+            try{
+                logger.addAppender(new FileAppender(new PatternLayout(),logFileName,false));
+            }catch (IOException ioe){
+                System.err.println("An error occurred while writing to the log file.");
+            }
+            logger.setLevel(Level.INFO);
+            commandLogger.addAppender(new ConsoleAppender(new PatternLayout()));
+            commandLogger.setLevel(Level.INFO);
+        }else{
+            logger.addAppender(nullAppender);
+            commandLogger.addAppender(new ConsoleAppender(new PatternLayout()));
+            if (quietMode){
+                commandLogger.setLevel(Level.WARN);
+            }else{
+                commandLogger.setLevel(Level.INFO);
+            }
+        }
+
+        logger.setAdditivity(false);
+        commandLogger.setAdditivity(true);
+        //Convert all active System.out.println statements to commandLogger.info()
+        //Convert all active System.err.println statements to commandLogger.error()
+        //Convert all debug statements to logger.debug()
+
+
+        int countOptions = 0;
+        if(pedFileName != null) {
+            countOptions++;
+        }
+        if(hapsFileName != null) {
+            countOptions++;
+        }
+        if(hapmapFileName != null) {
+            countOptions++;
+        }
+        if(phasedhmpdataFileName != null) {
+            countOptions++;
+            if(phasedhmpsampleFileName == null){
+                die("You must specify a sample file for phased hapmap input.");
+            }else if(phasedhmplegendFileName == null){
+                die("You must specify a legend file for phased hapmap input.");
+            }
+        }
+     /*   if(fastphaseFileName != null) {
+            countOptions++;
+            if (infoFileName == null) {
+                die("You must specify an info file for PHASE format input.");
+            }
+        }*/
+        if(phasedhapmapDownload) {
+            countOptions++;
+        }
+        if(plinkFileName != null){
+            countOptions++;
+            Options.setSNPBased(SNPBased);
+            if(mapFileName == null && Options.getSNPBased()){
+                die("You must specify a map file for plink format input.");
+            }
+        }
+        if(batchFileName != null) {
+            countOptions++;
+        }
+        if(countOptions > 1) {
+            die("Only one genotype input file may be specified on the command line.");
+        }
+        else if(countOptions == 0 && nogui) {
+            die("You must specify a genotype input file.");
+        }
+
+        //mess with vars, set defaults, etc
+        if(skipCheck) {
+            argHandlerMessages.add("Skipping genotype file check");
+        }
+        if(maxDistance == -1){
+            maxDistance = MAXDIST_DEFAULT;
+        }else{
+            argHandlerMessages.add("Max LD comparison distance = " +maxDistance + "kb");
+        }
+
+        Options.setMaxDistance(maxDistance);
+
+        if(hapThresh != -1) {
+            Options.setHaplotypeDisplayThreshold(hapThresh);
+            argHandlerMessages.add("Haplotype display threshold = " + hapThresh);
+        }
+
+        if(minimumMAF != -1) {
+            CheckData.mafCut = minimumMAF;
+            argHandlerMessages.add("Minimum MAF = " + minimumMAF);
+        }
+
+        if(minimumGenoPercent != -1) {
+            CheckData.failedGenoCut = (int)(minimumGenoPercent*100);
+            argHandlerMessages.add("Minimum SNP genotype % = " + minimumGenoPercent);
+        }
+
+        if(hwCutoff != -1) {
+            CheckData.hwCut = hwCutoff;
+            argHandlerMessages.add("Hardy Weinberg equilibrium p-value cutoff = " + hwCutoff);
+        }
+
+        if(maxMendel != -1) {
+            CheckData.numMendErrCut = maxMendel;
+            argHandlerMessages.add("Maximum number of Mendel errors = "+maxMendel);
+        }
+
+        if(spacingThresh != -1) {
+            Options.setSpacingThreshold(spacingThresh);
+            argHandlerMessages.add("LD display spacing value = "+spacingThresh);
+        }
+
+        if(missingCutoff != -1) {
+            Options.setMissingThreshold(missingCutoff);
+            argHandlerMessages.add("Maximum amount of missing data allowed per individual = "+missingCutoff);
+        }
+
+        if(cutHighCI != -1) {
+            FindBlocks.cutHighCI = cutHighCI;
+        }
+
+        if(cutLowCI != -1) {
+            FindBlocks.cutLowCI = cutLowCI;
+        }
+        if(mafThresh != -1) {
+            FindBlocks.mafThresh = mafThresh;
+        }
+        if(recHighCI != -1) {
+            FindBlocks.recHighCI = recHighCI;
+        }
+        if(informFrac != -1) {
+            FindBlocks.informFrac = informFrac;
+        }
+        if(fourGameteCutoff != -1) {
+            FindBlocks.fourGameteCutoff = fourGameteCutoff;
+        }
+        if(spineDP != -1) {
+            FindBlocks.spineDP = spineDP;
+        }
+
+        if(assocTDT) {
+            Options.setAssocTest(ASSOC_TRIO);
+        }else if(assocCC) {
+            Options.setAssocTest(ASSOC_CC);
+        }
+
+        if (Options.getAssocTest() != ASSOC_NONE && infoFileName == null && hapmapFileName == null) {
+            die("A marker info file must be specified when performing association tests.");
+        }
+
+        if(doPermutationTest) {
+            if(!assocCC && !assocTDT) {
+                die("An association test type must be specified for permutation tests to be performed.");
+            }
+        }
+
+        if(customAssocTestsFileName != null) {
+            if(!assocCC && !assocTDT) {
+                die("An association test type must be specified when using a custom association test file.");
+            }
+            if(infoFileName == null) {
+                die("A marker info file must be specified when using a custom association test file.");
+            }
+        }
+
+        if (aggressiveTagging){
+            if (aggressiveNumMarkers == 3){
+                tagging = Tagger.AGGRESSIVE_TRIPLE;
+            }else{
+                tagging = Tagger.AGGRESSIVE_DUPLE;
+            }
+        }
+
+        if(tagging != Tagger.NONE) {
+            if(infoFileName == null && hapmapFileName == null && batchFileName == null && phasedhmpdataFileName == null && !phasedhapmapDownload) {
+                die("A marker info file must be specified when tagging.");
+            }
+
+            if(forceExcludeTags == null) {
+                forceExcludeTags = new Vector();
+            } else if (forceExcludeName != null) {
+                die("-excludeTags and -excludeTagsFile cannot both be used");
+            }
+
+            if(forceExcludeName != null) {
+                try{
+                    BufferedReader br = new BufferedReader(new InputStreamReader(getInputStream(forceExcludeName)));
+                    forceExcludeTags = new Vector();
+                    String line;
+                    while((line = br.readLine()) != null) {
+                        if(line.length() > 0 && line.charAt(0) != '#'){
+                            forceExcludeTags.add(line);
+                        }
+                    }
+                }catch(IOException ioe) {
+                    die("An error occured while reading the file specified by -excludeTagsFile.");
+                }
+            }
+
+            if(forceIncludeTags == null ) {
+                forceIncludeTags = new Vector();
+            } else if (forceIncludeName != null) {
+                die("-includeTags and -includeTagsFile cannot both be used");
+            }
+
+            if(forceIncludeName != null) {
+                try{
+                    BufferedReader br = new BufferedReader(new InputStreamReader(getInputStream(forceIncludeName)));
+                    forceIncludeTags = new Vector();
+                    String line;
+                    while((line = br.readLine()) != null) {
+                        if(line.length() > 0 && line.charAt(0) != '#'){
+                            forceIncludeTags.add(line);
+                        }
+                    }
+                }catch(IOException ioe) {
+                    die("An error occured while reading the file specified by -includeTagsFile.");
+                }
+            }
+
+            if (captureAllelesName != null) {
+                try {
+                    BufferedReader br = new BufferedReader(new InputStreamReader(getInputStream(captureAllelesName)));
+                    captureAlleleTags = new Vector();
+                    String line;
+                    while((line = br.readLine()) != null) {
+                        if(line.length() > 0 && line.charAt(0) != '#'){
+                            line = line.trim();
+                            captureAlleleTags.add(line);
+                        }
+                    }
+                }catch(IOException ioe) {
+                    die("An error occured while reading the file specified by -captureAlleles.");
+                }
+            }
+
+            if (designScoresName != null) {
+                try {
+                    BufferedReader br = new BufferedReader(new InputStreamReader(getInputStream(designScoresName)));
+                    designScores = new Hashtable(1,1);
+                    String line;
+                    int lines = 0;
+                    while((line = br.readLine()) != null) {
+                        if(line.length() > 0 && line.charAt(0) != '#'){
+                            StringTokenizer st = new StringTokenizer(line);
+                            int length = st.countTokens();
+                            if (length != 2){
+                                die("Invalid formatting on line " + lines);
+                            }
+                            String marker = st.nextToken();
+                            Double score = new Double(st.nextToken());
+                            designScores.put(marker,score);
+                        }
+                        lines++;
+                    }
+                }catch(IOException ioe) {
+                    die("An error occured while reading the file specified by -designScores.");
+                }
+            }
+
+            if (minTagDistance != null) {
+                try{
+                    if (Integer.parseInt(minTagDistance) < 0){
+                        die("minimum tag distance cannot be negative");
+                    }
+                }catch(NumberFormatException nfe){
+                    die("minimum tag distance must be a positive integer");
+                }
+                Options.setTaggerMinDistance(Integer.parseInt(minTagDistance));
+            }
+
+            //check that there isn't any overlap between include/exclude lists
+            Vector tempInclude = (Vector) forceIncludeTags.clone();
+            tempInclude.retainAll(forceExcludeTags);
+            if(tempInclude.size() > 0) {
+                StringBuffer sb = new StringBuffer();
+                for (int i = 0; i < tempInclude.size(); i++) {
+                    String s = (String) tempInclude.elementAt(i);
+                    sb.append(s).append(",");
+                }
+                die("The following markers appear in both the include and exclude lists: " + sb.toString());
+            }
+
+            if(tagRSquaredCutOff != -1) {
+                Options.setTaggerRsqCutoff(tagRSquaredCutOff);
+            }
+
+        } else if(forceExcludeTags != null || forceIncludeTags != null || tagRSquaredCutOff != -1) {
+            die("-tagrSqCutoff, -excludeTags, -excludeTagsFile, -includeTags and -includeTagsFile cannot be used without a tagging option");
+        }
+
+
+        if(chromosomeArg != null && hapmapFileName != null) {
+            argHandlerMessages.add("-chromosome flag ignored when loading hapmap file");
+            chromosomeArg = null;
+        }
+
+        if(chromosomeArg != null) {
+            if ((chromosomeArg.equalsIgnoreCase("X") || chromosomeArg.equalsIgnoreCase("Y")) && hapsFileName != null){
+                die("Chromosome X and Chromosome Y are not supported in the phased haplotypes file format.");
+            }
+            Chromosome.setDataChrom("chr" + chromosomeArg);
+        }else{
+            chromosomeArg = "";
+        }
+
+        if (phasedhapmapDownload){
+            if (chromosomeArg == null){
+                die("-hapmapDownload requires a chromosome specification");
+            }else if (!(panelArg.equalsIgnoreCase("CEU") || panelArg.equalsIgnoreCase("YRI")  ||
+                    panelArg.equalsIgnoreCase("CHB+JPT"))){
+                die("-hapmapDownload requires an analysis panel specification of CEU, YRI, or CHB+JPT");
+            }
+
+            try{
+                if (Integer.parseInt(startPos) > Integer.parseInt(endPos)){
+                    die("-endpos must be greater then -startpos");
+                }
+            }catch(NumberFormatException nfe){
+                die("-startpos and -endpos must be integer values");
+            }
+
+            if (release == null){
+                release = "21";
+            }
+
+            if (!(release.equals("22")) && !(release.equals("21")) && !(release.startsWith("16"))){
+                die("release must be either 16a, 21 or 22");
+            }
+        }
+    }
+
+    private void die(String msg){
+        System.err.println(TITLE_STRING + " Fatal Error");
+        System.err.println(msg);
+        commandLineError = true;
+        if (isNogui()){
+            System.exit(1);
+        }
+    }
+
+    private void doBatch() {
+        Vector files;
+        File batchFile;
+        File dataFile;
+        String line;
+        StringTokenizer tok;
+        String infoMaybe;
+
+        files = new Vector();
+        if(batchFileName == null) {
+            return;
+        }
+        batchFile = new File(this.batchFileName);
+
+        if(!batchFile.exists()) {
+            commandLogger.warn("batch file " + batchFileName + " does not exist");
+            System.exit(1);
+        }
+
+        commandLogger.info("Processing batch input file: " + batchFile);
+
+        try {
+            BufferedReader br = new BufferedReader(new FileReader(batchFile));
+            while( (line = br.readLine()) != null ) {
+                files.add(line);
+            }
+            br.close();
+
+            for(int i = 0;i<files.size();i++){
+                line = (String)files.get(i);
+                tok = new StringTokenizer(line);
+                infoMaybe = null;
+                if(tok.hasMoreTokens()){
+                    dataFile = new File(tok.nextToken());
+                    if(tok.hasMoreTokens()){
+                        infoMaybe = tok.nextToken();
+                    }
+
+                    if(dataFile.exists()) {
+                        String name = dataFile.getName();
+                        //TODO: are there other things which need to be reset?
+                        Chromosome.setDataChrom("none");
+                        if( name.substring(name.length()-4,name.length()).equalsIgnoreCase(".ped") ) {
+                            processFile(name,PED_FILE,infoMaybe);
+                        }
+                        else if(name.substring(name.length()-5,name.length()).equalsIgnoreCase(".haps")) {
+                            processFile(name,HAPS_FILE,infoMaybe);
+                        }
+                        else if(name.substring(name.length()-4,name.length()).equalsIgnoreCase(".hmp")){
+                            processFile(name,HMP_FILE,null);
+                        }
+                        else{
+                            commandLogger.info("Filenames in batch file must end in .ped, .haps or .hmp\n" +
+                                        name + " is not properly formatted.");
+                        }
+                    }
+                    else {
+                        commandLogger.info("file " + dataFile.getName() + " listed in the batch file could not be found");
+                    }
+                }
+
+            }
+        }
+        catch(FileNotFoundException e){
+            System.out.println("the following error has occured:\n" + e.toString());
+        }
+        catch(IOException e){
+            System.out.println("the following error has occured:\n" + e.toString());
+        }
+
+    }
+
+    private File validateOutputFile(String fn){
+        File f;
+        try{
+            new URL(fn);
+            f = new File(fn.substring(fn.lastIndexOf("/")+1));
+        }catch(MalformedURLException mfe){
+            f = new File(fn);
+        }
+        if (f.exists()){
+            commandLogger.info("File " + f.getName() + " already exists and will be overwritten.");
+        }
+        commandLogger.info("Writing output to "+f.getName());
+        return f;
+    }
+
+    private InputStream getInputStream(String name){
+        InputStream theStream = null;
+        if (name != null){
+            try{
+                try{
+                    URL streamURL = new URL(name);
+                    theStream = streamURL.openStream();
+                }catch(MalformedURLException mfe){
+                    File streamFile = new File(name);
+                    theStream = new FileInputStream(streamFile);
+                }catch(IOException ioe){
+                    die("Could not connect to " + name);
+                }
+            }catch(IOException ioe){
+                die("Error reading " + name);
+            }
+        }
+        return theStream;
+    }
+
+    /**
+     * this method finds haplotypes and caclulates dprime without using any graphics
+     */
+    private void processTextOnly(){
+        String fileName;
+        int fileType;
+        if(hapsFileName != null) {
+            fileName = hapsFileName;
+            fileType = HAPS_FILE;
+        }
+        else if (pedFileName != null){
+            fileName = pedFileName;
+            fileType = PED_FILE;
+        }
+        else if (phasedhmpdataFileName != null){
+            fileName = phasedhmpdataFileName;
+            fileType = PHASEHMP_FILE;
+            phasedHapMapInfo = new String[]{phasedhmpdataFileName, phasedhmpsampleFileName, phasedhmplegendFileName, chromosomeArg};
+        }
+       /* else if (fastphaseFileName != null){
+            fileName = fastphaseFileName;
+            fileType = FASTPHASE_FILE;
+            phasedHapMapInfo = new String[]{fastphaseFileName, infoFileName, null, chromosomeArg};
+        }*/
+        else if (phasedhapmapDownload){
+            fileName = "Chromosome" + chromosomeArg + panelArg;
+            fileType = HMPDL_FILE;
+            phasedHapMapInfo = new String[]{fileName, panelArg, startPos, endPos, chromosomeArg, release, "max"};
+        }else{
+            fileName = hapmapFileName;
+            fileType = HMP_FILE;
+        }
+
+        processFile(fileName,fileType,infoFileName);
+    }
+    /**
+     * this
+     * @param fileName name of the file to process
+     * @param fileType true means pedfilem false means hapsfile
+     * @param infoFileName
+     */
+    private void processFile(String fileName, int fileType, String infoFileName){
+        try {
+            HaploData textData;
+            File outputFile;
+            AssociationTestSet customAssocSet;
+
+            if (fileName != null){
+                if (phasedhapmapDownload){
+                    commandLogger.info("Downloading chromosome " + chromosomeArg + ", analysis panel " + panelArg + ", " +
+                            startPos + ".." + endPos + " from HapMap release " + release + ".");
+                }else{
+                    commandLogger.info("Using data file: " + fileName);
+                }
+
+            }
+
+            if (outputRootName == null){
+                outputRootName = fileName;
+            }else{
+                commandLogger.info("Using output fileroot: " + outputRootName);
+            }
+
+           /* inputFile = new File(fileName);
+            if(!inputFile.exists() && !phasedhapmapDownload){
+                commandLogger.warn("input file: " + fileName + " does not exist");
+                System.exit(1);
+            }*/
+
+            textData = new HaploData();
+            //Vector result = null;
+
+            if(fileType == HAPS_FILE){
+                //read in haps file
+                textData.prepareHapsInput(fileName);
+            }
+            else if (fileType == PED_FILE) {
+                //read in ped file
+                textData.linkageToChrom(fileName, PED_FILE);
+
+                if(textData.getPedFile().isBogusParents()) {
+                    commandLogger.warn("Error: One or more individuals in the file reference non-existent parents.\nThese references have been ignored.");
+                }
+                if(textData.getPedFile().getHaploidHets() != null){
+                    commandLogger.warn("Error: At least one male in the file is heterozygous.\nThese genotypes have been ignored.");
+                }
+            }
+            else if (fileType == PHASEHMP_FILE || fileType == HMPDL_FILE /*|| fileType == FASTPHASE_FILE*/){
+                //read in phased data
+                textData.phasedToChrom(phasedHapMapInfo, fileType);
+            }
+            else{
+                //read in hapmapfile
+                textData.linkageToChrom(fileName,HMP_FILE);
+            }
+
+
+            InputStream markerStream = getInputStream(infoFileName);
+
+            textData.prepareMarkerInput(markerStream,textData.getPedFile().getHMInfo());
+
+            HashSet whiteListedCustomMarkers = new HashSet();
+            if (customAssocTestsFileName != null){
+                customAssocSet = new AssociationTestSet(customAssocTestsFileName);
+                whiteListedCustomMarkers = customAssocSet.getWhitelist();
+            }else{
+                customAssocSet = null;
+            }
+
+            Hashtable snpsByName = new Hashtable();
+            for(int i=0;i<Chromosome.getUnfilteredSize();i++) {
+                SNP snp = Chromosome.getUnfilteredMarker(i);
+                snpsByName.put(snp.getDisplayName(), snp);
+            }
+
+            if(forceIncludeTags != null) {
+                for(int i=0;i<forceIncludeTags.size();i++) {
+                    if(snpsByName.containsKey(forceIncludeTags.get(i))) {
+                        whiteListedCustomMarkers.add(snpsByName.get(forceIncludeTags.get(i)));
+                    }
+                }
+            }
+
+            if(captureAllelesName != null) {  //TODO: This is causing alleles to not show up as BAD in the check output even though they fail thresholds
+                for(int i =0;i<captureAlleleTags.size();i++) {
+                    if(snpsByName.containsKey(captureAlleleTags.get(i))) {
+                        whiteListedCustomMarkers.add(snpsByName.get(captureAlleleTags.get(i)));
+                    }
+                }
+            }
+
+            textData.getPedFile().setWhiteList(whiteListedCustomMarkers);
+
+            boolean[] markerResults = new boolean[Chromosome.getUnfilteredSize()];
+            Vector result;
+            result = textData.getPedFile().getResults();
+            //once check has been run we can filter the markers
+            int mafFails = 0;
+            int mendelFails = 0;
+            int genoFails = 0;
+            int hwFails = 0;
+            for (int i = 0; i < result.size(); i++){
+                if (((((MarkerResult)result.get(i)).getRating() > 0 || skipCheck) &&
+                        Chromosome.getUnfilteredMarker(i).getDupStatus() != 2)){
+                    markerResults[i] = true;
+                }else{
+                    markerResults[i] = false;
+                    int rating = ((MarkerResult)result.get(i)).getRating();
+                    if (rating <= -16){
+                        mafFails++;
+                        rating += 16;
+                    }
+                    if (rating <= -8){
+                        mendelFails++;
+                        rating += 8;
+                    }
+                    if (rating <= -4){
+                        hwFails++;
+                        rating += 4;
+                    }
+                    if (rating <= -2){
+                        genoFails++;
+                        rating += 2;
+                    }
+                }
+            }
+
+            for (int i = 0; i < excludedMarkers.size(); i++){
+                int cur = ((Integer)excludedMarkers.elementAt(i)).intValue();
+                if (cur < 1 || cur > markerResults.length){
+                    commandLogger.warn("Excluded marker out of bounds: " + cur +
+                            "\nMarkers must be between 1 and N, where N is the total number of markers.");
+                    System.exit(1);
+                }else{
+                    markerResults[cur-1] = false;
+                }
+            }
+
+
+            for(int i=0;i<Chromosome.getUnfilteredSize();i++) {
+                if(textData.getPedFile().isWhiteListed(Chromosome.getUnfilteredMarker(i))) {
+                    markerResults[i] = true;
+                }
+            }
+
+            Chromosome.doFilter(markerResults);
+
+            if(markerStream != null){
+                commandLogger.info("Using marker information file: " + infoFileName);
+            }
+            if(outputCheck && result != null){
+                textData.getPedFile().saveCheckDataToText(validateOutputFile(outputRootName + ".CHECK"));
+            }
+            if(individualCheck && result != null){
+                IndividualDialog id = new IndividualDialog(textData);
+                id.printTable(validateOutputFile(outputRootName + ".INDCHECK"));
+            }
+            if(mendel && result != null){
+                if (textData.getPedFile().getMendelsExist()){
+                    MendelDialog md = new MendelDialog(textData);
+                    md.printTable(validateOutputFile(outputRootName + ".MENDEL" ));
+                }
+            }
+            if(malehets && result != null){
+                if (textData.getPedFile().getHaploidHets() != null){
+                    HetsDialog hd = new HetsDialog(textData);
+                    hd.printTable(validateOutputFile(outputRootName + ".MALEHETS"));
+                }
+            }
+
+            logger.info((Chromosome.getUnfilteredSize() - mafFails) + " out of " + Chromosome.getUnfilteredSize() + " markers passed the MAF threshold.");
+            logger.info((Chromosome.getUnfilteredSize() - mendelFails) + " out of " + Chromosome.getUnfilteredSize() + " markers passed the Mendel threshold.");
+            logger.info((Chromosome.getUnfilteredSize() - genoFails) + " out of " + Chromosome.getUnfilteredSize() + " markers passed the genotyping threshold.");
+            logger.info((Chromosome.getUnfilteredSize() - hwFails) + " out of " + Chromosome.getUnfilteredSize() + " markers passed the Hardy Weinberg threshold.");
+            logger.info(Chromosome.getSize() + " out of " + Chromosome.getUnfilteredSize() + " markers passed all thresholds.");
+
+            Vector cust = new Vector();
+            AssociationTestSet blockTestSet = null;
+
+            if(blockOutputType != -1){
+                textData.generateDPrimeTable();
+                Haplotype[][] haplos;
+                Haplotype[][] filtHaplos;
+                switch(blockOutputType){
+                    case BLOX_GABRIEL:
+                        outputFile = validateOutputFile(outputRootName + ".GABRIELblocks");
+                        break;
+                    case BLOX_4GAM:
+                        outputFile = validateOutputFile(outputRootName + ".4GAMblocks");
+                        break;
+                    case BLOX_SPINE:
+                        outputFile = validateOutputFile(outputRootName + ".SPINEblocks");
+                        break;
+                    case BLOX_CUSTOM:
+                        outputFile = validateOutputFile(outputRootName + ".CUSTblocks");
+                        //read in the blocks file
+                        commandLogger.info("Using custom blocks file: " + blockName);
+                        cust = textData.readBlocks(getInputStream(blockName));
+                        break;
+                    case BLOX_ALL:
+                        //handled below, so we don't do anything here
+                        outputFile = null;
+                        break;
+                    default:
+                        outputFile = validateOutputFile(outputRootName + ".GABRIELblocks");
+                        break;
+
+                }
+
+                //this handles output type ALL
+                if(blockOutputType == BLOX_ALL) {
+                    outputFile = validateOutputFile(outputRootName + ".GABRIELblocks");
+                    textData.guessBlocks(BLOX_GABRIEL);
+
+                    haplos = textData.generateBlockHaplotypes(textData.blocks);
+                    if (haplos != null){
+                        filtHaplos = filterHaplos(haplos);
+                        textData.pickTags(filtHaplos);
+                        textData.saveHapsToText(haplos, textData.computeMultiDprime(filtHaplos), outputFile);
+                    }else {
+                        commandLogger.info("Skipping block output: no valid Gabriel blocks.");
+                    }
+
+                    outputFile = validateOutputFile(outputRootName + ".4GAMblocks");
+                    textData.guessBlocks(BLOX_4GAM);
+
+                    haplos = textData.generateBlockHaplotypes(textData.blocks);
+                    if (haplos != null){
+                        filtHaplos = filterHaplos(haplos);
+                        textData.pickTags(filtHaplos);
+                        textData.saveHapsToText(haplos, textData.computeMultiDprime(filtHaplos), outputFile);
+                    }else {
+                        commandLogger.info("Skipping block output: no valid 4 Gamete blocks.");
+                    }
+
+                    outputFile = validateOutputFile(outputRootName + ".SPINEblocks");
+                    textData.guessBlocks(BLOX_SPINE);
+
+                    haplos = textData.generateBlockHaplotypes(textData.blocks);
+                    if (haplos != null){
+                        filtHaplos = filterHaplos(haplos);
+                        textData.pickTags(filtHaplos);
+                        textData.saveHapsToText(haplos, textData.computeMultiDprime(filtHaplos), outputFile);
+                    }else {
+                        commandLogger.info("Skipping block output: no valid LD Spine blocks.");
+                    }
+
+                }else{
+                    //guesses blocks based on output type determined above.
+                    textData.guessBlocks(blockOutputType, cust);
+
+                    haplos = textData.generateBlockHaplotypes(textData.blocks);
+                    if (haplos != null){
+                        filtHaplos = filterHaplos(haplos);
+                        textData.pickTags(filtHaplos);
+                        textData.saveHapsToText(haplos, textData.computeMultiDprime(filtHaplos), outputFile);
+                    }else {
+                        commandLogger.info("Skipping block output: no valid blocks.");
+                    }
+                }
+
+                if(Options.getAssocTest() == ASSOC_TRIO || Options.getAssocTest() == ASSOC_CC) {
+                    if (blockOutputType == BLOX_ALL){
+                        commandLogger.warn("Haplotype association results cannot be used with block output \"ALL\"");
+                    }else{
+                        if (haplos != null){
+                            blockTestSet = new AssociationTestSet(haplos,null);
+                            blockTestSet.saveHapsToText(validateOutputFile(outputRootName + ".HAPASSOC"));
+
+                        }else {
+                            commandLogger.info("Skipping block association output: no valid blocks.");
+                        }
+                    }
+                }
+            }
+
+            if(outputDprime) {
+                outputFile = validateOutputFile(outputRootName + ".LD");
+                if (textData.dpTable != null){
+                    textData.saveDprimeToText(outputFile, TABLE_TYPE, 0, Chromosome.getSize());
+                }else{
+                    textData.saveDprimeToText(outputFile, LIVE_TYPE, 0, Chromosome.getSize());
+                }
+            }
+
+            if (outputPNG || outputCompressedPNG){
+                outputFile = validateOutputFile(outputRootName + ".LD.PNG");
+                if (textData.dpTable == null){
+                    textData.generateDPrimeTable();
+                    textData.guessBlocks(BLOX_CUSTOM, new Vector());
+                }
+                if (trackName != null){
+                    textData.readAnalysisTrack(getInputStream(trackName));
+                    commandLogger.info("Using analysis track file: " + trackName);
+                }
+                if (infoTrack){
+                    if (chromosomeArg.equals("") && (fileType == PED_FILE || fileType == HAPS_FILE || fileType == PHASEHMP_FILE)){
+                        commandLogger.warn("-infoTrack requires a -chromosome specification when used with this filetype");
+                    }else{
+                        Options.setShowGBrowse(true);
+                    }
+                }
+                DPrimeDisplay dpd = new DPrimeDisplay(textData);
+                BufferedImage i = dpd.export(0,Chromosome.getUnfilteredSize(),outputCompressedPNG);
+                try{
+                    Jimi.putImage("image/png", i, outputFile.getAbsolutePath());
+                }catch(JimiException je){
+                    System.out.println(je.getMessage());
+                }
+            }
+
+            if (outputSVG){
+                outputFile = validateOutputFile(outputRootName + ".LD.SVG");
+                if (textData.dpTable == null){
+                    textData.generateDPrimeTable();
+                    textData.guessBlocks(BLOX_CUSTOM, new Vector());
+                }
+                if (trackName != null){
+                    textData.readAnalysisTrack(getInputStream(trackName));
+                    commandLogger.info("Using analysis track file: " + trackName);
+                }
+                if (infoTrack){
+                    if (chromosomeArg.equals("") && (fileType == PED_FILE || fileType == HAPS_FILE || fileType == PHASEHMP_FILE)){
+                        commandLogger.warn("-infoTrack requires a -chromosome specification when used with this filetype");
+                    }else{
+                        Options.setShowGBrowse(true);
+                    }
+                }
+                DPrimeDisplay dpd = new DPrimeDisplay(textData);
+                SVGGraphics2D svg = dpd.exportSVG(0,Chromosome.getUnfilteredSize());
+                try{
+                    Writer out = new OutputStreamWriter(new FileOutputStream(outputFile), "UTF-8");
+                    svg.stream(out, true);
+                }catch (IOException ioe){
+                    commandLogger.error("An error occured writing the LD SVG file.");
+                }
+            }
+
+            AssociationTestSet markerTestSet =null;
+            if(Options.getAssocTest() == ASSOC_TRIO || Options.getAssocTest() == ASSOC_CC){
+                if (randomizeAffection){
+                    Vector aff = new Vector();
+                    for (int i = 0; i < textData.getPedFile().getNumIndividuals(); i++){
+                        if (i%2 == 0){
+                            aff.add(new Integer(1));
+                        }else{
+                            aff.add(new Integer(2));
+                        }
+                    }
+                    Collections.shuffle(aff);
+                    markerTestSet = new AssociationTestSet(textData.getPedFile(),aff,null,Chromosome.getAllMarkers());
+                }else{
+                    markerTestSet = new AssociationTestSet(textData.getPedFile(),null,null,Chromosome.getAllMarkers());
+                }
+                markerTestSet.saveSNPsToText(validateOutputFile(outputRootName + ".ASSOC"));
+            }
+
+            if(customAssocSet != null) {
+                commandLogger.info("Using custom association test file " + customAssocTestsFileName);
+                try {
+                    customAssocSet.setPermTests(doPermutationTest);
+                    customAssocSet.runFileTests(textData,markerTestSet.getMarkerAssociationResults());
+                    customAssocSet.saveResultsToText(validateOutputFile(outputRootName + ".CUSTASSOC"));
+
+                }catch(IOException ioe) {
+                    commandLogger.error("An error occured writing the custom association results file.");
+                    customAssocSet = null;
+                }
+            }
+
+            if(doPermutationTest) {
+                AssociationTestSet permTests = new AssociationTestSet();
+                permTests.cat(markerTestSet);
+                if(blockTestSet != null) {
+                    permTests.cat(blockTestSet);
+                }
+                final PermutationTestSet pts = new PermutationTestSet(permutationCount,textData.getPedFile(),customAssocSet,permTests);
+                Thread permThread = new Thread(new Runnable() {
+                    public void run() {
+                        if (pts.isCustom()){
+                            pts.doPermutations(PermutationTestSet.CUSTOM);
+                        }else{
+                            pts.doPermutations(PermutationTestSet.SINGLE_PLUS_BLOCKS);
+                        }
+                    }
+                });
+
+                permThread.start();
+
+
+                commandLogger.info("Starting " + permutationCount + " permutation tests (each . printed represents 1% of tests completed)");
+
+                int dotsPrinted =0;
+                while(pts.getPermutationCount() - pts.getPermutationsPerformed() > 0) {
+                    while(( (double)pts.getPermutationsPerformed() / pts.getPermutationCount())*100 > dotsPrinted) {
+                        System.out.print(".");
+                        dotsPrinted++;
+                    }
+                    try{
+                        Thread.sleep(100);
+                    }catch(InterruptedException ie) {}
+                }
+                StringBuffer buffer = new StringBuffer();
+                for (int i = 0; i < dotsPrinted; i++){
+                    buffer.append(".");
+                }
+                logger.info(buffer.toString());
+                System.out.println();
+
+                try {
+                    pts.writeResultsToFile(validateOutputFile(fileName  + ".PERMUT"));
+                } catch(IOException ioe) {
+                    commandLogger.error("An error occured while writing the permutation test results to file.");
+                }
+            }
+
+
+            if(tagging != Tagger.NONE) {
+
+                if(textData.dpTable == null) {
+                    textData.generateDPrimeTable();
+                }
+                Vector snps = Chromosome.getAllMarkers();
+                HashSet names = new HashSet();
+                for (int i = 0; i < snps.size(); i++) {
+                    SNP snp = (SNP) snps.elementAt(i);
+                    names.add(snp.getDisplayName());
+                }
+
+                HashSet filteredNames = new HashSet();
+                for(int i=0;i<Chromosome.getSize();i++) {
+                    filteredNames.add(Chromosome.getMarker(i).getDisplayName());
+                }
+
+                Vector sitesToCapture = new Vector();
+                if (captureAlleleTags == null){
+                    for(int i=0;i<Chromosome.getSize();i++) {
+                        sitesToCapture.add(Chromosome.getMarker(i));
+                    }
+                }else{
+                    for (int i = 0; i < captureAlleleTags.size(); i++){
+                        if (snpsByName.containsKey(captureAlleleTags.get(i))){
+                            sitesToCapture.add(snpsByName.get(captureAlleleTags.get(i)));
+                        }
+                    }
+                }
+
+                if (forceIncludeName != null){
+                    commandLogger.info("Using force include tags file: " + forceIncludeName);
+                }
+                if (forceExcludeName != null){
+                    commandLogger.info("Using force exclude tags file: " + forceExcludeName);
+                }
+                if (designScoresName != null){
+                    commandLogger.info("Using design scores file: " + designScoresName);
+                }
+                if (captureAllelesName != null){
+                    commandLogger.info("Using capture alleles file: " + captureAllelesName);
+                }
+
+                for (int i = 0; i < forceIncludeTags.size(); i++) {
+                    String s = (String) forceIncludeTags.elementAt(i);
+                    if(!names.contains(s)) {
+                        commandLogger.info("Warning: skipping marker " + s + " in the list of forced included tags since I don't know about it.");
+                    }
+                }
+
+                for (int i = 0; i < forceExcludeTags.size(); i++) {
+                    String s = (String) forceExcludeTags.elementAt(i);
+                    if(!names.contains(s)) {
+                        commandLogger.info("Warning: skipping marker " + s + " in the list of forced excluded tags since I don't know about it.");
+                    }
+                }
+
+                //chuck out filtered jazz from excludes, and nonexistent markers from both
+                forceExcludeTags.retainAll(filteredNames);
+                forceIncludeTags.retainAll(names);
+
+                commandLogger.info("Starting tagging.");
+
+                TaggerController tc = new TaggerController(textData,forceIncludeTags,forceExcludeTags,sitesToCapture,
+                        designScores,tagging,maxNumTags,findTags);
+                tc.runTagger();
+
+                while(!tc.isTaggingCompleted()) {
+                    try {
+                        Thread.sleep(100);
+                    }catch(InterruptedException ie) {}
+                }
+
+                tc.saveResultsToFile(validateOutputFile(outputRootName + ".TAGS"));
+                tc.dumpTests(validateOutputFile(outputRootName + ".TESTS"));
+                if (outputConditionalHaps){
+                    tc.dumpConditionalHaps(validateOutputFile(outputRootName + ".CHAPS"));
+                }
+                //todo: I don't like this at the moment, removed subject to further consideration.
+                //tc.dumpTags(validateOutputFile(outputRootName + ".TAGSNPS"));
+            }
+        }
+        catch(IOException e){
+            System.err.println("An error has occured:");
+            System.err.println(e.getMessage());
+        }
+        catch(HaploViewException e){
+            System.err.println(e.getMessage());
+        }
+        catch(PedFileException pfe) {
+            System.err.println(pfe.getMessage());
+        }
+        catch(TaggerException te){
+            System.err.println(te.getMessage());
+        }
+    }
+
+
+    public Haplotype[][] filterHaplos(Haplotype[][] haplos) {
+        if (haplos == null){
+            return null;
+        }
+        Haplotype[][] filteredHaplos = new Haplotype[haplos.length][];
+        for (int i = 0; i < haplos.length; i++){
+            Vector tempVector = new Vector();
+            for (int j = 0; j < haplos[i].length; j++){
+                if (haplos[i][j].getPercentage() > Options.getHaplotypeDisplayThreshold()){
+                    tempVector.add(haplos[i][j]);
+                }
+            }
+            filteredHaplos[i] = new Haplotype[tempVector.size()];
+            tempVector.copyInto(filteredHaplos[i]);
+        }
+
+        return filteredHaplos;
+
+    }
+}
diff --git a/edu/mit/wi/haploview/HaploView.java b/edu/mit/wi/haploview/HaploView.java
new file mode 100755
index 0000000..c066127
--- /dev/null
+++ b/edu/mit/wi/haploview/HaploView.java
@@ -0,0 +1,1924 @@
+package edu.mit.wi.haploview;
+
+
+import edu.mit.wi.pedfile.PedFileException;
+import edu.mit.wi.haploview.association.*;
+import edu.mit.wi.haploview.tagger.TaggerConfigPanel;
+import edu.mit.wi.haploview.tagger.TaggerResultsPanel;
+import edu.mit.wi.plink.Plink;
+import edu.mit.wi.plink.PlinkException;
+
+import javax.help.*;
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.event.*;
+import java.io.*;
+import java.util.*;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import com.sun.jimi.core.Jimi;
+import com.sun.jimi.core.JimiException;
+import org.apache.batik.svggen.SVGGraphics2D;
+
+public class HaploView extends JFrame implements ActionListener, Constants{
+    static final long serialVersionUID = -1918948520091554405L;
+
+    boolean DEBUG = false;
+
+    JMenuItem readMarkerItem, analysisItem, blocksItem, gbrowseItem, spacingItem, gbEditItem;
+    String exportItems[] = {
+            EXPORT_TEXT, EXPORT_PNG, EXPORT_OPTIONS
+    };
+    JMenuItem exportMenuItems[];
+    JMenu keyMenu, displayMenu, analysisMenu;
+    JMenuItem clearBlocksItem;
+
+    String viewItems[] = {
+            VIEW_DPRIME, VIEW_HAPLOTYPES, VIEW_CHECK_PANEL, VIEW_TAGGER, VIEW_PLINK, VIEW_ASSOC
+    };
+    JRadioButtonMenuItem viewMenuItems[];
+    String zoomItems[] = {
+            "Zoomed", "Medium", "Unzoomed"
+    };
+    JRadioButtonMenuItem zoomMenuItems[];
+    String colorItems[] = {
+            "Standard (D' / LOD)", "R-squared", "D' / LOD (alt)", "Confidence bounds", "4 Gamete", "GOLD heatmap"
+    };
+    JRadioButtonMenuItem colorMenuItems[];
+    JRadioButtonMenuItem blockMenuItems[];
+    String blockItems[] = {"Confidence intervals (Gabriel et al)",
+            "Four Gamete Rule",
+            "Solid spine of LD",
+            "Custom"};
+    String printValueItems[] = {
+            "D'","R-squared","None"
+    };
+    JRadioButtonMenuItem printValueMenuItems[];
+
+
+    HaploData theData;
+
+    private int currentBlockDef = BLOX_GABRIEL;
+    private javax.swing.Timer timer;
+
+    static HaploView window;
+    private Plink plink;
+    private Vector phasedSelection, colsToRemove;
+    private Hashtable removedCols;
+    public static JFileChooser fc;
+    private JScrollPane hapScroller;
+    HaploviewTabbedPane tabs;
+
+    DPrimeDisplay dPrimeDisplay;
+    HaplotypeDisplay hapDisplay;
+    CheckDataPanel checkPanel;
+    CustomAssocPanel custAssocPanel;
+    TDTPanel tdtPanel;
+    HaploAssocPanel hapAssocPanel;
+    PermutationTestPanel permutationPanel;
+    TaggerResultsPanel taggerResultsPanel;
+    TaggerConfigPanel taggerConfigPanel;
+    PlinkResultsPanel plinkPanel;
+
+    JProgressBar haploProgress;
+    boolean isMaxSet = false;
+    JPanel progressPanel = new JPanel();
+    LayoutManager defaultLayout = new GridBagLayout();
+
+    public HaploView(){
+        try{
+            fc = new JFileChooser(System.getProperty("user.dir"));
+        }catch(NullPointerException n){
+            try{
+                UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
+                fc = new JFileChooser(System.getProperty("user.dir"));
+                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+            }catch(Exception e){
+                JOptionPane.showMessageDialog(this,
+                        e.getMessage(),
+                        "Error",
+                        JOptionPane.ERROR_MESSAGE);
+            }
+        }
+
+
+        //menu setup
+        JMenuBar menuBar = new JMenuBar();
+        setJMenuBar(menuBar);
+        JMenuItem menuItem;
+
+        //file menu
+        JMenu fileMenu = new JMenu("File");
+        menuBar.add(fileMenu);
+
+        menuItem = new JMenuItem(READ_GENOTYPES);
+        setAccelerator(menuItem, 'O', false);
+        menuItem.addActionListener(this);
+        fileMenu.add(menuItem);
+
+        readMarkerItem = new JMenuItem(READ_MARKERS);
+        setAccelerator(readMarkerItem, 'I', false);
+        readMarkerItem.addActionListener(this);
+        readMarkerItem.setEnabled(false);
+        fileMenu.add(readMarkerItem);
+
+        analysisItem = new JMenuItem(READ_ANALYSIS_TRACK);
+        setAccelerator(analysisItem, 'A', false);
+        analysisItem.addActionListener(this);
+        analysisItem.setEnabled(false);
+        fileMenu.add(analysisItem);
+
+        blocksItem = new JMenuItem(READ_BLOCKS_FILE);
+        setAccelerator(blocksItem, 'B', false);
+        blocksItem.addActionListener(this);
+        blocksItem.setEnabled(false);
+        fileMenu.add(blocksItem);
+
+        gbrowseItem = new JMenuItem(DOWNLOAD_GBROWSE);
+        gbrowseItem.addActionListener(this);
+        gbrowseItem.setEnabled(false);
+        fileMenu.add(gbrowseItem);
+
+        fileMenu.addSeparator();
+
+        exportMenuItems = new JMenuItem[exportItems.length];
+        for (int i = 0; i < exportItems.length; i++) {
+            exportMenuItems[i] = new JMenuItem(exportItems[i]);
+            exportMenuItems[i].addActionListener(this);
+            exportMenuItems[i].setEnabled(false);
+            fileMenu.add(exportMenuItems[i]);
+        }
+
+        //update menu item
+        fileMenu.addSeparator();
+        menuItem = new JMenuItem("Check for update");
+        menuItem.addActionListener(this);
+        fileMenu.add(menuItem);
+
+
+        fileMenu.addSeparator();
+        menuItem = new JMenuItem("Quit");
+        setAccelerator(menuItem, 'Q', false);
+        menuItem.addActionListener(this);
+        fileMenu.add(menuItem);
+
+        fileMenu.setMnemonic(KeyEvent.VK_F);
+
+        /// display menu
+        displayMenu = new JMenu("Display");
+        displayMenu.setMnemonic(KeyEvent.VK_D);
+        menuBar.add(displayMenu);
+
+        ButtonGroup group = new ButtonGroup();
+        viewMenuItems = new JRadioButtonMenuItem[viewItems.length];
+        for (int i = 0; i < viewItems.length; i++) {
+            viewMenuItems[i] = new JRadioButtonMenuItem(viewItems[i], i == 0);
+            viewMenuItems[i].addActionListener(this);
+
+            KeyStroke ks = KeyStroke.getKeyStroke('1' + i, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
+            viewMenuItems[i].setAccelerator(ks);
+
+            displayMenu.add(viewMenuItems[i]);
+            viewMenuItems[i].setEnabled(false);
+            group.add(viewMenuItems[i]);
+        }
+        displayMenu.addSeparator();
+        //a submenu
+        ButtonGroup zg = new ButtonGroup();
+        JMenu zoomMenu = new JMenu("LD zoom");
+        zoomMenu.setMnemonic(KeyEvent.VK_Z);
+        zoomMenuItems = new JRadioButtonMenuItem[zoomItems.length];
+        for (int i = 0; i < zoomItems.length; i++){
+            zoomMenuItems[i] = new JRadioButtonMenuItem(zoomItems[i], i==0);
+            zoomMenuItems[i].addActionListener(this);
+            zoomMenuItems[i].setActionCommand("zoom" + i);
+            zoomMenu.add(zoomMenuItems[i]);
+            zg.add(zoomMenuItems[i]);
+        }
+        displayMenu.add(zoomMenu);
+        //another submenu
+        ButtonGroup cg = new ButtonGroup();
+        JMenu colorMenu = new JMenu("LD color scheme");
+        colorMenu.setMnemonic(KeyEvent.VK_C);
+        colorMenuItems = new JRadioButtonMenuItem[colorItems.length];
+        for (int i = 0; i< colorItems.length; i++){
+            colorMenuItems[i] = new JRadioButtonMenuItem(colorItems[i],i==0);
+            colorMenuItems[i].addActionListener(this);
+            colorMenuItems[i].setActionCommand("color" + i);
+            colorMenu.add(colorMenuItems[i]);
+            cg.add(colorMenuItems[i]);
+        }
+        colorMenuItems[Options.getLDColorScheme()].setSelected(true);
+        displayMenu.add(colorMenu);
+
+        ButtonGroup pg = new ButtonGroup();
+        JMenu printValueMenu = new JMenu("Show LD values");
+        printValueMenu.setMnemonic(KeyEvent.VK_V);
+        printValueMenuItems = new JRadioButtonMenuItem[printValueItems.length];
+        for (int i = 0; i< printValueItems.length; i++){
+            printValueMenuItems[i] = new JRadioButtonMenuItem(printValueItems[i],i==0);
+            printValueMenuItems[i].addActionListener(this);
+            printValueMenuItems[i].setActionCommand("printvalue" + i);
+            printValueMenu.add(printValueMenuItems[i]);
+            pg.add(printValueMenuItems[i]);
+        }
+        printValueMenuItems[Options.getPrintWhat()].setSelected(true);
+        displayMenu.add(printValueMenu);
+
+        spacingItem = new JMenuItem("LD Display Spacing");
+        spacingItem.setMnemonic(KeyEvent.VK_S);
+        spacingItem.addActionListener(this);
+        spacingItem.setEnabled(false);
+        displayMenu.add(spacingItem);
+
+        //gbrowse options editor
+        gbEditItem = new JMenuItem(GBROWSE_OPTS);
+        gbEditItem.setMnemonic(KeyEvent.VK_H);
+        gbEditItem.addActionListener(this);
+        gbEditItem.setEnabled(false);
+        displayMenu.add(gbEditItem);
+
+        //show block tag pooper?
+        JCheckBoxMenuItem pooper = new JCheckBoxMenuItem("Show tags in blocks");
+        pooper.addActionListener(this);
+        displayMenu.add(pooper);
+
+        displayMenu.setEnabled(false);
+
+        //analysis menu
+        analysisMenu = new JMenu("Analysis");
+        analysisMenu.setMnemonic(KeyEvent.VK_A);
+        menuBar.add(analysisMenu);
+        //a submenu
+        ButtonGroup bg = new ButtonGroup();
+        JMenu blockMenu = new JMenu("Define Blocks");
+        blockMenu.setMnemonic(KeyEvent.VK_B);
+        blockMenuItems = new JRadioButtonMenuItem[blockItems.length];
+        for (int i = 0; i < blockItems.length; i++){
+            blockMenuItems[i] = new JRadioButtonMenuItem(blockItems[i], i==0);
+            blockMenuItems[i].addActionListener(this);
+            blockMenuItems[i].setActionCommand("block" + i);
+            blockMenuItems[i].setEnabled(false);
+            blockMenu.add(blockMenuItems[i]);
+            bg.add(blockMenuItems[i]);
+        }
+        analysisMenu.add(blockMenu);
+        clearBlocksItem = new JMenuItem(CLEAR_BLOCKS);
+        setAccelerator(clearBlocksItem, 'C', false);
+        clearBlocksItem.addActionListener(this);
+        clearBlocksItem.setEnabled(false);
+        analysisMenu.add(clearBlocksItem);
+        JMenuItem customizeBlocksItem = new JMenuItem(CUST_BLOCKS);
+        customizeBlocksItem.addActionListener(this);
+        analysisMenu.add(customizeBlocksItem);
+        analysisMenu.setEnabled(false);
+
+        JMenu helpMenu = new JMenu("Help");
+        helpMenu.setMnemonic(KeyEvent.VK_H);
+        menuBar.add(helpMenu);
+
+        JMenuItem helpContentsItem;
+        helpContentsItem = new JMenuItem("Help Contents");
+        HelpSet hs;
+        HelpBroker hb;
+        String helpHS = "jhelpset.hs";
+        try {
+            URL hsURL = HelpSet.findHelpSet(HaploView.class.getClassLoader(), helpHS);
+            hs = new HelpSet(null, hsURL);
+        } catch (Exception ee) {
+            System.out.println( "HelpSet " + ee.getMessage());
+            System.out.println("HelpSet "+ helpHS +" not found");
+            return;
+        }
+        hb = hs.createHelpBroker();
+        helpContentsItem.addActionListener(new CSH.DisplayHelpFromSource(hb));
+        helpContentsItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0));
+        helpMenu.add(helpContentsItem);
+
+        menuItem = new JMenuItem("About Haploview");
+        menuItem.addActionListener(this);
+        helpMenu.add(menuItem);
+
+
+        //color key
+        keyMenu = new JMenu("Key");
+        menuBar.add(Box.createHorizontalGlue());
+        menuBar.add(keyMenu);
+
+
+
+        /*
+        Configuration.readConfigFile();
+        if(Configuration.isCheckForUpdate()) {
+            Object[] options = {"Yes",
+                                "Not now",
+                                "Never ask again"};
+            int n = JOptionPane.showOptionDialog(this,
+                    "Would you like to check if a new version "
+                    + "of haploview is available?",
+                    "Check for update",
+                    JOptionPane.YES_NO_CANCEL_OPTION,
+                    JOptionPane.QUESTION_MESSAGE,
+                    null,
+                    options,
+                    options[1]);
+
+            if(n == JOptionPane.YES_OPTION) {
+                UpdateChecker uc = new UpdateChecker();
+                if(uc.checkForUpdate()) {
+                    JOptionPane.showMessageDialog(this,
+                            "A new version of Haploview is available!\n Visit http://www.broad.mit.edu/mpg/haploview/ to download the new version\n (current version: " + Constants.VERSION
+                            + "  newest version: " + uc.getNewVersion() + ")" ,
+                            "Update Available",
+                            JOptionPane.INFORMATION_MESSAGE);
+                }
+            }
+            else if(n == JOptionPane.CANCEL_OPTION) {
+                Configuration.setCheckForUpdate(false);
+                Configuration.writeConfigFile();
+            }
+        }           */
+
+        addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent e){
+                quit();
+            }
+        });
+    }
+
+
+    // function workaround for overdesigned, underthought swing api -fry
+    void setAccelerator(JMenuItem menuItem, char what, boolean shift) {
+        menuItem.setAccelerator(KeyStroke.getKeyStroke(what, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | (shift ? ActionEvent.SHIFT_MASK : 0)));
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if (command.equals(READ_GENOTYPES)){
+            ReadDataDialog readDialog = new ReadDataDialog("Open new data", this);
+            readDialog.pack();
+            readDialog.setVisible(true);
+        } else if (command.equals(READ_MARKERS)){
+            //JFileChooser fc = new JFileChooser(System.getProperty("user.dir"));
+            fc.setSelectedFile(new File(""));
+            int returnVal = fc.showOpenDialog(this);
+            if (returnVal == JFileChooser.APPROVE_OPTION) {
+                File markerFile = fc.getSelectedFile();
+                if (markerFile.length() < 1){
+                    JOptionPane.showMessageDialog(this,
+                            "Marker information file is empty or non-existent: " + markerFile.getName(),
+                            "File Error",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+                InputStream infoStream = null;
+                try{
+                    infoStream = new FileInputStream(markerFile);
+                }catch(IOException ioe){
+                    JOptionPane.showMessageDialog(this,
+                            "Error reading the marker information file: " + markerFile.getName(),
+                            "File Error",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+                readMarkers(infoStream,null);
+            }
+        }else if (command.equals(READ_ANALYSIS_TRACK)){
+            fc.setSelectedFile(new File(""));
+            int returnVal = fc.showOpenDialog(this);
+            if (returnVal == JFileChooser.APPROVE_OPTION){
+                readAnalysisFile(fc.getSelectedFile());
+            }
+        }else if (command.equals(DOWNLOAD_GBROWSE)){
+            GBrowseDialog gbd = new GBrowseDialog(this, "Connect to HapMap Info Server");
+            gbd.pack();
+            gbd.setVisible(true);
+        }else if (command.equals(GBROWSE_OPTS)){
+            GBrowseOptionDialog gbod = new GBrowseOptionDialog(this, "HapMap Info Track Options");
+            gbod.pack();
+            gbod.setVisible(true);
+        }else if (command.equals(READ_BLOCKS_FILE)){
+            fc.setSelectedFile(new File(""));
+            if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION){
+                readBlocksFile(fc.getSelectedFile());
+            }
+        }else if (command.equals(CUST_BLOCKS)){
+            TweakBlockDefsDialog tweakDialog = new TweakBlockDefsDialog("Customize Blocks", this);
+            tweakDialog.pack();
+            tweakDialog.setVisible(true);
+        }else if (command.equals(CLEAR_BLOCKS)){
+            changeBlocks(BLOX_NONE);
+
+            //blockdef clauses
+        }else if (command.startsWith("block")){
+            int method = Integer.valueOf(command.substring(5)).intValue();
+
+            changeBlocks(method);
+
+            //zooming clauses
+        }else if (command.startsWith("zoom")){
+            dPrimeDisplay.zoom(Integer.valueOf(command.substring(4)).intValue());
+
+            //coloring clauses
+        }else if (command.startsWith("color")){
+            Options.setLDColorScheme(Integer.valueOf(command.substring(5)).intValue());
+            dPrimeDisplay.colorDPrime();
+            changeKey();
+
+            //which LD value to print.
+        }else if (command.startsWith("printvalue")){
+            Options.setPrintWhat(Integer.valueOf(command.substring(10)).intValue());
+            dPrimeDisplay.colorDPrime();
+
+            //exporting clauses
+        }else if (command.equals(EXPORT_PNG)){
+            export(tabs.getSelectedPrimary(), PNG_MODE, 0, Chromosome.getUnfilteredSize());
+        }else if (command.equals(EXPORT_TEXT)){
+            export(tabs.getSelectedPrimary(), TXT_MODE, 0, Chromosome.getUnfilteredSize());
+        }else if (command.equals(EXPORT_OPTIONS)){
+            ExportDialog exDialog = new ExportDialog(this);
+            exDialog.pack();
+            exDialog.setVisible(true);
+        }else if (command.equals("Show tags in blocks")){
+            Options.setShowBlockTags(((JCheckBoxMenuItem)e.getSource()).getState());
+            hapDisplay.repaint();
+        }else if (command.equals("LD Display Spacing")){
+            ProportionalSpacingDialog spaceDialog = new ProportionalSpacingDialog(this, "Adjust LD Spacing");
+            spaceDialog.pack();
+            spaceDialog.setVisible(true);
+        }else if (command.equals("About Haploview")){
+            JOptionPane.showMessageDialog(this,
+                    ABOUT_STRING,
+                    "About Haploview",
+                    JOptionPane.INFORMATION_MESSAGE);
+        } else if(command.equals("Check for update")) {
+            final SwingWorker worker = new SwingWorker(){
+                UpdateChecker uc;
+                String unableToConnect;
+                public Object construct() {
+                    uc = new UpdateChecker();
+                    try {
+                        uc.checkForUpdate();
+                    } catch(IOException ioe) {
+                        unableToConnect = ioe.getMessage();
+                    }
+                    return null;
+                }
+                public void finished() {
+                    window.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+                    if(uc != null) {
+                        if(unableToConnect != null) {
+                            JOptionPane.showMessageDialog(window,
+                                    "An error occured while checking for update.\n " + unableToConnect ,
+                                    "Update Check",
+                                    JOptionPane.ERROR_MESSAGE);
+                        }
+                        else if(uc.isNewVersionAvailable()) {
+                            UpdateDisplayDialog udp = new UpdateDisplayDialog(window,"Update Check",uc);
+                            udp.pack();
+                            udp.setVisible(true);
+                        } else {
+                            JOptionPane.showMessageDialog(window,
+                                    "Your version of Haploview is up to date.",
+                                    "Update Check",
+                                    JOptionPane.INFORMATION_MESSAGE);
+                        }
+                    }
+
+                }
+            };
+            setCursor(new Cursor(Cursor.WAIT_CURSOR));
+            worker.start();
+        }else if (command.equals("Quit")){
+            quit();
+        } else {
+            for (int i = 0; i < tabs.getTabCount(); i++) {
+                if (command.equals(tabs.getTitleAt(i))){
+                    tabs.setSelectedIndex(i);
+                    break;
+                }
+            }
+        }
+    }
+
+    private void changeKey() {
+        int scheme = Options.getLDColorScheme();
+        keyMenu.removeAll();
+        if (scheme == WMF_SCHEME){
+            JMenuItem keyItem = new JMenuItem("High D' / High LOD");
+            Dimension size = keyItem.getPreferredSize();
+            keyItem.setBackground(Color.black);
+            keyItem.setForeground(Color.white);
+            keyMenu.add(keyItem);
+            for (int i = 1; i < 5; i++){
+                double gr = i * (255.0 / 6.0);
+                keyItem = new JMenuItem("");
+                keyItem.setPreferredSize(size);
+                keyItem.setBackground(new Color((int)gr, (int)gr, (int)gr));
+                keyMenu.add(keyItem);
+            }
+            keyItem = new JMenuItem("Low D' / Low LOD");
+            keyItem.setBackground(Color.white);
+            keyMenu.add(keyItem);
+
+            keyItem = new JMenuItem("High D' / High LOD");
+            keyItem.setBackground(Color.black);
+            keyItem.setForeground(Color.white);
+            keyMenu.add(keyItem);
+            for (int i = 1; i < 5; i++){
+                double r = i * (255.0 / 6.0);
+                double gb = i * (200.0 / 6.0);
+                keyItem = new JMenuItem("");
+                keyItem.setPreferredSize(size);
+                keyItem.setBackground(new Color((int)r, (int)gb, (int)gb));
+                keyMenu.add(keyItem);
+            }
+            keyItem = new JMenuItem("High D' / Low LOD");
+            keyItem.setBackground(new Color(255, 200, 200));
+            keyMenu.add(keyItem);
+        } else if (scheme == RSQ_SCHEME){
+            JMenuItem keyItem = new JMenuItem("High R-squared");
+            Dimension size = keyItem.getPreferredSize();
+            keyItem.setBackground(Color.black);
+            keyItem.setForeground(Color.white);
+            keyMenu.add(keyItem);
+            keyItem = new JMenuItem("");
+            keyItem.setPreferredSize(size);
+            keyItem.setBackground(Color.darkGray);
+            keyMenu.add(keyItem);
+            keyItem = new JMenuItem("");
+            keyItem.setPreferredSize(size);
+            keyItem.setBackground(Color.gray);
+            keyMenu.add(keyItem);
+            keyItem = new JMenuItem("");
+            keyItem.setPreferredSize(size);
+            keyItem.setBackground(Color.lightGray);
+            keyMenu.add(keyItem);
+            keyItem = new JMenuItem("Low R-squared");
+            keyItem.setBackground(Color.white);
+            keyMenu.add(keyItem);
+        } else if (scheme == STD_SCHEME){
+            JMenuItem keyItem = new JMenuItem("High D'");
+            Dimension size = keyItem.getPreferredSize();
+            keyItem.setBackground(Color.red);
+            keyMenu.add(keyItem);
+            for (int i = 1; i < 4; i++){
+                double blgr = (255-32)*2*(0.5*i/3);
+                keyItem = new JMenuItem("");
+                keyItem.setPreferredSize(size);
+                keyItem.setBackground(new Color(255,(int)blgr, (int)blgr));
+                keyMenu.add(keyItem);
+            }
+            keyItem = new JMenuItem("Low D'");
+            keyItem.setBackground(Color.white);
+            keyMenu.add(keyItem);
+            keyItem = new JMenuItem("High D' / Low LOD");
+            keyItem.setBackground(new Color(192, 192, 240));
+            keyMenu.add(keyItem);
+        } else if (scheme == GAB_SCHEME){
+            JMenuItem keyItem = new JMenuItem("Strong Linkage");
+            keyItem.setBackground(Color.darkGray);
+            keyItem.setForeground(Color.white);
+            keyMenu.add(keyItem);
+            keyItem = new JMenuItem("Uninformative");
+            keyItem.setBackground(Color.lightGray);
+            keyMenu.add(keyItem);
+            keyItem = new JMenuItem("Recombination");
+            keyItem.setBackground(Color.white);
+            keyMenu.add(keyItem);
+        } else if (scheme == GAM_SCHEME){
+            JMenuItem keyItem = new JMenuItem("< 4 Gametes");
+            keyItem.setBackground(Color.darkGray);
+            keyItem.setForeground(Color.white);
+            keyMenu.add(keyItem);
+            keyItem = new JMenuItem("4 Gametes");
+            keyItem.setBackground(Color.white);
+            keyMenu.add(keyItem);
+        }else if (scheme == GOLD_SCHEME){
+            JMenuItem keyItem = new JMenuItem("High D'");
+            Dimension size = keyItem.getPreferredSize();
+            keyItem.setBackground(Color.red);
+            keyMenu.add(keyItem);
+            keyItem = new JMenuItem("");
+            keyItem.setPreferredSize(size);
+            keyItem.setBackground(new Color(255,255,0));
+            keyMenu.add(keyItem);
+            keyItem = new JMenuItem("");
+            keyItem.setPreferredSize(size);
+            keyItem.setBackground(Color.green);
+            keyMenu.add(keyItem);
+            keyItem = new JMenuItem("");
+            keyItem.setPreferredSize(size);
+            keyItem.setBackground(new Color(0,255,255));
+            keyMenu.add(keyItem);
+            keyItem = new JMenuItem("Low D'");
+            keyItem.setForeground(Color.white);
+            keyItem.setBackground(new Color(0,0,127));
+            keyMenu.add(keyItem);
+        }
+    }
+
+    void quit(){
+        //any handling that might need to take place here
+        Configuration.writeConfigFile();
+        System.exit(0);
+    }
+
+    void readAnalysisFile(File inFile){
+        try{
+            theData.readAnalysisTrack(new FileInputStream(inFile));
+        }catch (HaploViewException hve){
+            JOptionPane.showMessageDialog(this,
+                    hve.getMessage(),
+                    "File Error",
+                    JOptionPane.ERROR_MESSAGE);
+        }catch (IOException ioe){
+            JOptionPane.showMessageDialog(this,
+                    ioe.getMessage(),
+                    "File Error",
+                    JOptionPane.ERROR_MESSAGE);
+        }
+        dPrimeDisplay.computePreferredSize();
+        if (dPrimeDisplay != null && tabs.getTitleAt(tabs.getSelectedIndex()).equals(VIEW_DPRIME)){
+            dPrimeDisplay.repaint();
+        }
+    }
+
+    void readGenotypes(String[] inputOptions, int type){
+        //input is a 2 element array with
+        //inputOptions[0] = ped file or phased data file
+        //inputOptions[1] = info file or sample file for phased data (null if none)
+        //inputOptions[2] = custom association test list file or legend file for phased data (null if none)
+        //type is either 3 or 4 for ped and hapmap files respectively
+        //type is 6 for phased hapmap files
+        //type is 7 for phased hapmap downloads
+        final File inFile = new File(inputOptions[0]);
+        final AssociationTestSet customAssocSet;
+        final int progressType = type;
+
+        try {
+            if (type == HMPDL_FILE){
+                phasedSelection = new Vector();
+                phasedSelection.add(inputOptions[5]);
+                phasedSelection.add(inputOptions[4]);
+                phasedSelection.add(inputOptions[1]);
+                phasedSelection.add(inputOptions[2]);
+                phasedSelection.add(inputOptions[3]);
+            }
+            if (type != PHASEHMP_FILE && type != HMPDL_FILE){
+                if (inputOptions[2] != null && inputOptions[1] == null){
+                    throw new HaploViewException("A marker information file is required if a tests file is specified.");
+                }
+            }
+
+            if (inputOptions[1] == null && Options.getAssocTest() != ASSOC_NONE){
+                throw new HaploViewException("A marker information file is required for association tests.");
+            }
+
+            this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+            if (type == HAPS_FILE){
+                //these are not available for non ped files
+                viewMenuItems[VIEW_CHECK_NUM].setEnabled(false);
+                viewMenuItems[VIEW_ASSOC_NUM].setEnabled(false);
+                Options.setAssocTest(ASSOC_NONE);
+            }
+            theData = new HaploData();
+
+            if (type == HAPS_FILE){
+                theData.prepareHapsInput(inputOptions[0]);
+            }else if (type == PHASEHMP_FILE || type == HMPDL_FILE /*|| type == FASTPHASE_FILE*/){
+                theData.phasedToChrom(inputOptions, type);
+            }else{
+                theData.linkageToChrom(inputOptions[0], type);
+            }
+
+            if (type != PHASEHMP_FILE && type != HMPDL_FILE /*&& type != FASTPHASE_FILE*/){
+                if(theData.getPedFile().isBogusParents()) {
+                    JOptionPane.showMessageDialog(this,
+                            "One or more individuals in the file reference non-existent parents.\nThese references have been ignored.",
+                            "File Error",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+            }
+
+            if(theData.getPedFile().getHaploidHets() != null) {
+                JOptionPane.showMessageDialog(this,
+                        "At least one male in the file is heterozygous.\nThese genotypes have been ignored.",
+                        "File Error",
+                        JOptionPane.ERROR_MESSAGE);
+            }
+
+
+            //deal with marker information
+            theData.infoKnown = false;
+
+            //turn on/off gbrowse menu
+            if (Options.isGBrowseShown()){
+                gbEditItem.setEnabled(true);
+            }else{
+                gbEditItem.setEnabled(false);
+            }
+
+            InputStream markerStream = null;
+            if (inputOptions[1] != null && type != PHASEHMP_FILE && type != HMPDL_FILE){
+                try {
+                    URL markerURL = new URL(inputOptions[1]);
+                    markerStream = markerURL.openStream();
+                }catch (MalformedURLException mfe){
+                    File markerFile = new File(inputOptions[1]);
+                    if (markerFile.length() < 1){
+                        JOptionPane.showMessageDialog(this,
+                                "Marker information file is empty or non-existent: " + markerFile.getName(),
+                                "File Error",
+                                JOptionPane.ERROR_MESSAGE);
+                    }
+                    markerStream = new FileInputStream(markerFile);
+                }catch (IOException ioe){
+                    JOptionPane.showMessageDialog(this,
+                            "Could not connect to " + inputOptions[1],
+                            "File Error",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+            }
+            if (type == HAPS_FILE){
+                readMarkers(markerStream, null);
+                //initialize realIndex
+                Chromosome.doFilter(Chromosome.getUnfilteredSize());
+                customAssocSet = null;
+                theData.getPedFile().setWhiteList(new HashSet());
+                checkPanel = new CheckDataPanel(this);
+            }else if (type == PHASEHMP_FILE || type == HMPDL_FILE){
+                readMarkers(null, theData.getPedFile().getHMInfo());
+                Chromosome.doFilter(Chromosome.getUnfilteredSize());
+                customAssocSet = null;
+                theData.getPedFile().setWhiteList(new HashSet());
+                checkPanel = new CheckDataPanel(this);
+                Chromosome.doFilter(checkPanel.getMarkerResults());
+            }/*else if (type == FASTPHASE_FILE){  //TODO: How do we get marker info for fastPHASE?
+                readMarkers(markerStream, null);
+                Chromosome.doFilter(Chromosome.getUnfilteredSize());
+                customAssocSet = null;
+                theData.getPedFile().setWhiteList(new HashSet());
+                checkPanel = new CheckDataPanel(this);
+                Chromosome.doFilter(checkPanel.getMarkerResults());
+            }*/else{
+                readMarkers(markerStream, theData.getPedFile().getHMInfo());
+                //we read the file in first, so we can whitelist all the markers in the custom test set
+                HashSet whiteListedCustomMarkers = new HashSet();
+                if (inputOptions[2] != null){
+                    customAssocSet = new AssociationTestSet(inputOptions[2]);
+                    whiteListedCustomMarkers = customAssocSet.getWhitelist();
+                }else{
+                    customAssocSet = null;
+                }
+                theData.getPedFile().setWhiteList(whiteListedCustomMarkers);
+
+                checkPanel = new CheckDataPanel(this);
+                checkPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
+
+                //set up the indexing to take into account skipped markers.
+                Chromosome.doFilter(checkPanel.getMarkerResults());
+            }
+
+
+            //let's start the math
+            final SwingWorker worker = new SwingWorker(){
+                public Object construct(){
+                    Container contents = getContentPane();
+                    contents.removeAll();
+                    contents.repaint();
+                    defaultLayout = contents.getLayout();
+                    contents.setLayout(new GridBagLayout());
+                    haploProgress = new JProgressBar(0,2);
+                    haploProgress.setValue(0);
+                    haploProgress.setStringPainted(true);
+                    haploProgress.setForeground(new Color(40,40,255));
+                    haploProgress.setPreferredSize(new Dimension(250,20));
+                    progressPanel.setLayout(new BoxLayout(progressPanel,BoxLayout.Y_AXIS));
+                    JLabel progressLabel;
+                    if (progressType != HMPDL_FILE){
+                        progressLabel = new JLabel("Loading data...");
+                    }else{
+                        progressLabel = new JLabel("Downloading HapMap data...");
+                    }
+                    progressPanel.add(progressLabel);
+                    progressLabel.setAlignmentX(CENTER_ALIGNMENT);
+                    progressPanel.add(haploProgress);
+                    contents.add(progressPanel);
+                    progressPanel.revalidate();
+
+                    for (int i = 0; i < viewMenuItems.length; i++){
+                        viewMenuItems[i].setEnabled(false);
+                    }
+                    dPrimeDisplay=null;
+
+                    changeKey();
+                    theData.generateDPrimeTable();
+                    theData.guessBlocks(BLOX_GABRIEL);
+                    //theData.guessBlocks(BLOX_NONE);  //for debugging, doesn't call blocks at first
+
+                    blockMenuItems[0].setSelected(true);
+                    zoomMenuItems[0].setSelected(true);
+                    theData.blocksChanged = false;
+                    contents = getContentPane();
+                    contents.removeAll();
+
+                    tabs = new HaploviewTabbedPane();
+                    tabs.addChangeListener(new TabChangeListener());
+
+                    //first, draw the D' picture
+                    dPrimeDisplay = new DPrimeDisplay(window);
+                    JScrollPane dPrimeScroller = new JScrollPane(dPrimeDisplay);
+                    dPrimeScroller.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
+                    dPrimeScroller.getVerticalScrollBar().setUnitIncrement(60);
+                    dPrimeScroller.getHorizontalScrollBar().setUnitIncrement(60);
+                    HaploviewTab ldTab = new HaploviewTab(dPrimeDisplay);
+                    ldTab.add(dPrimeScroller);
+                    tabs.addTab(VIEW_DPRIME, ldTab);
+                    viewMenuItems[VIEW_D_NUM].setEnabled(true);
+
+                    //compute and show haps on next tab
+                    try {
+                        hapDisplay = new HaplotypeDisplay(theData);
+                    } catch(HaploViewException e) {
+                        JOptionPane.showMessageDialog(window,
+                                e.getMessage(),
+                                "Error",
+                                JOptionPane.ERROR_MESSAGE);
+                    }
+                    HaplotypeDisplayController hdc =
+                            new HaplotypeDisplayController(hapDisplay);
+                    hapScroller = new JScrollPane(hapDisplay);
+                    hapScroller.getVerticalScrollBar().setUnitIncrement(60);
+                    hapScroller.getHorizontalScrollBar().setUnitIncrement(60);
+                    HaploviewTab hapsTab = new HaploviewTab(hapDisplay);
+                    hapsTab.add(hapScroller);
+                    hapsTab.add(hdc);
+                    tabs.addTab(VIEW_HAPLOTYPES, hapsTab);
+                    viewMenuItems[VIEW_HAP_NUM].setEnabled(true);
+                    displayMenu.setEnabled(true);
+                    analysisMenu.setEnabled(true);
+
+                    //check data panel
+                    HaploviewTab checkTab = new HaploviewTab(checkPanel);
+                    checkTab.add(checkPanel);
+                    CheckDataController cdc = new CheckDataController(checkPanel);
+                    checkTab.add(cdc);
+
+                    tabs.addTab(VIEW_CHECK_PANEL, checkTab);
+                    viewMenuItems[VIEW_CHECK_NUM].setEnabled(true);
+                    tabs.setSelectedComponent(checkTab);
+
+
+                    //only show tagger if we have a .info file
+                    if (theData.infoKnown){
+                        //tagger display
+
+                        taggerConfigPanel = new TaggerConfigPanel(theData,plink != null);
+                        HaploviewTabbedPane tagTabs = new HaploviewTabbedPane();
+                        tagTabs.add("Configuration",taggerConfigPanel);
+
+                        taggerResultsPanel = new TaggerResultsPanel();
+                        taggerConfigPanel.addActionListener(taggerResultsPanel);
+                        tagTabs.addTab("Results",taggerResultsPanel);
+
+                        HaploviewTab taggerTab = new HaploviewTab(tagTabs);
+                        taggerTab.add(tagTabs);
+                        tabs.addTab(VIEW_TAGGER,taggerTab);
+                        viewMenuItems[VIEW_TAGGER_NUM].setEnabled(true);
+                    }
+
+                    //Association panel
+                    if(Options.getAssocTest() != ASSOC_NONE) {
+                        HaploviewTabbedPane metaAssoc = new HaploviewTabbedPane();
+                        try{
+                            tdtPanel = new TDTPanel(new AssociationTestSet(theData.getPedFile(), null,null, Chromosome.getAllMarkers()));
+                        } catch(PedFileException e) {
+                            JOptionPane.showMessageDialog(window,
+                                    e.getMessage(),
+                                    "Error",
+                                    JOptionPane.ERROR_MESSAGE);
+                        }
+
+
+                        metaAssoc.add("Single Marker", tdtPanel);
+
+                        hapAssocPanel = new HaploAssocPanel(new AssociationTestSet(theData.getHaplotypes(), null));
+                        metaAssoc.add("Haplotypes", hapAssocPanel);
+
+                        //custom association tests
+                        custAssocPanel = null;
+                        if(customAssocSet != null) {
+                            try {
+                                customAssocSet.runFileTests(theData, tdtPanel.getTestSet().getMarkerAssociationResults());
+                                custAssocPanel = new CustomAssocPanel(customAssocSet);
+                                metaAssoc.addTab("Custom",custAssocPanel);
+                                metaAssoc.setSelectedComponent(custAssocPanel);
+                            } catch (HaploViewException e) {
+                                JOptionPane.showMessageDialog(window,
+                                        e.getMessage(),
+                                        "Error",
+                                        JOptionPane.ERROR_MESSAGE);
+                            }
+                        }
+
+                        AssociationTestSet custPermSet = null;
+                        if (custAssocPanel != null){
+                            custPermSet = custAssocPanel.getTestSet();
+                        }
+                        AssociationTestSet permSet = new AssociationTestSet();
+                        permSet.cat(tdtPanel.getTestSet());
+                        permSet.cat(hapAssocPanel.getTestSet());
+
+                        permutationPanel = new PermutationTestPanel(
+                                new PermutationTestSet(0,theData.getPedFile(),custPermSet, permSet));
+                        metaAssoc.add(permutationPanel,"Permutation Tests");
+
+                        HaploviewTab associationTab = new HaploviewTab(metaAssoc);
+                        associationTab.add(metaAssoc);
+                        tabs.addTab(VIEW_ASSOC, associationTab);
+                        viewMenuItems[VIEW_ASSOC_NUM].setEnabled(true);
+
+                    }
+
+                    if(plinkPanel != null){
+                        HaploviewTab plinkTab = new HaploviewTab(plinkPanel);
+                        plinkTab.add(plinkPanel);
+                        tabs.addTab(VIEW_PLINK, plinkTab);
+                        viewMenuItems[VIEW_PLINK_NUM].setEnabled(true);
+                    }
+
+
+                    contents.remove(progressPanel);
+                    contents.setLayout(defaultLayout);
+                    contents.add(tabs);
+
+                    repaint();
+                    setVisible(true);
+
+                    theData.finished = true;
+                    setTitle(TITLE_STRING + " -- " + inFile.getName());
+                    return null;
+                }
+            };
+
+            timer = new javax.swing.Timer(50, new ActionListener(){
+                public void actionPerformed(ActionEvent evt){
+                    if (isMaxSet){
+                        haploProgress.setValue(theData.dPrimeCount);
+                    }
+                    if (theData.finished){
+                        timer.stop();
+                        for (int i = 0; i < blockMenuItems.length; i++){
+                            blockMenuItems[i].setEnabled(true);
+                        }
+                        clearBlocksItem.setEnabled(true);
+                        readMarkerItem.setEnabled(true);
+                        blocksItem.setEnabled(true);
+                        exportMenuItems[2].setEnabled(true);
+                        progressPanel.removeAll();
+                        isMaxSet = false;
+                        theData.dPrimeCount = 0;
+                        theData.dPrimeTotalCount = -1;
+
+                        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+                    }
+                    if (theData.dPrimeTotalCount != -1 && !isMaxSet){
+                        haploProgress.setMaximum(theData.dPrimeTotalCount);
+                        isMaxSet = true;
+                    }
+                }
+            });
+
+            worker.start();
+            timer.start();
+        }catch(IOException ioexec) {
+            JOptionPane.showMessageDialog(this,
+                    ioexec.getMessage(),
+                    "File Error",
+                    JOptionPane.ERROR_MESSAGE);
+            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+        }catch(PedFileException pfe){
+            JOptionPane.showMessageDialog(this,
+                    pfe.getMessage(),
+                    "File Error",
+                    JOptionPane.ERROR_MESSAGE);
+            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+        }catch (HaploViewException hve){
+            JOptionPane.showMessageDialog(this,
+                    hve.getMessage(),
+                    "File Error",
+                    JOptionPane.ERROR_MESSAGE);
+            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+        }
+    }
+
+    void readWGA(String[] inputOptions) {
+        String wgaFile = inputOptions[0];
+        String mapFile = inputOptions[1];
+        String secondaryFile = inputOptions[2];
+        String embeddedMap = inputOptions[3];
+        String fisherColumn = inputOptions[4];
+        String chrom = inputOptions[5];
+        boolean embed = false;
+        this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+        if (plink == null){
+            plink = new Plink();
+        }
+        try{
+            if (fisherColumn != null){
+                Vector columns = new Vector();
+                StringTokenizer st = new StringTokenizer(fisherColumn);
+                while (st.hasMoreTokens()){
+                    columns.add(new Integer(st.nextToken()));
+                }
+                plink.doFisherCombined(columns);
+            }
+
+            if (embeddedMap != null){
+                embed = true;
+            }
+
+            if (inputOptions[6] != null && (wgaFile != null || secondaryFile != null)){
+                try{
+                    BufferedReader wgaReader = null;
+                    if (wgaFile != null){
+                        try{
+                            URL columnURL = new URL(wgaFile);
+                            wgaReader = new BufferedReader(new InputStreamReader(columnURL.openStream()));
+                        }catch(MalformedURLException mfe){
+                            wgaReader = new BufferedReader(new FileReader(new File(wgaFile)));
+                        }catch(IOException ioe){
+                            JOptionPane.showMessageDialog(this,
+                                    "Could not connect to " + wgaFile,
+                                    "File Error",
+                                    JOptionPane.ERROR_MESSAGE);
+                        }
+                    }else{
+                        wgaReader = new BufferedReader(new FileReader(new File(secondaryFile)));
+                    }
+                    String columnLine = wgaReader.readLine();
+                    wgaReader.close();
+                    Vector colChoices = new Vector();
+                    StringTokenizer ct = new StringTokenizer(columnLine);
+                    while (ct.hasMoreTokens()){
+                        String currentCol = ct.nextToken();
+                        if (Options.getSNPBased()){
+                            if (!currentCol.equalsIgnoreCase("SNP") && !currentCol.equalsIgnoreCase("CHR") && !currentCol.equalsIgnoreCase("POS") && !currentCol.equalsIgnoreCase("POSITION") && !currentCol.equalsIgnoreCase("BP")){
+                                colChoices.add(currentCol);
+                            }
+                        }else{
+                            if (!currentCol.equalsIgnoreCase("FID") && !currentCol.equalsIgnoreCase("IID")){
+                                colChoices.add(currentCol);
+                            }
+                        }
+                    }
+                    ColumnChooser colChooser = new ColumnChooser(this,"Select Columns",colChoices);
+                    colChooser.pack();
+                    colChooser.setVisible(true);
+                }catch(IOException ioe){
+                    JOptionPane.showMessageDialog(this,
+                            "Error reading the results file.",
+                            "File Error",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+            }
+
+            if (wgaFile != null){
+                if (Options.getSNPBased()){
+                    plink.parseWGA(wgaFile,mapFile,embed,chrom,colsToRemove);
+                }else{
+                    plink.parseNonSNP(wgaFile,colsToRemove);
+                }
+            }
+            if (secondaryFile != null){
+                Vector v = plink.parseMoreResults(secondaryFile,colsToRemove);
+                Vector im = plink.getIgnoredMarkers();
+                if (im != null){
+                    if (im.size() != 0){
+                        IgnoredMarkersDialog imd = new IgnoredMarkersDialog(this,"Ignored Markers",im,true);
+                        imd.pack();
+                        imd.setVisible(true);
+                    }
+                }
+                for (int i = 0; i < v.size(); i++){
+                    JOptionPane.showMessageDialog(this,
+                            "A column already appears in the dataset.\n" +
+                                    "The new column is marked as " + v.get(i),
+                            "Duplicate value",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+            }
+            if (colsToRemove != null){
+                colsToRemove.clear();
+            }
+
+            plinkPanel = new PlinkResultsPanel(this,plink.getResults(),plink.getColumnNames(),plink.getPlinkDups(),removedCols);
+            if (removedCols != null){
+                removedCols.clear();
+            }
+            HaploviewTab plinkTab = new HaploviewTab(plinkPanel);
+            plinkTab.add(plinkPanel);
+
+
+            tabs = new HaploviewTabbedPane();
+            tabs.addTab(VIEW_PLINK, plinkTab);
+            readMarkerItem.setEnabled(false);
+            analysisItem.setEnabled(false);
+            blocksItem.setEnabled(false);
+            gbrowseItem.setEnabled(false);
+            exportMenuItems[0].setEnabled(true);
+            displayMenu.setEnabled(false);
+            analysisMenu.setEnabled(false);
+            keyMenu.setEnabled(false);
+            tabs.setSelectedComponent(plinkTab);
+
+
+            Container contents = getContentPane();
+            contents.removeAll();
+            contents.repaint();
+            contents.add(tabs);
+
+            if (wgaFile != null){
+                File plinkFile = new File(wgaFile);
+                setTitle(TITLE_STRING + " -- " + plinkFile.getName());
+            }
+
+            repaint();
+            setVisible(true);
+        }catch(PlinkException wge){
+            JOptionPane.showMessageDialog(this,
+                    wge.getMessage(),
+                    "File Error",
+                    JOptionPane.ERROR_MESSAGE);
+        }
+        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+
+    }
+
+    void readBlocksFile(File file) {
+        try{
+            Vector cust = theData.readBlocks(new FileInputStream(file));
+            theData.guessBlocks(BLOX_CUSTOM, cust);
+            changeBlocks(BLOX_CUSTOM);
+        }catch (HaploViewException hve){
+            JOptionPane.showMessageDialog(this,
+                    hve.getMessage(),
+                    "File Error",
+                    JOptionPane.ERROR_MESSAGE);
+        }catch (IOException ioe){
+            JOptionPane.showMessageDialog(this,
+                    ioe.getMessage(),
+                    "File Error",
+                    JOptionPane.ERROR_MESSAGE);
+        }
+    }
+
+    void readMarkers(InputStream is, String[][] hminfo){
+        try {
+            theData.prepareMarkerInput(is, hminfo);
+            if (theData.infoKnown){
+                analysisItem.setEnabled(true);
+                gbrowseItem.setEnabled(true);
+                spacingItem.setEnabled(true);
+            }else{
+                analysisItem.setEnabled(false);
+                gbrowseItem.setEnabled(false);
+                spacingItem.setEnabled(false);
+            }
+            if (checkPanel != null && plink == null){
+                //this is triggered when loading markers after already loading genotypes
+                //it is dumb and sucks, but at least it works. bah.
+                checkPanel = new CheckDataPanel(this);
+                HaploviewTab checkTab = ((HaploviewTab)tabs.getComponentAt(VIEW_CHECK_NUM));
+                checkTab.removeAll();
+
+                JPanel metaCheckPanel = new JPanel();
+                metaCheckPanel.setLayout(new BoxLayout(metaCheckPanel, BoxLayout.Y_AXIS));
+                metaCheckPanel.add(checkPanel);
+                CheckDataController cdc = new CheckDataController(checkPanel);
+                metaCheckPanel.add(cdc);
+
+                checkTab.add(metaCheckPanel);
+                repaint();
+            }
+            if (tdtPanel != null){
+                tdtPanel.refreshNames();
+            }
+
+            if (dPrimeDisplay != null && plinkPanel == null){
+                dPrimeDisplay.computePreferredSize();
+            }
+        }catch (HaploViewException e){
+            JOptionPane.showMessageDialog(this,
+                    e.getMessage(),
+                    "Error",
+                    JOptionPane.ERROR_MESSAGE);
+        }catch (IOException ioexec){
+            JOptionPane.showMessageDialog(this,
+                    ioexec.getMessage(),
+                    "File Error",
+                    JOptionPane.ERROR_MESSAGE);
+        }
+    }
+
+    public int getCurrentBlockDef() {
+        return currentBlockDef;
+    }
+
+    public void changeBlocks(int method){
+        if (method == BLOX_NONE || method == BLOX_CUSTOM){
+            blockMenuItems[BLOX_CUSTOM].setSelected(true);
+        }
+        if (method != BLOX_CUSTOM){
+            theData.guessBlocks(method);
+        }
+
+        dPrimeDisplay.repaint();
+        currentBlockDef = method;
+
+        try{
+            if (tabs.getTitleAt(tabs.getSelectedIndex()).equals(VIEW_HAPLOTYPES)){
+                hapDisplay.getHaps();
+            }
+        }catch(HaploViewException hve) {
+            JOptionPane.showMessageDialog(this,
+                    hve.getMessage(),
+                    "Error",
+                    JOptionPane.ERROR_MESSAGE);
+        }
+        hapScroller.setViewportView(hapDisplay);
+    }
+
+    public String getChosenMarker(){
+        if (plinkPanel != null){
+            return plinkPanel.getChosenMarker();
+        }else{
+            return null;
+        }
+    }
+
+    public Vector getPhasedSelection(){
+        return phasedSelection;
+    }
+
+    public void setRemovedColumns(Hashtable removed){
+        removedCols = removed;
+    }
+
+    public void clearDisplays() {
+        if (tabs != null){
+            tabs.removeAll();
+            dPrimeDisplay = null;
+            hapDisplay = null;
+            tdtPanel = null;
+            checkPanel = null;
+            if (plinkPanel != null){
+                plinkPanel.disposePlot();
+            }
+            plinkPanel = null;
+            plink = null; //todo?
+        }
+    }
+
+    class TabChangeListener implements ChangeListener{
+        public void stateChanged(ChangeEvent e) {
+            if (tabs.getSelectedIndex() != -1){
+                window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+
+                String title = tabs.getTitleAt(tabs.getSelectedIndex());
+                if (title.equals(VIEW_DPRIME) || title.equals(VIEW_HAPLOTYPES)){
+                    exportMenuItems[0].setEnabled(true);
+                    exportMenuItems[1].setEnabled(true);
+                    exportMenuItems[2].setEnabled(true);
+                }else if (title.equals(VIEW_ASSOC) || title.equals(VIEW_CHECK_PANEL) || title.equals(VIEW_TAGGER)){
+                    exportMenuItems[0].setEnabled(true);
+                    exportMenuItems[1].setEnabled(false);
+                    exportMenuItems[2].setEnabled(true);
+                }else if (title.equals(VIEW_PLINK)){
+                    exportMenuItems[0].setEnabled(true);
+                    exportMenuItems[1].setEnabled(false);
+                    exportMenuItems[2].setEnabled(false);
+                }else{
+                    exportMenuItems[0].setEnabled(false);
+                    exportMenuItems[1].setEnabled(false);
+                }
+
+                //if we've adjusted the haps display thresh we need to change the haps ass panel
+                if (title.equals(VIEW_ASSOC)){
+                    JTabbedPane metaAssoc = ((JTabbedPane)((HaploviewTab)tabs.getSelectedComponent()).getComponent(0));
+                    //this is the haps ass tab inside the assoc super-tab
+                    HaploAssocPanel htp = (HaploAssocPanel) metaAssoc.getComponent(1);
+                    if (htp.initialHaplotypeDisplayThreshold != Options.getHaplotypeDisplayThreshold() &&
+                            !Chromosome.getDataChrom().equalsIgnoreCase("chrx")){
+                        htp.makeTable(new AssociationTestSet(theData.getHaplotypes(), null));
+                        permutationPanel.setBlocksChanged();
+                        AssociationTestSet custSet = null;
+                        if (custAssocPanel != null){
+                            custSet = custAssocPanel.getTestSet();
+                        }
+                        AssociationTestSet permSet = new AssociationTestSet();
+                        if (tdtPanel != null){
+                            permSet.cat(tdtPanel.getTestSet());
+                        }
+                        if (hapAssocPanel != null){
+                            permSet.cat(hapAssocPanel.getTestSet());
+                        }
+                        permutationPanel.setTestSet(
+                                new PermutationTestSet(0,theData.getPedFile(),custSet,permSet));
+                    }
+                }
+
+                if (title.equals(VIEW_DPRIME)){
+                    keyMenu.setEnabled(true);
+                }else{
+                    keyMenu.setEnabled(false);
+                }
+
+                viewMenuItems[tabs.getSelectedIndex()].setSelected(true);
+
+                if (checkPanel != null && checkPanel.changed){
+                    //first store up the current blocks
+                    Vector currentBlocks = new Vector();
+                    for (int blocks = 0; blocks < theData.blocks.size(); blocks++){
+                        int thisBlock[] = (int[]) theData.blocks.elementAt(blocks);
+                        int thisBlockReal[] = new int[thisBlock.length];
+                        for (int marker = 0; marker < thisBlock.length; marker++){
+                            thisBlockReal[marker] = Chromosome.realIndex[thisBlock[marker]];
+                        }
+                        currentBlocks.add(thisBlockReal);
+                    }
+
+                    Chromosome.doFilter(checkPanel.getMarkerResults());
+
+                    //after editing the filtered marker list, needs to be prodded into
+                    //resizing correctly
+                    dPrimeDisplay.computePreferredSize();
+                    dPrimeDisplay.colorDPrime();
+                    dPrimeDisplay.revalidate();
+
+                    hapDisplay.theData = theData;
+
+                    if (currentBlockDef != BLOX_CUSTOM){
+                        changeBlocks(currentBlockDef);
+                    }else{
+                        //adjust the blocks
+                        Vector theBlocks = new Vector();
+                        for (int x = 0; x < currentBlocks.size(); x++){
+                            Vector goodies = new Vector();
+                            int currentBlock[] = (int[])currentBlocks.elementAt(x);
+                            for (int marker = 0; marker < currentBlock.length; marker++){
+                                for (int y = 0; y < Chromosome.realIndex.length; y++){
+                                    //we only keep markers from the input that are "good" from checkdata
+                                    //we also realign the input file to the current "good" subset since input is
+                                    //indexed of all possible markers in the dataset
+                                    if (Chromosome.realIndex[y] == currentBlock[marker]){
+                                        goodies.add(new Integer(y));
+                                    }
+                                }
+                            }
+                            int thisBlock[] = new int[goodies.size()];
+                            for (int marker = 0; marker < thisBlock.length; marker++){
+                                thisBlock[marker] = ((Integer)goodies.elementAt(marker)).intValue();
+                            }
+                            if (thisBlock.length > 1){
+                                theBlocks.add(thisBlock);
+                            }
+                        }
+                        theData.guessBlocks(BLOX_CUSTOM, theBlocks);
+                    }
+
+                    if (tdtPanel != null){
+                        tdtPanel.refreshTable();
+                    }
+
+                    if (taggerConfigPanel != null){
+                        taggerConfigPanel.refreshTable();
+                    }
+
+                    if (taggerResultsPanel != null){
+                        for (int i = 0; i < tabs.getComponentCount(); i ++){
+                            if (tabs.getTitleAt(i).equals(VIEW_TAGGER)){
+                                ((HaploviewTabbedPane)(((HaploviewTab)(tabs.getComponentAt(i))).getPrimary())).removeTabAt(1);
+                                taggerResultsPanel = new TaggerResultsPanel();
+                                taggerConfigPanel.addActionListener(taggerResultsPanel);
+                                ((HaploviewTabbedPane)(((HaploviewTab)(tabs.getComponentAt(i))).getPrimary())).addTab("Results",taggerResultsPanel);
+                                break;
+                            }
+                        }
+                    }
+
+                    if(permutationPanel != null) {
+                        permutationPanel.setBlocksChanged();
+                        AssociationTestSet custSet = null;
+                        if (custAssocPanel != null){
+                            custSet = custAssocPanel.getTestSet();
+                        }
+                        AssociationTestSet permSet = new AssociationTestSet();
+                        if (tdtPanel != null){
+                            permSet.cat(tdtPanel.getTestSet());
+                        }
+                        if (hapAssocPanel != null){
+                            permSet.cat(hapAssocPanel.getTestSet());
+                        }
+                        permutationPanel.setTestSet(
+                                new PermutationTestSet(0,theData.getPedFile(),custSet,permSet));
+                    }
+
+                    checkPanel.changed=false;
+                }
+
+                if (hapDisplay != null && theData.blocksChanged){
+                    try{
+                        hapDisplay.getHaps();
+                        if(Options.getAssocTest() != ASSOC_NONE ){
+                            //this is the haps ass tab inside the assoc super-tab
+                            hapAssocPanel.makeTable(new AssociationTestSet(theData.getHaplotypes(), null));
+
+                            permutationPanel.setBlocksChanged();
+                            AssociationTestSet custSet = null;
+                            if (custAssocPanel != null){
+                                custSet = custAssocPanel.getTestSet();
+                            }
+                            AssociationTestSet permSet = new AssociationTestSet();
+                            if (tdtPanel != null){
+                                permSet.cat(tdtPanel.getTestSet());
+                            }
+                            if (hapAssocPanel != null){
+                                permSet.cat(hapAssocPanel.getTestSet());
+                            }
+                            permutationPanel.setTestSet(
+                                    new PermutationTestSet(0,theData.getPedFile(),custSet,permSet));
+                        }
+                    }catch(HaploViewException hv){
+                        JOptionPane.showMessageDialog(window,
+                                hv.getMessage(),
+                                "Error",
+                                JOptionPane.ERROR_MESSAGE);
+                    }
+                    hapScroller.setViewportView(hapDisplay);
+
+                    theData.blocksChanged = false;
+                }
+                if (theData.finished){
+                    setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+                }
+            }
+            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+        }
+    }
+
+    void export(Component c, int format, int start, int stop){
+        if (c == null) return;
+
+        fc.setSelectedFile(new File(""));
+        if (fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION){
+            File outfile = fc.getSelectedFile();
+
+            if (format == PNG_MODE || format == COMPRESSED_PNG_MODE){
+                BufferedImage image = null;
+                if (c.equals(dPrimeDisplay)){
+                    try {
+                        if (format == PNG_MODE){
+                            image = dPrimeDisplay.export(start, stop, false);
+                        }else{
+                            image = dPrimeDisplay.export(start, stop, true);
+                        }
+                    } catch(HaploViewException hve) {
+                        JOptionPane.showMessageDialog(this,
+                                hve.getMessage(),
+                                "Export Error",
+                                JOptionPane.ERROR_MESSAGE);
+                    }
+                }else if (c.equals(hapDisplay)){
+                    image = hapDisplay.export();
+                }else{
+                    image = new BufferedImage(1,1,BufferedImage.TYPE_3BYTE_BGR);
+                }
+
+                if (image != null){
+                    try{
+                        String filename = outfile.getPath();
+                        if (! (filename.endsWith(".png") || filename.endsWith(".PNG"))){
+                            filename += ".png";
+                        }
+                        Jimi.putImage("image/png", image, filename);
+                    }catch(JimiException je){
+                        JOptionPane.showMessageDialog(this,
+                                je.getMessage(),
+                                "Error",
+                                JOptionPane.ERROR_MESSAGE);
+                    }
+                }
+            }else if (format == SVG_MODE){
+                SVGGraphics2D svgGenerator = null;
+                if (c.equals(dPrimeDisplay)){
+                    try{
+                        svgGenerator = dPrimeDisplay.exportSVG(start, stop);
+                    }catch(HaploViewException hve){
+                        JOptionPane.showMessageDialog(this,
+                                hve.getMessage(),
+                                "Export Error",
+                                JOptionPane.ERROR_MESSAGE);
+                    }
+                }else if (c.equals(hapDisplay)){
+                    svgGenerator = hapDisplay.exportSVG();
+                }
+                boolean useCSS = true;
+                if (svgGenerator != null){
+                    try{
+                        String filename = outfile.getPath();
+                        if (! (filename.endsWith(".svg") || filename.endsWith(".SVG"))){
+                            filename += ".svg";
+                        }
+                        Writer out = new OutputStreamWriter(new FileOutputStream(new File(filename)), "UTF-8");
+                        svgGenerator.stream(out, useCSS);
+                    }catch (IOException ioe){
+                        JOptionPane.showMessageDialog(this,
+                                ioe.getMessage(),
+                                "Error",
+                                JOptionPane.ERROR_MESSAGE);
+                    }
+                }
+            } else if (format == TXT_MODE){
+                try{
+                    if (c.equals(dPrimeDisplay)){
+                        theData.saveDprimeToText(outfile, TABLE_TYPE, start, stop);
+                    }else if (c.equals(hapDisplay)){
+                        theData.saveHapsToText(hapDisplay.filteredHaplos,hapDisplay.multidprimeArray, outfile);
+                    }else if (c.equals(checkPanel)){
+                        checkPanel.saveTableToText(outfile);
+                    }else if (c.equals(tdtPanel)){
+                        tdtPanel.getTestSet().saveSNPsToText(outfile);
+                    }else if (c.equals(hapAssocPanel)){
+                        hapAssocPanel.getTestSet().saveHapsToText(outfile);
+                    }else if (c.equals(permutationPanel)){
+                        permutationPanel.export(outfile);
+                    }else if (c.equals(custAssocPanel)){
+                        custAssocPanel.getTestSet().saveResultsToText(outfile);
+                    }else if (c.equals(taggerConfigPanel) || c.equals(taggerResultsPanel)){
+                        taggerConfigPanel.export(outfile);
+                    }else if (c.equals(plinkPanel)){
+                        plinkPanel.exportTable(outfile);
+                    }
+                }catch(IOException ioe){
+                    JOptionPane.showMessageDialog(this,
+                            ioe.getMessage(),
+                            "Error",
+                            JOptionPane.ERROR_MESSAGE);
+                }catch(HaploViewException he){
+                    JOptionPane.showMessageDialog(this,
+                            he.getMessage(),
+                            "Error",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+            }
+        }
+    }
+
+    public static void main(String[] args) {
+        //this parses the command line arguments. if nogui mode is specified,
+        //then haploText will execute whatever the user specified
+        HaploText argParser = new HaploText(args);
+
+        //if nogui is specified, then HaploText has already executed everything, and let Main() return
+        //otherwise, we want to actually load and run the gui
+        if(!argParser.isNogui()) {
+            try {
+                UIManager.put("EditorPane.selectionBackground",Color.lightGray);
+                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+            } catch (Exception e) {
+                JOptionPane.showMessageDialog(window,
+                        e.getMessage(),
+                        "Error",
+                        JOptionPane.ERROR_MESSAGE);
+            }
+
+            window  =  new HaploView();
+
+            //setup view object
+            window.setTitle(TITLE_STRING);
+
+            //center the window on the screen
+            Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
+            window.setSize(1024,768);
+
+            window.setLocation((screen.width - window.getWidth()) / 2,
+                    (screen.height - window.getHeight()) / 2);
+
+            window.setVisible(true);
+            //if the screen isn't big enough jigger it to max allowable size
+            if (screen.getWidth() <= 1024){
+                window.setExtendedState(JFrame.MAXIMIZED_HORIZ);
+                if (screen.getHeight() <= 768){
+                    window.setExtendedState(JFrame.MAXIMIZED_BOTH);
+                }
+            }else if (screen.getHeight() <= 768){
+                window.setExtendedState(JFrame.MAXIMIZED_VERT);
+            }
+
+
+            if (Constants.BETA_VERSION > 0){
+                UpdateChecker betaUc;
+                betaUc = new UpdateChecker();
+                try {
+                    betaUc.checkForUpdate();
+                } catch(IOException ioe) {
+                    //this means we couldnt connect but we want it to die quietly
+                }
+
+                if (betaUc.isNewVersionAvailable()){
+                    UpdateDisplayDialog betaUdp = new UpdateDisplayDialog(window,"Update Check",betaUc);
+                    betaUdp.pack();
+                    betaUdp.setVisible(true);
+                }
+            }
+
+            final SwingWorker worker = showUpdatePanel();
+            worker.start();
+
+
+            //parse command line stuff for input files or prompt data dialog
+            String[] inputArray = new String[7];
+            if (!argParser.getCommandLineError()){
+                if (argParser.getHapsFileName() != null){
+                    inputArray[0] = argParser.getHapsFileName();
+                    inputArray[1] = argParser.getInfoFileName();
+                    inputArray[2] = null;
+                    window.readGenotypes(inputArray, HAPS_FILE);
+                }else if (argParser.getPedFileName() != null){
+                    inputArray[0] = argParser.getPedFileName();
+                    inputArray[1] = argParser.getInfoFileName();
+                    inputArray[2] = null;
+                    window.readGenotypes(inputArray, PED_FILE);
+                }else if (argParser.getHapmapFileName() != null){
+                    inputArray[0] = argParser.getHapmapFileName();
+                    inputArray[1] = null;
+                    inputArray[2] = null;
+                    window.readGenotypes(inputArray, HMP_FILE);
+                }else if (argParser.getPhasedHmpDataName() != null){
+                    if (!argParser.getChromosome().equals("")){
+                        Options.setShowGBrowse(true);
+                    }
+                    inputArray[0] = argParser.getPhasedHmpDataName();
+                    inputArray[1] = argParser.getPhasedHmpSampleName();
+                    inputArray[2] = argParser.getPhasedHmpLegendName();
+                    inputArray[3] = argParser.getChromosome();
+                    window.readGenotypes(inputArray, PHASEHMP_FILE);
+                }/*else if (argParser.getFastphaseFileName() != null){
+                    inputArray[0] = argParser.getFastphaseFileName();
+                    inputArray[1] = argParser.getInfoFileName();
+                    inputArray[2] = null;
+                    inputArray[3] = argParser.getChromosome();
+                    window.readGenotypes(inputArray, FASTPHASE_FILE);
+                }*/else if (argParser.getPhasedHmpDownload()){
+                    Options.setShowGBrowse(true);
+                    inputArray[0] = "Chr" + argParser.getChromosome() + ":" + argParser.getPanel() + ":" +
+                            argParser.getStartPos() + ".." + argParser.getEndPos();
+                    inputArray[1] = argParser.getPanel();
+                    inputArray[2] = argParser.getStartPos();
+                    inputArray[3] = argParser.getEndPos();
+                    inputArray[4] = argParser.getChromosome();
+                    inputArray[5] = argParser.getRelease();
+                    inputArray[6] = "txt";
+                    window.readGenotypes(inputArray, HMPDL_FILE);
+                }else if (argParser.getPlinkFileName() != null){
+                    inputArray[0] = argParser.getPlinkFileName();
+                    inputArray[1] = argParser.getMapFileName();
+                    inputArray[2] = null;
+                    inputArray[3] = null;
+                    inputArray[4] = null;
+                    inputArray[5] = argParser.getChromosome();
+                    inputArray[6] = argParser.getSelectCols();
+                    window.readWGA(inputArray);
+                }else{
+                    ReadDataDialog readDialog = new ReadDataDialog("Welcome to HaploView", window);
+                    readDialog.pack();
+                    readDialog.setVisible(true);
+                }
+            }
+        }
+    }
+
+    public static SwingWorker showUpdatePanel(){
+        final SwingWorker worker;
+        worker = new SwingWorker(){
+            UpdateChecker uc;
+            public Object construct() {
+                uc = new UpdateChecker();
+                try {
+                    uc.checkForUpdate();
+                } catch(IOException ioe) {
+                    //this means we couldnt connect but we want it to die quietly
+                }
+                return null;
+            }
+            public void finished() {
+                if(uc != null) {
+                    if(uc.isNewVersionAvailable()) {
+                        //theres an update available so lets pop some crap up
+                        final JLayeredPane jlp = window.getLayeredPane();
+
+                        final JPanel udp = new JPanel();
+
+                        udp.setLayout(new BoxLayout(udp, BoxLayout.Y_AXIS));
+                        double version = uc.getNewVersion();
+                        int betaVersion = uc.getNewBetaVersion();
+                        Font detailsFont = new Font("Default", Font.PLAIN, 14);
+                        JLabel announceLabel;
+                        if (betaVersion == -1){
+                            announceLabel = new JLabel("A newer version of Haploview (" +version+") is available.");
+                        }else{
+                            announceLabel = new JLabel("A newer BETA version of Haploview (" + version + "beta" + betaVersion+") is available.");
+                        }
+                        announceLabel.setFont(detailsFont);
+                        JLabel detailsLabel = new JLabel("See \"Check for update\" in the file menu for details.");
+                        detailsLabel.setFont(detailsFont);
+                        udp.add(announceLabel);
+                        udp.add(detailsLabel);
+
+                        udp.setBorder(BorderFactory.createRaisedBevelBorder());
+                        int width = udp.getPreferredSize().width;
+                        int height = udp.getPreferredSize().height;
+                        int borderwidth = udp.getBorder().getBorderInsets(udp).right;
+                        int borderheight = udp.getBorder().getBorderInsets(udp).bottom;
+                        udp.setBounds(jlp.getWidth()-width-borderwidth, jlp.getHeight()-height-borderheight,
+                                udp.getPreferredSize().width, udp.getPreferredSize().height);
+                        udp.setOpaque(true);
+
+                        jlp.add(udp, JLayeredPane.POPUP_LAYER);
+
+                        java.util.Timer updateTimer = new java.util.Timer();
+                        //show this update message for 6.5 seconds
+                        updateTimer.schedule(new TimerTask() {
+                            public void run() {
+                                jlp.remove(udp);
+                                jlp.repaint();
+                            }
+                        },6000);
+                    }
+                }
+            }
+        };
+        return worker;
+    }
+
+    class HaploviewTabbedPane extends JTabbedPane{
+        static final long serialVersionUID = -8460034374471805260L;
+
+        public Component getSelectedPrimary(){
+            //find selected component recursively in case there are tabs of tabs of tabs...
+            //return the primary component of the HaploviewTab, as opposed to the tab itself
+            Component c = ((HaploviewTab)getSelectedComponent()).getPrimary();
+            if (c instanceof HaploviewTabbedPane){
+                return ((HaploviewTabbedPane)c).getSelectedPrimary();
+            }else{
+                return c;
+            }
+        }
+
+    }
+
+    class IgnoredMarkersDialog extends JDialog implements ActionListener {
+        static final long serialVersionUID = -3168995002044399156L;
+
+        public IgnoredMarkersDialog (HaploView h, String title, Vector ignored, boolean extra){
+            super(h,title);
+
+            JPanel contents = new JPanel();
+            contents.setPreferredSize(new Dimension(200,200));
+            contents.setLayout(new BoxLayout(contents,BoxLayout.Y_AXIS));
+            JTable table;
+
+            Vector colNames = new Vector();
+            colNames.add("#");
+            colNames.add("SNP");
+
+            Vector data = new Vector();
+
+            for (int i = 0; i < ignored.size(); i++){
+                Vector tmpVec = new Vector();
+                tmpVec.add(new Integer(i+1));
+                tmpVec.add(ignored.get(i));
+                data.add(tmpVec);
+            }
+
+            TableSorter sorter = new TableSorter(new BasicTableModel(colNames, data));
+            table = new JTable(sorter);
+            sorter.setTableHeader(table.getTableHeader());
+            table.getColumnModel().getColumn(0).setPreferredWidth(30);
+            table.getColumnModel().getColumn(1).setPreferredWidth(75);
+
+            JScrollPane tableScroller = new JScrollPane(table);
+            tableScroller.setPreferredSize(new Dimension(75,300));
+
+            JLabel label;
+            if (extra){
+                label = new JLabel("<HTML><b>The following markers do not appear in the " +
+                        "loaded dataset and will therefore be ignored.</b>");
+            }else{
+                label = new JLabel("<HTML><b>The following markers do not appear in the " +
+                        "loaded mapfile and will therefore be ignored.</b>");
+            }
+            label.setAlignmentX(Component.CENTER_ALIGNMENT);
+            contents.add(label);
+            tableScroller.setAlignmentX(Component.CENTER_ALIGNMENT);
+            contents.add(tableScroller);
+            JButton okButton = new JButton("Close");
+            okButton.addActionListener(this);
+            okButton.setAlignmentX(Component.CENTER_ALIGNMENT);
+            contents.add(okButton);
+            setContentPane(contents);
+
+            this.setLocation(this.getParent().getX() + 100,
+                    this.getParent().getY() + 100);
+            this.setModal(true);
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            String command = e.getActionCommand();
+            if(command.equals("Close")) {
+                this.dispose();
+            }
+        }
+    }
+
+    class ColumnChooser extends JDialog implements ActionListener {
+        static final long serialVersionUID = 9005814069417052404L;
+
+        JCheckBox[] checks;
+        boolean select;
+
+        public ColumnChooser (HaploView h, String title, Vector columns){
+            super(h,title);
+
+            JPanel contents = new JPanel();
+            contents.setPreferredSize(new Dimension(150,405));
+            contents.setLayout(new BoxLayout(contents,BoxLayout.Y_AXIS));
+            checks = new JCheckBox[columns.size()];
+            for (int i = 0; i < columns.size(); i++){
+                checks[i] = new JCheckBox((String)columns.get(i));
+                checks[i].setSelected(true);
+            }
+
+            JPanel chPanel = new JPanel();
+            chPanel.setLayout(new BoxLayout(chPanel,BoxLayout.Y_AXIS));
+            for (int i = 0; i < checks.length; i++){
+                chPanel.add(checks[i]);
+            }
+            JScrollPane listPane = new JScrollPane(chPanel);
+
+            JLabel label = new JLabel("Select which columns to load:");
+            label.setAlignmentX(Component.CENTER_ALIGNMENT);
+            contents.add(label);
+            JButton selectDeselect = new JButton("Select/Deselect All");
+            selectDeselect.addActionListener(this);
+            selectDeselect.setAlignmentX(Component.CENTER_ALIGNMENT);
+            contents.add(selectDeselect);
+            contents.add(listPane);
+            JButton okButton = new JButton("Ok");
+            okButton.addActionListener(this);
+            okButton.setAlignmentX(Component.CENTER_ALIGNMENT);
+            contents.add(okButton);
+            setContentPane(contents);
+
+            this.setLocation(this.getParent().getX() + 100,
+                    this.getParent().getY() + 100);
+            this.getRootPane().setDefaultButton(okButton);
+            this.setModal(true);
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            String command = e.getActionCommand();
+            if(command.equals("Ok")) {
+                colsToRemove = new Vector();
+                for (int i = 0; i < checks.length; i++){
+                    if (!checks[i].isSelected()){
+                        colsToRemove.add(checks[i].getText());
+                    }
+                }
+                if (colsToRemove.size() == 0){
+                    colsToRemove = null;
+                }
+                this.dispose();
+            }else if (command.equals("Select/Deselect All")){
+                if (select){
+                    for (int i = 0; i < checks.length; i++){
+                        checks[i].setSelected(true);
+                    }
+                    select = !select;
+                }else{
+                    for (int i = 0; i < checks.length; i++){
+                        checks[i].setSelected(false);
+                    }
+                    select = !select;
+                }
+            }
+        }
+    }
+}
+
+
+
diff --git a/edu/mit/wi/haploview/HaploViewException.java b/edu/mit/wi/haploview/HaploViewException.java
new file mode 100755
index 0000000..65d4e3d
--- /dev/null
+++ b/edu/mit/wi/haploview/HaploViewException.java
@@ -0,0 +1,8 @@
+package edu.mit.wi.haploview;
+
+public class HaploViewException extends Exception {
+    static final long serialVersionUID = 1875791670387646L;
+         public HaploViewException(String s){
+             super(s);
+         }
+}
diff --git a/edu/mit/wi/haploview/Haplotype.java b/edu/mit/wi/haploview/Haplotype.java
new file mode 100755
index 0000000..2b40d2a
--- /dev/null
+++ b/edu/mit/wi/haploview/Haplotype.java
@@ -0,0 +1,160 @@
+package edu.mit.wi.haploview;
+
+
+public class Haplotype{
+    private static final String[] alleleCodes = {"X","A","C","G","T"};
+
+    private int[] genotypes;
+    private int[] markers;
+    private int listorder;
+    private boolean[] tags;
+    private double percentage;
+    private double[] crossovers;
+    private double transCount;
+    private double untransCount;
+    private double[] discordantAlleleCounts;
+    private double caseCount;
+    private double controlCount;
+    private EM blockEM;
+
+    public Haplotype(int[] g, double p, int[] m, EM em){
+        genotypes=g;
+        percentage = p;
+        markers = m;
+        tags = new boolean[genotypes.length];
+        listorder = 0;
+        blockEM = em;
+        discordantAlleleCounts = new double[9];
+    }
+
+    public int[] getGeno(){
+        return genotypes;
+    }
+
+    public double getPercentage(){
+        return percentage;
+    }
+
+    public void addCrossovers(double[] c){
+        crossovers = c;
+    }
+
+    public void addTag(int t){
+        tags[t] = true;
+    }
+
+    public void clearTags(){
+        for (int t = 0; t < tags.length; t++){
+            tags[t] = false;
+        }
+    }
+
+    public void setTags(boolean[] t){
+        tags = t;
+    }
+
+    public double getCrossover(int index){
+        return crossovers[index];
+    }
+
+    public double[] getCrossovers(){
+        return crossovers;
+    }
+
+    public int[] getMarkers(){
+        return markers;
+    }
+
+    public boolean[] getTags(){
+        return tags;
+    }
+
+    public int getListOrder(){
+        return listorder;
+    }
+
+    public void setListOrder(int slo){
+        listorder = slo;
+    }
+
+    public double getTransCount() {
+        return transCount;
+    }
+
+    public void setTransCount(double transCount) {
+        this.transCount = transCount;
+    }
+
+    public double getUntransCount() {
+        return untransCount;
+    }
+
+    public void setUntransCount(double untransCount) {
+        this.untransCount = untransCount;
+    }
+
+    public double getCaseCount() {
+        return caseCount;
+    }
+
+    public void setCaseCount(double caseCount) {
+        this.caseCount = caseCount;
+    }
+
+    public double getControlCount() {
+        return controlCount;
+    }
+
+    public void setControlCount(double controlCount) {
+        this.controlCount = controlCount;
+    }
+
+    public double[] getDiscordantAlleleCounts() {
+        return discordantAlleleCounts;
+    }
+
+    public void setDiscordantAlleleCounts(int[] discordantAlleleCounts) {
+        if(discordantAlleleCounts.length != 9 ) {
+            throw new IllegalArgumentException();
+        }
+
+        this.discordantAlleleCounts = new double[9];
+        for(int i=0;i<9;i++) {
+            this.discordantAlleleCounts[i] = discordantAlleleCounts[i];
+        }
+    }
+
+    public void setDiscordantAlleleCounts(double[] d) {
+        if(d.length != 9) {
+            throw new IllegalArgumentException();
+        }
+
+        this.discordantAlleleCounts = d;
+    }
+
+    public String toString(){
+        StringBuffer curHap = new StringBuffer(genotypes.length);
+        for(int k=0;k<genotypes.length;k++) {
+            curHap.append(alleleCodes[genotypes[k]]);
+        }
+
+        return curHap.toString();
+    }
+
+    public String toNumericString() {
+        StringBuffer curHap = new StringBuffer(genotypes.length);
+        for(int k=0;k<genotypes.length;k++) {
+            curHap.append(genotypes[k]);
+        }
+
+        return curHap.toString();
+    }
+
+    public EM getEM() {
+        return blockEM;
+    }
+
+    public void setEM(EM em) {
+        blockEM = em;
+    }
+}
diff --git a/edu/mit/wi/haploview/HaplotypeDisplay.java b/edu/mit/wi/haploview/HaplotypeDisplay.java
new file mode 100755
index 0000000..3dfa42e
--- /dev/null
+++ b/edu/mit/wi/haploview/HaplotypeDisplay.java
@@ -0,0 +1,491 @@
+package edu.mit.wi.haploview;
+import org.apache.batik.svggen.SVGGraphics2D;
+import org.apache.batik.dom.GenericDOMImplementation;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+
+import java.awt.*;
+//import java.awt.geom.*;
+import java.awt.image.*;
+import java.io.*;
+import javax.swing.*;
+import java.text.*;
+import java.util.*;
+
+public class HaplotypeDisplay extends JComponent {
+    static final long serialVersionUID = 4418481582878322690L;
+
+    Haplotype[][] orderedHaplos;
+    Haplotype[][] filteredHaplos;
+    HaploData theData;
+
+    double multidprimeArray[];
+    int missingLimit = 5;
+    boolean useThickness = true;
+    double thinThresh = 0.01;
+    double thickThresh = 0.1;
+    private boolean forExport = false;
+    public int alleleDisp;
+    private Color dullRed = new Color(204,51,51);
+    private Color dullBlue = new Color(51,51,204);
+    private final Color BG_GREY = new Color(212,208,200);
+
+
+    public HaplotypeDisplay(HaploData h) throws HaploViewException {
+
+        theData=h;
+        if (blackNumImages == null) loadFontImages();
+        getHaps();
+
+        //    int[][] bk = parent.blockController.blocks;
+        //Vector blockVector = new Vector();
+        //for (int i = 0; i < bk.length; i++) {
+        //  blockVector.add(bk[i]);
+        // }
+    }
+
+    public BufferedImage export(){
+        forExport = true;
+
+        Dimension size = getPreferredSize();
+        BufferedImage i = new BufferedImage(size.width, size.height,
+                            BufferedImage.TYPE_3BYTE_BGR);
+        paintComponent(i.getGraphics());
+        forExport = false;
+        return i;
+    }
+
+    public SVGGraphics2D exportSVG(){
+        forExport = true;
+        DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
+        Document document = domImpl.createDocument(null, "svg", null);
+        SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
+        svgGenerator.getGeneratorContext().setPrecision(6); //TODO: Look at changing precision
+        paintComponent(svgGenerator);
+        forExport = false;
+        return svgGenerator;
+    }
+
+    public void getHaps() throws HaploViewException{
+        if (theData.blocks == null) {return;}
+
+        orderedHaplos = theData.generateBlockHaplotypes(theData.blocks);
+
+        adjustDisplay();
+    }
+
+
+    static Image charImages[];
+    static Image blackNumImages[];
+    static Image grayNumImages[];
+    static Image markerNumImages[];
+
+    static final int ROW_HEIGHT = 20;
+    static final int CHAR_WIDTH = 10;
+    static final int CHAR_HEIGHT = 16;
+    static final int MARKER_CHAR_WIDTH = 6;
+    static final int MARKER_CHAR_HEIGHT = 9;
+
+    // amount of room for lines between things
+    final int LINE_LEFT = 3;
+    final int LINE_RIGHT = 34;
+    final int LINE_SPAN = 37;
+
+    // border around entire image
+    static final int BORDER = 10;
+
+    // tag diamond
+    static Image tagImage; // li'l pointy arrow
+    static final int TAG_SPAN = 10;
+
+    // load fake font as images, since java font stuff is so lousy
+    protected void loadFontImages() {
+        // read images from file
+        Toolkit tk = Toolkit.getDefaultToolkit();
+        byte data[] = null;
+
+        try {
+            InputStream is = getClass().getResourceAsStream("orator.raw");
+            BufferedInputStream bis = new BufferedInputStream(is);
+            ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+            int c = bis.read();
+            while (c != -1) {
+                out.write(c);
+                c = bis.read();
+            }
+            //return out.toByteArray();
+            data = out.toByteArray();
+
+        } catch (IOException e) {
+            HaploText.commandLogger.error("error loading font data from orator.raw");
+            e.printStackTrace();
+            System.exit(1);
+        }
+
+        //byte data[] = loadBytes("orator1675.raw");
+        int pixels[] = new int[data.length];
+        for (int i = 0; i < data.length; i++) {
+            // image values are the alpha of the image
+            // image itself would be black
+            pixels[i] = ((255 - (data[i] & 0xff)) << 24) | 0x000000;
+        }
+        MemoryImageSource mis;
+
+        // top data is 110x48 image
+        // lines are 16 pixels high, each char 10 pixels wide
+        // the fifth 'char' is actually the little down triangle for tags
+        // the eighth char is the 'unknown', which is an 8 in the data struct
+        // (the acgt letters are 1234)
+        // 6 and 7 are blank, but who cares, since it makes the code simpler
+        // ACGT^  x
+        // 012345679. (black)
+        // 012345679. (gray)
+
+        charImages = new Image[8];
+        for (int i = 0; i < 8; i++) {
+            mis = new MemoryImageSource(10, 16, pixels, i*CHAR_WIDTH, 110);
+            charImages[i] = tk.createImage(mis);
+        }
+        tagImage = charImages[4];
+
+        blackNumImages = new Image[11];
+        grayNumImages = new Image[11];
+        for (int i = 0; i < 11; i++) {
+            mis = new MemoryImageSource(CHAR_WIDTH, CHAR_HEIGHT, pixels,
+                    110*CHAR_HEIGHT + i*CHAR_WIDTH, 110);
+            blackNumImages[i] = tk.createImage(mis);
+
+            mis = new MemoryImageSource(CHAR_WIDTH, CHAR_HEIGHT, pixels,
+                    110*CHAR_HEIGHT*2 + i*CHAR_WIDTH, 110);
+            grayNumImages[i] = tk.createImage(mis);
+        }
+
+        // tag nums are rotated in the file (as they are used in the vis)
+        // so they start at 9 and go down to 0
+        markerNumImages = new Image[10];
+        int offset = 110 * CHAR_HEIGHT * 3;
+        for (int i = 9; i >= 0; --i) {
+            mis = new MemoryImageSource(MARKER_CHAR_HEIGHT,
+                    MARKER_CHAR_WIDTH, pixels,
+                    offset + MARKER_CHAR_WIDTH*110*(9-i), 110);
+            markerNumImages[i] = tk.createImage(mis);
+        }
+    }
+
+    public void adjustDisplay(){
+        //this is called when the controller wants to change the haps
+        //displayed, instead of directly repainting so that none of this math
+        //is done when the screen repaints for other reasons (resizing, focus change, etc)
+
+
+        if (orderedHaplos == null){
+            filteredHaplos = null;
+            return;
+        }
+        //first filter haps on displaythresh
+        Haplotype[][] filts;
+        filts = new Haplotype[orderedHaplos.length][];
+        int numhaps = 0;
+        int printable = 0;
+        for (int i = 0; i < orderedHaplos.length; i++){
+                        Vector tempVector = new Vector();
+            for (int j = 0; j < orderedHaplos[i].length; j++){
+                if (orderedHaplos[i][j].getPercentage() > Options.getHaplotypeDisplayThreshold()){
+                    tempVector.add(orderedHaplos[i][j]);
+                    numhaps++;
+                }
+            }
+            if (numhaps > 0){
+                printable++; numhaps=0;
+            }
+            filts[i] = new Haplotype[tempVector.size()];
+            tempVector.copyInto(filts[i]);
+        }
+
+        // if user sets display thresh higher than most common hap in any given block
+        if (!(printable == filts.length)){
+            JOptionPane.showMessageDialog(this.getParent(),
+                    "Error: At least one block has no haplotypes of frequency > " + Util.roundDouble(Options.getHaplotypeDisplayThreshold(),3),
+                    "Error",
+                    JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+
+        filteredHaplos = filts;
+
+        //then re-tag, sort and get MA D'
+        filteredHaplos = theData.orderByCrossing(filteredHaplos);
+        theData.pickTags(filteredHaplos);
+        multidprimeArray = theData.computeMultiDprime(filteredHaplos);
+
+        //if the haps pane exists, we want to make sure the vert scroll bar appears if necessary
+        if (this.getParent() != null){
+            if (this.getPreferredSize().height > this.getParent().getHeight()){
+                    ((JScrollPane)this.getParent().getParent()).setVerticalScrollBarPolicy(
+                            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS );
+            }else{
+               ((JScrollPane)this.getParent().getParent()).setVerticalScrollBarPolicy(
+                            JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+            }
+        }
+        repaint();
+    }
+
+    public Dimension getPreferredSize() {
+        int wide = BORDER*2;
+        int high = BORDER*2;
+        if (filteredHaplos == null) {return new Dimension(wide, high);}
+
+        // height for the marker digits
+        int markerDigits =
+                (int) (0.0000001 + Math.log(Chromosome.getUnfilteredSize()) / Math.log(10)) + 1;
+        high += MARKER_CHAR_WIDTH * markerDigits;
+
+        // space for the diamond
+        high += TAG_SPAN;
+
+        int maxh = 0;
+        for (int i = 0; i < filteredHaplos.length; i++){
+            maxh = Math.max(filteredHaplos[i].length, maxh);
+            // size of genotypes for this column
+            // +4 for the percentage reading
+            wide += (filteredHaplos[i][0].getGeno().length + 4) * CHAR_WIDTH;
+            // percentage plus the line size
+            if (i != 0) wide += LINE_SPAN;
+        }
+        // +1 because of extra row for multi between the columns
+        high += (maxh + 1) * ROW_HEIGHT;
+
+        return new Dimension(wide, high);
+    }
+
+    public void paintComponent(Graphics graphics) {
+
+        if (filteredHaplos == null){
+            super.paintComponent(graphics);
+            return;
+        }
+
+        Graphics2D g = (Graphics2D) graphics;
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                RenderingHints.VALUE_ANTIALIAS_ON);
+
+        Dimension size = getSize();
+        Dimension pref = getPreferredSize();
+        g.setColor(BG_GREY);
+        g.fillRect(0,0,size.width, size.height);
+
+
+        if (!forExport){
+            g.translate((size.width - pref.width) / 2,
+                    (size.height - pref.height) / 2);
+        }
+
+        //g.drawRect(0, 0, pref.width, pref.height);
+
+        final BasicStroke thinStroke = new BasicStroke(0.5f);
+        final BasicStroke thickStroke = new BasicStroke(2.0f);
+
+        // width of one letter of the haplotype block
+        //int letterWidth = haploMetrics.charWidth('G');
+        //int percentWidth = pctMetrics.stringWidth(".000");
+
+        //final int verticalOffset = 43; // room for tags and diamonds
+        int left = BORDER;
+        int top = BORDER; //verticalOffset;
+        //int totalWidth = 0;
+
+        // percentages for each haplotype
+        NumberFormat nf = NumberFormat.getInstance(Locale.US);
+        nf.setMinimumFractionDigits(3);
+        nf.setMaximumFractionDigits(3);
+        nf.setMinimumIntegerDigits(0);
+        nf.setMaximumIntegerDigits(0);
+
+        // multi reading, between the columns
+        NumberFormat nfMulti = NumberFormat.getInstance(Locale.US);
+        nfMulti.setMinimumFractionDigits(2);
+        nfMulti.setMaximumFractionDigits(2);
+        nfMulti.setMinimumIntegerDigits(0);
+        nfMulti.setMaximumIntegerDigits(0);
+
+        int[][] lookupPos = new int[filteredHaplos.length][];
+        for (int p = 0; p < lookupPos.length; p++) {
+            lookupPos[p] = new int[filteredHaplos[p].length];
+            for (int q = 0; q < lookupPos[p].length; q++){
+                lookupPos[p][filteredHaplos[p][q].getListOrder()] = q;
+            }
+        }
+
+        // set number formatter to pad with appropriate number of zeroes
+        NumberFormat nfMarker = NumberFormat.getInstance(Locale.US);
+        nfMarker.setGroupingUsed(false);
+        int markerCount = Chromosome.getUnfilteredSize();
+        // the +0.0000001 is because there is
+        // some suckage where log(1000) / log(10) isn't actually 3
+        int markerDigits =
+                (int) (0.0000001 + Math.log(markerCount) / Math.log(10)) + 1;
+        nfMarker.setMinimumIntegerDigits(markerDigits);
+        nfMarker.setMaximumIntegerDigits(markerDigits);
+
+        //int tagShapeX[] = new int[3];
+        //int tagShapeY[] = new int[3];
+        //Polygon tagShape;
+        int textRight = 0; // gets updated for scooting over
+
+        // i = 0 to number of columns - 1
+        for (int i = 0; i < filteredHaplos.length; i++) {
+            int[] markerNums = filteredHaplos[i][0].getMarkers();
+            boolean[] tags = filteredHaplos[i][0].getTags();
+            //int headerX = x;
+
+            //block labels
+            g.setColor(Color.black);
+            g.drawString("Block " + (i+1),
+                    left,
+                    top - CHAR_HEIGHT);
+
+            for (int z = 0; z < markerNums.length; z++) {
+                //int tagMiddle = tagMetrics.getAscent() / 2;
+                //int tagLeft = x + z*letterWidth + tagMiddle;
+                //g.translate(tagLeft, 20);
+
+                // if tag snp, draw little triangle pooper
+                if (tags[z] && Options.isShowBlockTags()) {
+                    g.drawImage(tagImage, left + z*CHAR_WIDTH,
+                            top + markerDigits*MARKER_CHAR_WIDTH
+                            -(CHAR_HEIGHT - TAG_SPAN), null);
+                }
+
+                //g.rotate(-Math.PI / 2.0);
+                //g.drawLine(0, 0, 0, 0);
+
+                //g.setColor(Color.black);
+                //g.drawString(nfMarker.format(markerNums[z]), 0, tagMiddle);
+                char markerChars[] = nfMarker.format(Chromosome.realIndex[markerNums[z]]+1).toCharArray();
+                for (int m = 0; m < markerDigits; m++) {
+                    g.drawImage(markerNumImages[markerChars[m] - '0'],
+                            left + z*CHAR_WIDTH +
+                            (1 + CHAR_WIDTH - MARKER_CHAR_HEIGHT)/2,
+                            top + (markerDigits-m-1)*MARKER_CHAR_WIDTH, null);
+                }
+
+                // undo the transform.. no push/pop.. arrgh
+                //g.rotate(Math.PI / 2.0);
+                //g.translate(-tagLeft, -20);
+            }
+
+            // y position of the first image for the haplotype letter
+            // top + the size of the marker digits + the size of the tag +
+            // the character height centered in the row's height
+            int above = top + markerDigits*MARKER_CHAR_WIDTH + TAG_SPAN +
+                    (ROW_HEIGHT - CHAR_HEIGHT) / 2;
+
+            //figure out which allele is the major allele for each marker
+            int[] majorAllele = new int[filteredHaplos[i][0].getGeno().length];
+            for (int k = 0; k < majorAllele.length; k++){
+                majorAllele[k] = Chromosome.getMarker(markerNums[k]).getMajor();
+            }
+
+            for (int j = 0; j < filteredHaplos[i].length; j++){
+                int curHapNum = lookupPos[i][j];
+                //String theHap = new String();
+                //String thePercentage = new String();
+                int[] theGeno = filteredHaplos[i][curHapNum].getGeno(); //getGeno();
+
+                // j is the row of haplotype
+                for (int k = 0; k < theGeno.length; k++) {
+                    // theGeno[k] will be 1,2,3,4 (acgt) or 8 (for bad)
+                    if (alleleDisp == 0){
+                        g.drawImage(charImages[theGeno[k] - 1],
+                                left + k*CHAR_WIDTH, above + j*ROW_HEIGHT, null);
+                    }else if (alleleDisp == 1){
+                        g.drawImage(blackNumImages[theGeno[k]],
+                                left + k*CHAR_WIDTH, above + j*ROW_HEIGHT, null);
+                    }else{
+                        if (theGeno[k] == majorAllele[k]){
+                            g.setColor(dullBlue);
+                        }else{
+                            g.setColor(dullRed);
+                        }
+                        g.fillRect(left + k*CHAR_WIDTH,
+                                above + j*ROW_HEIGHT + (ROW_HEIGHT - CHAR_WIDTH)/2,
+                                CHAR_WIDTH, CHAR_WIDTH);
+                    }
+                }
+
+                //draw the percentage value in non mono font
+                double percent = filteredHaplos[i][curHapNum].getPercentage();
+                //thePercentage = " " + nf.format(percent);
+                char percentChars[] = nf.format(percent).toCharArray();
+
+                // perhaps need an exceptional case for 1.0 being the percent
+                for (int m = 0; m < percentChars.length; m++) {
+                    g.drawImage(grayNumImages[(m == 0) ? 10 : percentChars[m]-'0'],
+                            left + theGeno.length*CHAR_WIDTH + m*CHAR_WIDTH,
+                            above + j*ROW_HEIGHT, null);
+                }
+
+                // 4 is the number of chars in .999 for the percent
+                textRight = left + theGeno.length*CHAR_WIDTH + 4*CHAR_WIDTH;
+
+
+                g.setColor(Color.black);
+                if (i < filteredHaplos.length - 1) {  //draw crossovers
+                    for (int crossCount = 0;
+                         crossCount < filteredHaplos[i+1].length;
+                         crossCount++) {
+                        double crossValue =
+                                filteredHaplos[i][curHapNum].getCrossover(crossCount);
+
+                        //draw thin and thick lines
+                        if (crossValue > thinThresh) {
+                            g.setStroke(crossValue > thickThresh ? thickStroke : thinStroke);
+                            int connectTo = filteredHaplos[i+1][crossCount].getListOrder();
+                            g.drawLine(textRight + LINE_LEFT,
+                                    above + j*ROW_HEIGHT + ROW_HEIGHT/2,
+                                    textRight + LINE_RIGHT,
+                                    above + connectTo*ROW_HEIGHT + ROW_HEIGHT/2);
+                        }
+                    }
+                }
+            }
+            left = textRight;
+
+            // add the multilocus d prime if appropriate
+            if (i < filteredHaplos.length - 1) {
+
+                //put the numbers in the right place vertically
+                int depth;
+                if (filteredHaplos[i].length > filteredHaplos[i+1].length){
+                    depth = filteredHaplos[i].length;
+                }else{
+                    depth = filteredHaplos[i+1].length;
+                }
+
+                char multiChars[] =
+                        nfMulti.format(multidprimeArray[i]).toCharArray();
+                if (multidprimeArray[i] > 0.99){
+                    //draw 1.0 vals specially
+                    g.drawImage(blackNumImages[1],
+                            left + (LINE_SPAN - 7*CHAR_WIDTH/2)/2 + CHAR_WIDTH,
+                            above + (depth * ROW_HEIGHT), null);
+                    g.drawImage(blackNumImages[10],
+                            left + (LINE_SPAN - 9*CHAR_WIDTH/2)/2 + 2*CHAR_WIDTH,
+                            above + (depth * ROW_HEIGHT), null);
+                    g.drawImage(blackNumImages[0],
+                            left + (LINE_SPAN - 9*CHAR_WIDTH/2)/2 + 3*CHAR_WIDTH,
+                            above + (depth * ROW_HEIGHT), null);
+                }else{
+                    for (int m = 0; m < 3; m++) {
+                        g.drawImage(blackNumImages[(m == 0) ? 10 : multiChars[m]-'0'],
+                                left + (LINE_SPAN - 7*CHAR_WIDTH/2)/2 + m*CHAR_WIDTH,
+                                above + (depth * ROW_HEIGHT), null);
+                    }
+                }
+            }
+            left += LINE_SPAN;
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/HaplotypeDisplayController.java b/edu/mit/wi/haploview/HaplotypeDisplayController.java
new file mode 100755
index 0000000..cc48c33
--- /dev/null
+++ b/edu/mit/wi/haploview/HaplotypeDisplayController.java
@@ -0,0 +1,123 @@
+package edu.mit.wi.haploview;
+
+import java.awt.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import javax.swing.*;
+
+
+public class HaplotypeDisplayController extends JPanel {
+    static final long serialVersionUID = -9053016986208269037L;
+    // amount of spacing between elements
+    static final int BETWEEN = 4;
+
+    NumberTextField minDisplayField;
+    NumberTextField minThickField;
+    NumberTextField minThinField;
+    ButtonGroup alleleDisplayGroup;
+    JButton goButton;
+    Dimension fieldSize;
+
+    HaplotypeDisplay parent;
+
+    public HaplotypeDisplayController(HaplotypeDisplay parent){
+        this.parent = parent;
+
+        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+
+        JPanel leftPanel = new JPanel();
+        leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS));
+        JPanel rightPanel = new JPanel();
+        rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));
+
+        JPanel hapPercentPanel = new JPanel();
+        hapPercentPanel.add(new JLabel("Examine haplotypes above "));
+            hapPercentPanel.add(minDisplayField =
+                    new NumberTextField(String.valueOf(Options.getHaplotypeDisplayThreshold()*100), 5, true, false));
+
+        hapPercentPanel.add(new JLabel("%"));
+        leftPanel.add(hapPercentPanel);
+
+        JPanel thinPanel = new JPanel();
+        thinPanel.add(new JLabel("Connect with thin lines if > "));
+        thinPanel.add(minThinField =
+                new NumberTextField(String.valueOf(parent.thinThresh*100), 5, true, false));
+        thinPanel.add(new JLabel("%"));
+        leftPanel.add(thinPanel);
+
+        JPanel thickPanel = new JPanel();
+        thickPanel.add(new JLabel("Connect with thick lines if > "));
+        thickPanel.add(minThickField =
+                new NumberTextField(String.valueOf(parent.thickThresh*100), 5, true, false));
+        thickPanel.add(new JLabel("%"));
+        leftPanel.add(thickPanel);
+
+        JLabel dispLab = new JLabel("Display alleles as:");
+        rightPanel.add(dispLab);
+
+        JRadioButton letBut = new JRadioButton("letters");
+        letBut.setActionCommand("0");
+        letBut.setSelected(true);
+        rightPanel.add(letBut);
+        JRadioButton numBut = new JRadioButton("numbers");
+        numBut.setActionCommand("1");
+        rightPanel.add(numBut);
+        JRadioButton sqBut = new JRadioButton("colored squares");
+        sqBut.setActionCommand("2");
+        rightPanel.add(sqBut);
+        alleleDisplayGroup = new ButtonGroup();
+        alleleDisplayGroup.add(letBut);
+        alleleDisplayGroup.add(numBut);
+        alleleDisplayGroup.add(sqBut);
+
+        JPanel optionPanel = new JPanel();
+        optionPanel.add(leftPanel);
+        optionPanel.add(rightPanel);
+        add(optionPanel);
+
+        goButton = new JButton("Go");
+        goButton.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                setDisplayThresh(Double.parseDouble(minDisplayField.getText()));
+                setThinThresh(Double.parseDouble(minThinField.getText()));
+                setThickThresh(Double.parseDouble(minThickField.getText()));
+                setNumericAlls(alleleDisplayGroup.getSelection().getActionCommand());
+                paintIt();
+            }
+        });
+        add(goButton);
+
+        fieldSize = minDisplayField.getPreferredSize();
+    }
+
+    private void setNumericAlls(String selection) {
+        parent.alleleDisp = Integer.parseInt(selection);
+    }
+
+    public void setDisplayThresh(double amount){
+        if (Options.getHaplotypeDisplayThreshold() != amount){
+            Options.setHaplotypeDisplayThreshold(amount/100);
+            parent.adjustDisplay();
+        }
+    }
+
+    public void setThinThresh(double amount) {
+        parent.thinThresh = amount/100;
+    }
+
+    public void setThickThresh(double amount) {
+        parent.thickThresh = amount/100;
+    }
+
+    public void paintIt(){
+        parent.repaint();
+    }
+
+    public Dimension getMaximumSize() {
+        return new Dimension(super.getPreferredSize().width,
+                fieldSize.height*3 + BETWEEN*2);
+    }
+
+
+
+}
diff --git a/edu/mit/wi/haploview/HaploviewTab.java b/edu/mit/wi/haploview/HaploviewTab.java
new file mode 100755
index 0000000..fdf1b27
--- /dev/null
+++ b/edu/mit/wi/haploview/HaploviewTab.java
@@ -0,0 +1,24 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class HaploviewTab extends JPanel{
+    static final long serialVersionUID = -5375703117173625581L;
+
+    private Component primary;
+
+    public HaploviewTab(){
+        primary = this;
+    }
+
+    public HaploviewTab(Component primary){
+        this.primary = primary;
+        this.setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
+    }
+
+    public Component getPrimary() {
+        return primary;
+    }
+
+}
\ No newline at end of file
diff --git a/edu/mit/wi/haploview/HetsDialog.java b/edu/mit/wi/haploview/HetsDialog.java
new file mode 100755
index 0000000..aa12968
--- /dev/null
+++ b/edu/mit/wi/haploview/HetsDialog.java
@@ -0,0 +1,166 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.*;
+import java.util.Vector;
+import java.util.StringTokenizer;
+import java.io.File;
+import java.io.IOException;
+import java.io.FileWriter;
+
+
+/**
+ * Custom Dialog showing Male Heterozygotes
+ *
+ * this class is not thread safe (untested).
+ * modified version of MendelDialog
+ * @author David Bender
+ */
+
+
+
+
+public class HetsDialog extends JDialog implements ActionListener, Constants {
+    static final long serialVersionUID = 2540864803651257131L;
+    private BasicTableModel tableModel;
+
+
+    public HetsDialog (HaploView h, String title) {
+        super(h,title);
+
+        JPanel contents = new JPanel();
+        JTable table;
+
+        contents.setLayout(new BoxLayout(contents,BoxLayout.Y_AXIS));
+
+        Vector results = h.theData.getPedFile().getHaploidHets();
+
+        Vector colNames = new Vector();
+        colNames.add("FamilyID");
+        colNames.add("IndividualID");
+        colNames.add("Marker");
+        Vector data = new Vector();
+
+        for(int i=0;i<results.size();i++) {
+            StringTokenizer st = new StringTokenizer((String)results.get(i));
+            Vector tmpVec = new Vector();
+            tmpVec.add(st.nextToken());
+            tmpVec.add(st.nextToken());
+            tmpVec.add(Chromosome.getUnfilteredMarker(Integer.parseInt(st.nextToken())).getDisplayName());
+            data.add(tmpVec);
+
+        }
+
+        tableModel = new BasicTableModel(colNames,data);
+        TableSorter sorter = new TableSorter(tableModel);
+        table = new JTable(sorter);
+        sorter.setTableHeader(table.getTableHeader());
+        table.getColumnModel().getColumn(2).setPreferredWidth(30);
+
+        JScrollPane tableScroller = new JScrollPane(table);
+        int tableHeight = (table.getRowHeight()+table.getRowMargin())*(table.getRowCount()+2);
+        if (tableHeight > 300){
+            tableScroller.setPreferredSize(new Dimension(400, 300));
+        }else{
+            tableScroller.setPreferredSize(new Dimension(400, tableHeight));
+        }
+        tableScroller.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
+
+        contents.add(tableScroller);
+
+        JPanel buttonPanel = new JPanel();
+        JButton exportButton = new JButton("Export to File");
+        exportButton.addActionListener(this);
+        JButton okButton = new JButton("Close");
+        okButton.addActionListener(this);
+        buttonPanel.add(exportButton);
+        buttonPanel.add(okButton);
+        contents.add(buttonPanel);
+        setContentPane(contents);
+
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+    }
+
+    public HetsDialog(HaploData hd){
+        Vector results = hd.getPedFile().getHaploidHets();
+
+        Vector colNames = new Vector();
+        colNames.add("FamilyID");
+        colNames.add("IndividualID");
+        colNames.add("Marker");
+        Vector data = new Vector();
+
+        for(int i=0;i<results.size();i++) {
+            StringTokenizer st = new StringTokenizer((String)results.get(i));
+            Vector tmpVec = new Vector();
+            tmpVec.add(st.nextToken());
+            tmpVec.add(st.nextToken());
+            tmpVec.add(Chromosome.getUnfilteredMarker(Integer.parseInt(st.nextToken())).getDisplayName());
+            data.add(tmpVec);
+        }
+
+        tableModel = new BasicTableModel(colNames, data);
+    }
+
+    public void printTable(File outfile) throws IOException {
+        FileWriter checkWriter = null;
+        if (outfile != null){
+            checkWriter = new FileWriter(outfile);
+        }
+
+        int numCols = tableModel.getColumnCount();
+        StringBuffer header = new StringBuffer();
+        for (int i = 0; i < numCols; i++){
+            header.append(tableModel.getColumnName(i)).append("\t");
+        }
+        header.append("\n");
+
+        if (outfile != null){
+            checkWriter.write(header.toString());
+        }else{
+            System.out.print(header.toString());
+        }
+        for (int i = 0; i < tableModel.getRowCount(); i++){
+            StringBuffer sb = new StringBuffer();
+            for (int j = 0; j < numCols; j++){
+                sb.append(tableModel.getValueAt(i,j)).append("\t");
+            }
+            sb.append("\n");
+
+            if (outfile != null){
+                checkWriter.write(sb.toString());
+            }else{
+                System.out.print(sb.toString());
+            }
+        }
+        if (outfile != null){
+            checkWriter.close();
+        }
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if(command.equals("Close")) {
+            this.dispose();
+        }else if (command.equals("Export to File")){
+            HaploView.fc.setSelectedFile(new File(""));
+
+            if (HaploView.fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION){
+                File file = HaploView.fc.getSelectedFile();
+                try{
+                    printTable(file);
+                }catch(IOException ioe){
+                    JOptionPane.showMessageDialog(this,
+                            ioe.getMessage(),
+                            "Error",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+            }
+        }
+    }
+}
+
diff --git a/edu/mit/wi/haploview/IndividualDialog.java b/edu/mit/wi/haploview/IndividualDialog.java
new file mode 100755
index 0000000..e7dd60f
--- /dev/null
+++ b/edu/mit/wi/haploview/IndividualDialog.java
@@ -0,0 +1,228 @@
+package edu.mit.wi.haploview;
+
+import edu.mit.wi.pedfile.Individual;
+import javax.swing.*;
+import javax.swing.table.DefaultTableCellRenderer;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.*;
+import java.util.Vector;
+import java.util.Locale;
+import java.io.File;
+import java.io.IOException;
+import java.io.FileWriter;
+import java.text.NumberFormat;
+
+
+/**
+ * Custom Dialog showing Geno% by Individual ID
+ *
+ * this class is not thread safe (untested).
+ * modified version of FilteredIndividualsDialog
+ * @author David Bender
+ */
+
+
+
+
+public class IndividualDialog extends JDialog implements ActionListener, Constants {
+    static final long serialVersionUID = -2204440564566814820L;
+    private BasicTableModel tableModel;
+    private static NumberFormat nf = NumberFormat.getInstance(Locale.US);
+    static {
+        nf.setMinimumFractionDigits(0);
+        nf.setMaximumFractionDigits(1);
+    }
+
+    public IndividualDialog (HaploView h, String title) {
+        super(h,title);
+
+        JPanel contents = new JPanel();
+        JTable table;
+
+        contents.setLayout(new BoxLayout(contents,BoxLayout.Y_AXIS));
+
+        Vector people = h.theData.getPedFile().getAllIndividuals();
+        Vector excludedPeople = h.theData.getPedFile().getAxedPeople();
+
+        Vector colNames = new Vector();
+        colNames.add("FamilyID");
+        colNames.add("IndividualID");
+        colNames.add("Geno%");
+        Vector data = new Vector();
+
+        for (int i=0; i<excludedPeople.size(); i++){
+            Vector tmpVecB = new Vector();
+            Individual axedInd = (Individual)excludedPeople.get(i);
+            tmpVecB.add(axedInd.getFamilyID());
+            tmpVecB.add(axedInd.getIndividualID());
+            tmpVecB.add(new Double(nf.format(axedInd.getGenoPC()*100)));
+            data.add(tmpVecB);
+        }
+
+        for(int i=0;i<people.size();i++) {
+            Vector tmpVec = new Vector();
+            Individual currentInd = (Individual)people.get(i);
+            tmpVec.add(currentInd.getFamilyID());
+            tmpVec.add(currentInd.getIndividualID());
+            tmpVec.add(new Double(nf.format(currentInd.getGenoPC()*100)));
+            data.add(tmpVec);
+        }
+
+        tableModel = new BasicTableModel(colNames,data);
+        TableSorter sorter = new TableSorter(tableModel);
+        table = new JTable(sorter);
+        sorter.setTableHeader(table.getTableHeader());
+
+        IndividualCellRenderer renderer = new IndividualCellRenderer();
+        try{
+            table.setDefaultRenderer(Class.forName("java.lang.Double"), renderer);
+            table.setDefaultRenderer(Class.forName("java.lang.Integer"), renderer);
+            table.setDefaultRenderer(Class.forName("java.lang.Long"), renderer);
+            table.setDefaultRenderer(Class.forName("java.lang.String"),renderer);
+        }catch (Exception e){
+        }
+        table.getColumnModel().getColumn(2).setPreferredWidth(30);
+
+        JScrollPane tableScroller = new JScrollPane(table);
+        int tableHeight = (table.getRowHeight()+table.getRowMargin())*(table.getRowCount()+2);
+        if (tableHeight > 300){
+            tableScroller.setPreferredSize(new Dimension(400, 300));
+        }else{
+            tableScroller.setPreferredSize(new Dimension(400, tableHeight));
+        }
+        tableScroller.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
+
+        contents.add(tableScroller);
+
+        JPanel buttonPanel = new JPanel();
+        JButton exportButton = new JButton("Export to File");
+        exportButton.addActionListener(this);
+        JButton okButton = new JButton("Close");
+        okButton.addActionListener(this);
+        buttonPanel.add(exportButton);
+        buttonPanel.add(okButton);
+        contents.add(buttonPanel);
+        setContentPane(contents);
+
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+    }
+
+    public IndividualDialog (HaploData hd){
+
+        Vector people = hd.getPedFile().getAllIndividuals();
+        Vector colNames = new Vector();
+        colNames.add("FamilyID");
+        colNames.add("IndividualID");
+        colNames.add("Geno%");
+        Vector data = new Vector();
+
+        for(int i=0;i<people.size();i++) {
+            Vector tmpVec = new Vector();
+            Individual currentInd = (Individual)people.get(i);
+            tmpVec.add(currentInd.getFamilyID());
+            tmpVec.add(currentInd.getIndividualID());
+            tmpVec.add(new Double(nf.format(currentInd.getGenoPC()*100)));
+            data.add(tmpVec);
+        }
+
+        tableModel = new BasicTableModel(colNames, data);
+    }
+
+    public void printTable(File outfile) throws IOException {
+        FileWriter checkWriter = null;
+        Double dValue = new Double(0);
+        double dVal;
+        double minGeno = (1 - Options.getMissingThreshold())*100;
+        if (outfile != null){
+            checkWriter = new FileWriter(outfile);
+        }
+
+        int numCols = tableModel.getColumnCount();
+        StringBuffer header = new StringBuffer();
+        for (int i = 0; i < numCols; i++){
+            header.append(tableModel.getColumnName(i)).append("\t");
+        }
+        header.append("\n");
+
+        if (outfile != null){
+            checkWriter.write(header.toString());
+        }else{
+            System.out.print(header.toString());
+        }
+        for (int i = 0; i < tableModel.getRowCount(); i++){
+            StringBuffer sb = new StringBuffer();
+            for (int j = 0; j < numCols; j++){
+                sb.append(tableModel.getValueAt(i,j)).append("\t");
+                if (j == 2){
+                    dValue = new Double(tableModel.getValueAt(i,j).toString());
+                    dVal = dValue.doubleValue();
+                    if (dVal < minGeno){
+                        sb.append("BAD");
+                    }
+                }
+            }
+            sb.append("\n");
+
+            if (outfile != null){
+                checkWriter.write(sb.toString());
+            }else{
+                System.out.print(sb.toString());
+            }
+        }
+        if (outfile != null){
+            checkWriter.close();
+        }
+    }
+
+    class IndividualCellRenderer extends DefaultTableCellRenderer {
+        static final long serialVersionUID = -7421465728691975408L;
+        public Component getTableCellRendererComponent
+                (JTable table, Object value, boolean isSelected,
+                 boolean hasFocus, int row, int column)
+        {
+            Component cell = super.getTableCellRendererComponent
+                    (table, value, isSelected, hasFocus, row, column);
+            boolean threshold = false;
+            String thisColumnName = table.getColumnName(column);
+            if (thisColumnName.equals("Geno%")){
+                Double dval = new Double(value.toString());
+                if (dval.doubleValue() < ((1 - Options.getMissingThreshold())*100)){
+                    threshold = true;
+                }
+            }
+            cell.setBackground(Color.white);
+            cell.setForeground(Color.black);
+
+            if (thisColumnName.equals("Geno%") && threshold){
+                cell.setForeground(Color.red);
+                threshold = false;
+            }
+
+            return cell;
+        }
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if(command.equals("Close")) {
+            this.dispose();
+        }else if (command.equals("Export to File")){
+            HaploView.fc.setSelectedFile(new File(""));
+
+            if (HaploView.fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION){
+                File file = HaploView.fc.getSelectedFile();
+                try{
+                    printTable(file);
+                }catch(IOException ioe){
+                    JOptionPane.showMessageDialog(this,
+                            ioe.getMessage(),
+                            "Error",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+            }
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/MendelDialog.java b/edu/mit/wi/haploview/MendelDialog.java
new file mode 100755
index 0000000..59a294c
--- /dev/null
+++ b/edu/mit/wi/haploview/MendelDialog.java
@@ -0,0 +1,183 @@
+package edu.mit.wi.haploview;
+
+import edu.mit.wi.pedfile.MarkerResult;
+import edu.mit.wi.pedfile.MendelError;
+
+import javax.swing.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.*;
+import java.util.Vector;
+import java.io.File;
+import java.io.IOException;
+import java.io.FileWriter;
+
+
+/**
+ * Custom Dialog showing Mendel Errors
+ *
+ * this class is not thread safe (untested).
+ * modified version of IndividualsDialog
+ * @author David Bender
+ */
+
+
+
+
+public class MendelDialog extends JDialog implements ActionListener, Constants {
+    static final long serialVersionUID = -100432308843520115L;
+    private BasicTableModel tableModel;
+
+
+    public MendelDialog (HaploView h, String title) {
+        super(h,title);
+
+        JPanel contents = new JPanel();
+        JTable table;
+
+        contents.setLayout(new BoxLayout(contents,BoxLayout.Y_AXIS));
+
+        Vector results = h.theData.getPedFile().getResults();
+
+        Vector colNames = new Vector();
+        colNames.add("FamilyID");
+        colNames.add("ChildID");
+        colNames.add("Marker");
+        colNames.add("Position");
+        Vector data = new Vector();
+
+        for(int i=0;i<results.size();i++) {
+            MarkerResult currentResult = (MarkerResult)results.get(i);
+            if (currentResult.getMendErrNum() > 0){
+                Vector mendelErrors = currentResult.getMendelErrors();
+                for (int j = 0; j < mendelErrors.size(); j++){
+                    MendelError error = (MendelError)mendelErrors.get(j);
+                    Vector tmpVec = new Vector();
+                    tmpVec.add(error.getFamilyID());
+                    tmpVec.add(error.getChildID());
+                    tmpVec.add(Chromosome.getUnfilteredMarker(i).getDisplayName());
+                    tmpVec.add(new Long(Chromosome.getUnfilteredMarker(i).getPosition()));
+                    data.add(tmpVec);
+                }
+            }
+        }
+
+        tableModel = new BasicTableModel(colNames,data);
+        TableSorter sorter = new TableSorter(tableModel);
+        table = new JTable(sorter);
+        sorter.setTableHeader(table.getTableHeader());
+        table.getColumnModel().getColumn(2).setPreferredWidth(30);
+
+        JScrollPane tableScroller = new JScrollPane(table);
+        int tableHeight = (table.getRowHeight()+table.getRowMargin())*(table.getRowCount()+2);
+        if (tableHeight > 300){
+            tableScroller.setPreferredSize(new Dimension(400, 300));
+        }else{
+            tableScroller.setPreferredSize(new Dimension(400, tableHeight));
+        }
+        tableScroller.setBorder(BorderFactory.createEmptyBorder(2,5,2,5));
+
+        contents.add(tableScroller);
+
+        JPanel buttonPanel = new JPanel();
+        JButton exportButton = new JButton("Export to File");
+        exportButton.addActionListener(this);
+        JButton okButton = new JButton("Close");
+        okButton.addActionListener(this);
+        buttonPanel.add(exportButton);
+        buttonPanel.add(okButton);
+        contents.add(buttonPanel);
+        setContentPane(contents);
+
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+    }
+
+    public MendelDialog(HaploData hd){
+        Vector results = hd.getPedFile().getResults();
+
+        Vector colNames = new Vector();
+        colNames.add("FamilyID");
+        colNames.add("ChildID");
+        colNames.add("Marker");
+        colNames.add("Position");
+        Vector data = new Vector();
+
+        for(int i=0;i<results.size();i++) {
+            MarkerResult currentResult = (MarkerResult)results.get(i);
+            if (currentResult.getMendErrNum() > 0){
+                Vector tmpVec = new Vector();
+                Vector mendelErrors = currentResult.getMendelErrors();
+                for (int j = 0; j < mendelErrors.size(); j++){
+                    MendelError error = (MendelError)mendelErrors.get(j);
+                    tmpVec.add(error.getFamilyID());
+                    tmpVec.add(error.getChildID());
+                    tmpVec.add(Chromosome.getUnfilteredMarker(i).getDisplayName());
+                    tmpVec.add(new Long(Chromosome.getUnfilteredMarker(i).getPosition()));
+                    data.add(tmpVec);
+                }
+            }
+        }
+
+        tableModel = new BasicTableModel(colNames, data);
+    }
+
+    public void printTable(File outfile) throws IOException {
+        FileWriter checkWriter = null;
+        if (outfile != null){
+            checkWriter = new FileWriter(outfile);
+        }
+
+        int numCols = tableModel.getColumnCount();
+        StringBuffer header = new StringBuffer();
+        for (int i = 0; i < numCols; i++){
+            header.append(tableModel.getColumnName(i)).append("\t");
+        }
+        header.append("\n");
+
+        if (outfile != null){
+            checkWriter.write(header.toString());
+        }else{
+            System.out.print(header.toString());
+        }
+        for (int i = 0; i < tableModel.getRowCount(); i++){
+            StringBuffer sb = new StringBuffer();
+            for (int j = 0; j < numCols; j++){
+                sb.append(tableModel.getValueAt(i,j)).append("\t");
+            }
+            sb.append("\n");
+
+            if (outfile != null){
+                checkWriter.write(sb.toString());
+            }else{
+                System.out.print(sb.toString());
+            }
+        }
+        if (outfile != null){
+            checkWriter.close();
+        }
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if(command.equals("Close")) {
+            this.dispose();
+        }else if (command.equals("Export to File")){
+            HaploView.fc.setSelectedFile(new File(""));
+
+            if (HaploView.fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION){
+                File file = HaploView.fc.getSelectedFile();
+                try{
+                    printTable(file);
+                }catch(IOException ioe){
+                    JOptionPane.showMessageDialog(this,
+                            ioe.getMessage(),
+                            "Error",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+            }
+        }
+    }
+}
+
diff --git a/edu/mit/wi/haploview/NumberTextField.java b/edu/mit/wi/haploview/NumberTextField.java
new file mode 100755
index 0000000..1fc84ed
--- /dev/null
+++ b/edu/mit/wi/haploview/NumberTextField.java
@@ -0,0 +1,59 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import javax.swing.text.Document;
+import javax.swing.text.PlainDocument;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.BadLocationException;
+import java.awt.*;
+
+
+public class NumberTextField extends JTextField {
+    static final long serialVersionUID = -4453143860561756291L;
+
+    int size;
+    boolean decimal;
+    boolean negative;
+
+    public NumberTextField(String str, int s, boolean allowDecimal, boolean allowNegative){
+        super(s);
+        size = s;
+        decimal = allowDecimal;
+        negative = allowNegative;
+        this.setText(str);
+    }
+
+    protected Document createDefaultModel(){
+        return new NTFDocument(this);
+    }
+
+    protected class NTFDocument extends PlainDocument {
+        static final long serialVersionUID = 4966113743009411598L;
+        NumberTextField ntf;
+
+        public NTFDocument(NumberTextField ntf){
+            super();
+            this.ntf = ntf;
+        }
+
+        public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
+            int length = ntf.getText().length();
+            char[] source = str.toCharArray();
+            String to_insert = "";
+            for (int i=0; i<source.length; i++){
+                if (length+i > size-1){
+                    Toolkit.getDefaultToolkit().beep();
+                    super.insertString(offs, to_insert, a);
+                    return;
+                }
+                if (Character.isDigit(source[i]) || (String.valueOf(source[i]).equals(".") && decimal) || (String.valueOf(source[i]).equals("-") && negative)){
+                    to_insert+=source[i];
+                }else{
+                    Toolkit.getDefaultToolkit().beep();
+                }
+            }
+
+            super.insertString(offs, to_insert, a);
+        }
+    }
+}
\ No newline at end of file
diff --git a/edu/mit/wi/haploview/Options.java b/edu/mit/wi/haploview/Options.java
new file mode 100755
index 0000000..9eeef9d
--- /dev/null
+++ b/edu/mit/wi/haploview/Options.java
@@ -0,0 +1,210 @@
+package edu.mit.wi.haploview;
+
+import edu.mit.wi.tagger.Tagger;
+
+public class Options implements Constants{
+    private static int maxDistance = MAXDIST_DEFAULT*1000;
+    private static double missingThreshold = 0.5;
+    private static double spacingThreshold = 0.0;
+    private static int assocTest = ASSOC_NONE;
+    private static int tdtType = TDT_STD;
+    private static double haplotypeDisplayThreshold = 0.01;
+    private static int LDColorScheme = STD_SCHEME;
+    private static boolean showGBrowse = false;
+    private static long gBrowseLeft = 0;
+    private static long gBrowseRight = 0;
+    private static String gBrowseOpts = GB_DEFAULT_OPTS;
+    private static String gBrowseTypes = GB_DEFAULT_TYPES;
+    private static double taggerRsqCutoff = Tagger.DEFAULT_RSQ_CUTOFF;
+    private static double taggerLODCutoff = Tagger.DEFAULT_LOD_CUTOFF;
+    private static int taggerMinDistance = Tagger.DEFAULT_MIN_DISTANCE;
+    private static double taggerMinDesignScore = Tagger.DEFAULT_MIN_DESIGNSCORE;
+    private static int printWhat = D_PRIME;
+    private static boolean showBlockTags = false;
+    private static boolean printAllTags = false;
+    private static boolean gZip, SNPBased;
+
+    public static int getLDColorScheme() {
+        return LDColorScheme;
+    }
+
+    public static void setLDColorScheme(int LDColorScheme) {
+        Options.LDColorScheme = LDColorScheme;
+    }
+
+    public static int getMaxDistance() {
+        return maxDistance;
+    }
+
+    public static void setMaxDistance(int maxDistance) {
+        //takes in max separation in kilobases and converts it to bp
+        Options.maxDistance = maxDistance*1000;
+    }
+
+    public static double getMissingThreshold() {
+        return missingThreshold;
+    }
+
+    public static void setMissingThreshold(double missingThreshold) {
+        Options.missingThreshold = missingThreshold;
+    }
+
+    public static double getSpacingThreshold() {
+        return spacingThreshold;
+    }
+
+    public static void setSpacingThreshold(double spacingThreshold) {
+        //we scale from (0 to 1) to (0 to .5) since values greater than .5 cause the display to look really stupid
+        Options.spacingThreshold = spacingThreshold*0.5;
+    }
+
+    public static int getAssocTest() {
+        return assocTest;
+    }
+
+    public static void setAssocTest(int assocTest) {
+        Options.assocTest = assocTest;
+    }
+
+    public static double getHaplotypeDisplayThreshold() {
+        return haplotypeDisplayThreshold;
+    }
+
+    public static void setHaplotypeDisplayThreshold(double haplotypeDisplayThreshold) {
+        Options.haplotypeDisplayThreshold = haplotypeDisplayThreshold;
+    }
+
+    public static boolean isGBrowseShown() {
+        return showGBrowse;
+    }
+
+    public static void setShowGBrowse(boolean showGBrowse) {
+        Options.showGBrowse = showGBrowse;
+    }
+
+    public static long getgBrowseLeft() {
+        return gBrowseLeft;
+    }
+
+    public static void setgBrowseLeft(long gBrowseLeft) {
+        Options.gBrowseLeft = gBrowseLeft;
+    }
+
+    public static long getgBrowseRight() {
+        return gBrowseRight;
+    }
+
+    public static void setgBrowseRight(long gBrowseRight) {
+        Options.gBrowseRight = gBrowseRight;
+    }
+
+    public static String getgBrowseOpts() {
+        if (gBrowseOpts.equals("")){
+            return "null";
+        }else{
+            return gBrowseOpts;
+        }
+    }
+
+    public static void setgBrowseOpts(String gBrowseOpts) {
+        Options.gBrowseOpts = gBrowseOpts;
+    }
+
+    public static String getgBrowseTypes() {
+        if (gBrowseTypes.equals("")){
+            return "null";
+        }else{
+            return gBrowseTypes;
+        }
+    }
+
+    public static void setgBrowseTypes(String gBrowseTypes) {
+        Options.gBrowseTypes = gBrowseTypes;
+    }
+
+    public static double getTaggerRsqCutoff() {
+        return taggerRsqCutoff;
+    }
+
+    public static void setTaggerRsqCutoff(double taggerRsqCutoff) {
+        Options.taggerRsqCutoff = taggerRsqCutoff;
+    }
+
+    public static double getTaggerLODCutoff() {
+        return taggerLODCutoff;
+    }
+
+    public static void setTaggerLODCutoff(double taggerLODCutoff) {
+        Options.taggerLODCutoff = taggerLODCutoff;
+    }
+
+    public static int getTaggerMinDistance() {
+        return taggerMinDistance;
+    }
+
+    public static void setTaggerMinDistance(int taggerMinDistance) {
+        Options.taggerMinDistance = taggerMinDistance;
+    }
+
+    public static double getTaggerMinDesignScore() {
+        return taggerMinDesignScore;
+    }
+
+    public static void setTaggerMinDesignScore(double taggerMinDesignScore) {
+        Options.taggerMinDesignScore = taggerMinDesignScore;
+    }
+
+    public static int getTdtType() {
+        return tdtType;
+    }
+
+    public static void setTdtType(int tdtType) {
+        Options.tdtType = tdtType;
+    }
+
+    public static int getPrintWhat() {
+        return printWhat;
+    }
+
+    public static void setPrintWhat(int printWhat) {
+        Options.printWhat = printWhat;
+    }
+
+    public static boolean isShowBlockTags() {
+        return showBlockTags;
+    }
+
+    public static void setShowBlockTags(boolean showBlockTags) {
+        Options.showBlockTags = showBlockTags;
+    }
+
+    public static boolean isPrintAllTags() {
+        return printAllTags;
+    }
+
+    public static void setPrintAllTags(boolean printAllTags) {
+        Options.printAllTags = printAllTags;
+    }
+
+    public static boolean getGzip(){
+        return gZip;
+    }
+
+    public static void setGzip(boolean gZip){
+        Options.gZip = gZip;
+    }
+
+    public static boolean getSNPBased(){
+        return SNPBased;
+    }
+
+    public static void setSNPBased(boolean snp){
+        SNPBased = snp;
+    }
+
+    public static void setProxy(String host, String port){
+        System.getProperties().put("proxySet", "true");
+        System.getProperties().put("proxyHost", host);
+        System.getProperties().put("proxyPort", port);
+    }
+}
diff --git a/edu/mit/wi/haploview/PairwiseLinkage.java b/edu/mit/wi/haploview/PairwiseLinkage.java
new file mode 100755
index 0000000..42ac4fc
--- /dev/null
+++ b/edu/mit/wi/haploview/PairwiseLinkage.java
@@ -0,0 +1,57 @@
+package edu.mit.wi.haploview;
+
+import java.awt.Color;
+
+public class PairwiseLinkage{
+
+    private double dprime, lod, r2, ci_low, ci_high;
+    private double[] freqs;
+    private Color color;
+
+    PairwiseLinkage(double d, double l, double r, double lo, double hi, double[] f){
+        dprime = d;
+        lod = l;
+        r2 = r;
+        ci_low = lo;
+        ci_high = hi;
+        freqs = f;
+        color = Color.white;
+    }
+
+    public double getDPrime(){
+        return dprime;
+    }
+
+    public double getLOD(){
+        return lod;
+    }
+
+    public double getRSquared(){
+        return r2;
+    }
+
+    public double getConfidenceLow(){
+        return ci_low;
+    }
+
+    public double getConfidenceHigh(){
+        return ci_high;
+    }
+
+    public double[] getFreqs(){
+        return freqs;
+    }
+
+
+    public Color getColor(){
+        return color;
+    }
+
+    public void setColor(Color c){
+        color = c;
+    }
+
+    public String toString(){
+        return new String(dprime + "\t" + lod + "\t" + r2 + "\t" + ci_low + "\t" + ci_high);
+    }
+}
diff --git a/edu/mit/wi/haploview/PlinkResultsPanel.java b/edu/mit/wi/haploview/PlinkResultsPanel.java
new file mode 100755
index 0000000..7c468b2
--- /dev/null
+++ b/edu/mit/wi/haploview/PlinkResultsPanel.java
@@ -0,0 +1,740 @@
+package edu.mit.wi.haploview;
+
+import edu.mit.wi.plink.PlinkTableModel;
+import edu.mit.wi.plink.PlinkGraph;
+import edu.mit.wi.plink.PlotOptionDialog;
+
+import javax.swing.*;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.border.TitledBorder;
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.Arrays;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+import java.io.IOException;
+import java.io.FileWriter;
+import java.io.BufferedWriter;
+
+public class PlinkResultsPanel extends JPanel implements ActionListener, Constants {
+    static final long serialVersionUID = -6824022261340524367L;
+    private JTable table;
+    private PlinkTableModel plinkTableModel;
+    private TableSorter sorter;
+
+    String[] chromNames = {"","1","2","3","4","5","6","7","8","9","10",
+            "11","12","13","14","15","16","17","18","19","20","21","22","X","Y","XY","MT"};
+    String[] signs = {"",">",">=","=","<=","<"};
+    private JComboBox chromChooser, genericChooser, signChooser, removeChooser;
+    private JButton viewFilters;
+    private NumberTextField chromStart, chromEnd;
+    private JTextField valueField, markerField;
+    private JPanel filterPanel;
+    private Vector originalColumns, genericFilters;
+    private Hashtable removedColumns;
+
+    private String chosenMarker, chromChoice;
+    private int startPos, endPos;
+    private HaploView hv;
+    private PlinkGraph theGraph;
+
+
+    public PlinkResultsPanel(HaploView h, Vector results, Vector colNames, boolean dups, Hashtable remove){
+        hv = h;
+
+        setLayout(new GridBagLayout());
+
+        plinkTableModel = new PlinkTableModel(colNames,results);
+        sorter = new TableSorter(plinkTableModel);
+        removedColumns = new Hashtable();
+        originalColumns = (Vector)plinkTableModel.getUnknownColumns().clone();
+        genericFilters = new Vector();
+
+
+        table = new JTable(sorter);
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        table.setSelectionBackground(Color.lightGray);
+        table.getTableHeader().setReorderingAllowed(false);
+        //due to an old JTable bug this is necessary to activate a horizontal scrollbar
+        if (table.getColumnCount() >= 20 && table.getRowCount() > 60){
+            table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+        }
+        //table.getModel().addTableModelListener(this);
+
+        sorter.setTableHeader(table.getTableHeader());
+        table.getColumnModel().getColumn(0).setPreferredWidth(60);
+
+        if (Options.getSNPBased()){
+            final PlinkCellRenderer renderer = new PlinkCellRenderer();
+            try{
+                table.setDefaultRenderer(Class.forName("java.lang.Double"), renderer);
+                table.setDefaultRenderer(Class.forName("java.lang.Integer"), renderer);
+                table.setDefaultRenderer(Class.forName("java.lang.Long"), renderer);
+                table.setDefaultRenderer(Class.forName("java.lang.String"),renderer);
+            }catch (Exception e){
+            }
+        }
+
+        JScrollPane tableScroller = new JScrollPane(table);
+
+
+        JPanel mainFilterPanel = new JPanel();
+        JPanel extraFilterPanel = new JPanel();
+        mainFilterPanel.setMinimumSize(new Dimension(700,40));
+        if (Options.getSNPBased()){
+            mainFilterPanel.add(new JLabel("Chr:"));
+            chromChooser = new JComboBox(chromNames);
+            mainFilterPanel.add(chromChooser);
+            mainFilterPanel.add(new JLabel("Start kb:"));
+            chromStart = new NumberTextField("",6,false, false);
+            mainFilterPanel.add(chromStart);
+            mainFilterPanel.add(new JLabel("End kb:"));
+            chromEnd = new NumberTextField("",6,false, false);
+            mainFilterPanel.add(chromEnd);
+            extraFilterPanel.add(new JLabel("Goto Marker:"));
+            markerField = new JTextField(8);
+            extraFilterPanel.add(markerField);
+            JButton doMarkerFilter = new JButton("Go");
+            doMarkerFilter.setActionCommand("marker filter");
+            doMarkerFilter.addActionListener(this);
+            extraFilterPanel.add(doMarkerFilter);
+        }
+        mainFilterPanel.add(new JLabel("Filter:"));
+        genericChooser = new JComboBox(plinkTableModel.getUnknownColumns());
+        genericChooser.setSelectedIndex(-1);
+        mainFilterPanel.add(genericChooser);
+        signChooser = new JComboBox(signs);
+        signChooser.setSelectedIndex(-1);
+        mainFilterPanel.add(signChooser);
+        valueField = new JTextField(8);
+        mainFilterPanel.add(valueField);
+        JButton doFilter = new JButton("Filter");
+        doFilter.addActionListener(this);
+        mainFilterPanel.add(doFilter);
+        viewFilters = new JButton("View Active Filters");
+        viewFilters.setActionCommand("View Active Filters");
+        viewFilters.addActionListener(this);
+        viewFilters.setEnabled(false);
+        mainFilterPanel.add(viewFilters);
+
+        extraFilterPanel.add(new JLabel("Remove Column:"));
+        removeChooser = new JComboBox(plinkTableModel.getUnknownColumns());
+        extraFilterPanel.add(removeChooser);
+        JButton removeButton = new JButton("Remove");
+        removeButton.addActionListener(this);
+        extraFilterPanel.add(removeButton);
+
+        JButton resetFilters = new JButton("Reset");
+        resetFilters.addActionListener(this);
+        JButton moreResults = new JButton("Load Additional Results");
+        moreResults.addActionListener(this);
+        if (dups){
+            moreResults.setEnabled(false);
+        }
+        JButton fisherButton = new JButton("Fisher Combine P-Values");
+        fisherButton.addActionListener(this);
+
+        JButton plotButton = new JButton("Plot");
+        plotButton.addActionListener(this);
+
+        filterPanel = new JPanel(new GridBagLayout());
+        GridBagConstraints a = new GridBagConstraints();
+        filterPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black),
+                "Viewing " + plinkTableModel.getRowCount() + " results"));
+        ((TitledBorder)(filterPanel.getBorder())).setTitleColor(Color.black);
+
+        a.gridwidth = 5;
+        a.anchor = GridBagConstraints.CENTER;
+        a.weightx = 1;
+        filterPanel.add(mainFilterPanel,a);
+        a.gridy = 1;
+        filterPanel.add(extraFilterPanel,a);
+        a.gridy = 2;
+        a.gridwidth = 1;
+        a.anchor = GridBagConstraints.SOUTHWEST;
+        a.insets = new Insets(5,0,0,0);
+        if (Options.getSNPBased()){
+            filterPanel.add(moreResults,a);
+            a.gridx = 1;
+            filterPanel.add(fisherButton,a);
+            a.gridx = 2;
+            a.anchor = GridBagConstraints.SOUTH;
+        }
+        filterPanel.add(plotButton,a);
+        a.gridx = 3;
+        a.anchor = GridBagConstraints.SOUTHEAST;
+        filterPanel.add(resetFilters,a);
+
+
+        JPanel goPanel = new JPanel();
+        JButton goButton = new JButton("<html><b>Go to Selected Region</b>");
+        goButton.addActionListener(this);
+        goButton.setActionCommand("Go to Selected Region");
+        goPanel.add(goButton);
+
+        GridBagConstraints c = new GridBagConstraints();
+        c.fill = 1;
+        c.weightx = 1;
+        c.weighty = 1;
+        c.insets = new Insets(10,10,10,10);
+        add(tableScroller, c);
+        c.weighty = 0;
+        c.gridy = 1;
+        add(filterPanel, c);
+        if (Options.getSNPBased()){
+            c.gridy = 2;
+            add(goPanel,c);
+        }
+
+        if (remove != null){
+            for (int i = 1; i < plinkTableModel.getUnknownColumns().size(); i++){
+                if (remove.containsKey(plinkTableModel.getUnknownColumns().get(i))){
+                    removeColumn((String)plinkTableModel.getUnknownColumns().get(i),i);
+                }
+            }
+        }
+    }
+
+    public void jumpToMarker(String marker){
+        for (int i = 0; i < table.getRowCount(); i++){
+            String currMarker = (String) table.getValueAt(i,1);
+            if (currMarker.equalsIgnoreCase(marker)){
+                table.changeSelection(i,1,false,false);
+                break;
+            }
+        }
+        hv.requestFocus();
+        hv.toFront();
+    }
+
+    public void jumpToNonSNP(String fid, String iid){
+        if (plinkTableModel.getFIDColumn() != -1 && plinkTableModel.getIIDColumn() != -1){
+            for (int i = 0; i < table.getRowCount(); i++){
+                String currentFid = (String)table.getValueAt(i,plinkTableModel.getFIDColumn());
+                String currentIid = (String)table.getValueAt(i,plinkTableModel.getIIDColumn());
+                if (currentFid.equals(fid) && currentIid.equals(iid)){
+                    table.changeSelection(i,1,false,false);
+                    break;
+                }
+            }
+            hv.requestFocus();
+            hv.toFront();
+        }
+    }
+
+    public void setChosenMarker(String chosenMarker) {
+        this.chosenMarker = chosenMarker;
+    }
+
+    public String getChosenMarker() {
+        return chosenMarker;
+    }
+
+    public Vector getSNPs(){
+        return plinkTableModel.getSNPs();
+    }
+
+    public Object getValueAt(int row, int col){
+        return plinkTableModel.getValueAt(row,col);
+    }
+
+    public void doFilters(){
+        chromChoice = "";
+        startPos = -1;
+        endPos = -1;
+        if (Options.getSNPBased()){
+            chromChoice = (String)chromChooser.getSelectedItem();
+
+            if (chromStart.getText().equals("")){
+                startPos = -1;
+            }else{
+                startPos = Integer.parseInt(chromStart.getText());
+            }
+
+            if (chromEnd.getText().equals("")){
+                endPos = -1;
+            }else{
+                endPos = Integer.parseInt(chromEnd.getText());
+            }
+            if (startPos > endPos){
+                JOptionPane.showMessageDialog(this.getParent(),
+                        "End position must be greater then start position.",
+                        "Invalid value",
+                        JOptionPane.ERROR_MESSAGE);
+                return;
+            }
+        }
+
+        String columnChoice, signChoice, val;
+
+        if (genericChooser.getSelectedIndex() > 0){
+            columnChoice = (String) genericChooser.getSelectedItem();
+
+            if (signChooser.getSelectedIndex() > 0){
+                signChoice = (String) signChooser.getSelectedItem();
+
+                if (!(valueField.getText().equals(""))){
+                    val = valueField.getText();
+                    genericFilters.add(columnChoice + "\t" + signChoice + "\t" + val);
+                }
+            }
+        }
+
+        reSort();
+        plinkTableModel.filterAll(chromChoice,startPos,endPos,genericFilters);
+        countResults();
+    }
+
+    public void removeColumn(String col, int index){
+        TableColumn deletedColumn = table.getColumn(col);
+        removedColumns.put(col,deletedColumn);
+        table.removeColumn(deletedColumn);
+        removeChooser.removeItemAt(index);
+        removeChooser.setSelectedIndex(removeChooser.getItemCount()-1);
+        genericChooser.setSelectedIndex(0);
+        if (table.getColumnCount() < 20){
+            table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
+        }
+        repaint();
+    }
+
+    public void clearFilters(){
+        if (removedColumns.size() > 0){
+            for (int i = 1; i < removeChooser.getItemCount(); i++){
+                TableColumn deletedColumn = table.getColumn(removeChooser.getItemAt(i));
+                removedColumns.put(removeChooser.getItemAt(i),deletedColumn);
+                table.removeColumn(deletedColumn);
+            }
+
+            removeChooser.removeAllItems();
+            removeChooser.addItem("");
+
+            for (int i = 1; i < originalColumns.size(); i++){
+                TableColumn addedColumn = (TableColumn)removedColumns.get(originalColumns.get(i));
+                table.addColumn(addedColumn);
+                removeChooser.addItem(originalColumns.get(i));
+            }
+            removedColumns.clear();
+        }
+
+        if (table.getColumnCount() >= 20 && table.getRowCount() > 60){
+            table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+        }
+
+        plinkTableModel.resetFilters();
+        if (Options.getSNPBased()){
+            chromChooser.setSelectedIndex(0);
+            chromStart.setText("");
+            chromEnd.setText("");
+            markerField.setText("");
+        }
+        genericChooser.setSelectedIndex(0);
+        genericChooser.updateUI();
+        signChooser.setSelectedIndex(0);
+        valueField.setText("");
+        genericFilters = new Vector();
+        reSort();
+        countResults();
+    }
+
+    public void reSort(){
+        for (int i = 0; i < table.getColumnCount(); i++){
+            sorter.setSortingStatus(i,sorter.getSortingStatus(i));
+        }
+    }
+
+    public void countResults(){
+        filterPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black),
+                "Viewing " + plinkTableModel.getRowCount() + " results"));
+        if (genericFilters.size() == 0){
+            viewFilters.setEnabled(false);
+            viewFilters.setText("View Active Filters");
+        }else{
+            viewFilters.setEnabled(true);
+            viewFilters.setText("View " + genericFilters.size() + " Active Filters");
+        }
+        repaint();
+    }
+
+    public void makeChart(String title,int yPlotType,int yColumn,int xPlotType,int xColumn,double suggestive,double significant,boolean useSug,boolean useSig,int[] signs,int[] thresholds,int dotSize,int colorColumn,boolean grid, File svgFile, int width, int height){
+        hv.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+        theGraph = new PlinkGraph(title,yPlotType,yColumn,xPlotType,xColumn,suggestive,significant,useSug,useSig,signs,thresholds,dotSize,colorColumn,grid,svgFile,width,height,table,plinkTableModel,this);
+        hv.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    }
+
+    public void gotoRegion(){
+        if (table.getSelectedRow() == -1){
+            JOptionPane.showMessageDialog(this,
+                    "Please select a region.",
+                    "Invalid value",
+                    JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+        String gotoChrom = (String)table.getValueAt(table.getSelectedRow(),0);
+        String gotoMarker = (String)table.getValueAt(table.getSelectedRow(),1);
+        long markerPosition = ((Long)(table.getValueAt(table.getSelectedRow(),2))).longValue();
+
+        RegionDialog rd = new RegionDialog(hv,gotoChrom,gotoMarker,
+                this,markerPosition,"Go to Region");
+        rd.pack();
+        rd.setVisible(true);
+    }
+
+    public Vector getUnknownColumns(){
+        return plinkTableModel.getUnknownColumns();
+    }
+
+    public Vector getOriginalColumns(){
+        return originalColumns;
+    }
+
+    public void exportTable(File outfile) throws IOException, HaploViewException{
+        BufferedWriter plinkWriter = new BufferedWriter(new FileWriter(outfile));
+        for (int i = 0; i < table.getColumnCount(); i++){
+            if (table.getColumnName(i).equalsIgnoreCase("CHROM")){
+                plinkWriter.write("CHR"+"\t");
+            }else if (table.getColumnName(i).equalsIgnoreCase("MARKER")){
+                plinkWriter.write("SNP"+"\t");
+            }else{
+                plinkWriter.write(table.getColumnName(i)+"\t");
+            }
+        }
+        plinkWriter.newLine();
+
+        for (int i = 0; i < table.getRowCount(); i++){
+            for (int j = 0; j < table.getColumnCount(); j++){
+                if (table.getValueAt(i,j) == null){
+                    plinkWriter.write("-"+"\t");
+                }else{
+                    plinkWriter.write(table.getValueAt(i,j)+"\t");
+                }
+            }
+            plinkWriter.newLine();
+        }
+        plinkWriter.close();
+    }
+
+    public void disposePlot(){
+        if (theGraph != null){
+            theGraph.disposePlot();
+            theGraph = null;
+        }
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if (command.equals("Filter")){
+            doFilters();
+        }else if (command.equals("marker filter")){
+            String marker = markerField.getText();
+            if (!(marker.equals(""))){
+                jumpToMarker(marker);
+            }
+        }else if (command.equals("Reset")){
+            clearFilters();
+        }else if (command.equals("Load Additional Results")){
+            HaploView.fc.setSelectedFile(new File(""));
+            int returned = HaploView.fc.showOpenDialog(this);
+            if (returned != JFileChooser.APPROVE_OPTION) return;
+            File file = HaploView.fc.getSelectedFile();
+            String fullName = file.getParent()+File.separator+file.getName();
+            String[] inputs = {null,null,fullName,null,null,null,"Y"};
+            if (removedColumns.size() > 0){
+                hv.setRemovedColumns(removedColumns);
+            }
+            hv.readWGA(inputs);
+        }else if (command.equals("Fisher Combine P-Values")){
+            FisherCombinedDialog fcd = new FisherCombinedDialog("Combine");
+            fcd.pack();
+            fcd.setVisible(true);
+        }else if (command.equals("View Active Filters")){
+            ActiveFiltersDialog afd = new ActiveFiltersDialog("Active Filters");
+            afd.pack();
+            afd.setVisible(true);
+        }else if (command.equals("Remove")){
+            if (removeChooser.getSelectedIndex() > 0){
+                removeColumn((String)removeChooser.getSelectedItem(),removeChooser.getSelectedIndex());
+            }
+        }else if (command.equals("Plot")){
+            PlotOptionDialog pod = new PlotOptionDialog(hv,this,"Plot Options",plinkTableModel);
+            pod.pack();
+            pod.setVisible(true);
+        }else if (command.equals("Go to Selected Region")){
+            gotoRegion();
+        }
+    }
+
+    class FisherCombinedDialog extends JDialog implements ActionListener{
+        static final long serialVersionUID = 3662196055616495550L;
+        private JComboBox pval1, pval2, pval3, pval4, pval5;
+        private Vector columnIndeces;
+        FisherCombinedDialog(String title){
+            super(hv,title);
+
+            JPanel contents = new JPanel();
+            contents.setLayout(new BoxLayout(contents,BoxLayout.Y_AXIS));
+            //contents.setPreferredSize(new Dimension(150,200));
+
+            if (removedColumns != null){
+                if (removedColumns.size() > 0){
+                    clearFilters();
+                }
+            }
+
+            Vector doubleColumns = new Vector();
+            columnIndeces = new Vector();
+            doubleColumns.add("");
+            for (int i = 1; i < plinkTableModel.getUnknownColumns().size(); i++){
+                for (int j = 0; j < 50; j++){
+                    if (table.getValueAt(j,i+2) != null){
+                        if (table.getValueAt(j,i+2) instanceof Double){
+                            doubleColumns.add(plinkTableModel.getUnknownColumns().get(i));
+                            columnIndeces.add(new Integer(i-1));
+                        }
+                        break;
+                    }else if (j == 49){
+                        doubleColumns.add(plinkTableModel.getUnknownColumns().get(i));
+                        columnIndeces.add(new Integer(i-1));
+                    }
+                }
+            }
+
+            JPanel pval1Panel = new JPanel();
+            pval1Panel.add(new JLabel("Pval 1:"));
+            pval1 = new JComboBox(doubleColumns);
+            pval1Panel.add(pval1);
+            JPanel pval2Panel = new JPanel();
+            pval2Panel.add(new JLabel("Pval 2:"));
+            pval2 = new JComboBox(doubleColumns);
+            pval2Panel.add(pval2);
+            JPanel pval3Panel = new JPanel();
+            pval3Panel.add(new JLabel("Pval 3:"));
+            pval3 = new JComboBox(doubleColumns);
+            pval3Panel.add(pval3);
+            JPanel pval4Panel = new JPanel();
+            pval4Panel.add(new JLabel("Pval 4:"));
+            pval4 = new JComboBox(doubleColumns);
+            pval4Panel.add(pval4);
+            JPanel pval5Panel = new JPanel();
+            pval5Panel.add(new JLabel("Pval 5:"));
+            pval5 = new JComboBox(doubleColumns);
+            pval5Panel.add(pval5);
+
+            JPanel choicePanel = new JPanel();
+            JButton goButton = new JButton("Go");
+            goButton.addActionListener(this);
+            choicePanel.add(goButton);
+            JButton cancelButton = new JButton("Cancel");
+            cancelButton.addActionListener(this);
+            choicePanel.add(cancelButton);
+
+            contents.add(pval1Panel);
+            contents.add(pval2Panel);
+            contents.add(pval3Panel);
+            contents.add(pval4Panel);
+            contents.add(pval5Panel);
+            contents.add(choicePanel);
+
+            setContentPane(contents);
+            this.setLocation(this.getParent().getX() + 100,
+                    this.getParent().getY() + 100);
+            this.setModal(true);
+            this.getRootPane().setDefaultButton(goButton);
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            String command = e.getActionCommand();
+
+            if (command.equals("Cancel")){
+                this.dispose();
+            }else if (command.equals("Go")){
+                int[] pCols = new int[5];
+                Arrays.fill(pCols,-1);
+                int numPvals = 0;
+
+                if (pval1.getSelectedIndex() > 0){
+                    numPvals++;
+                    pCols[0] = ((Integer)columnIndeces.get(pval1.getSelectedIndex()-1)).intValue();
+                }
+                if (pval2.getSelectedIndex() > 0){
+                    numPvals++;
+                    pCols[1] = ((Integer)columnIndeces.get(pval2.getSelectedIndex()-1)).intValue();
+                }
+                if (pval3.getSelectedIndex() > 0){
+                    numPvals++;
+                    pCols[2] = ((Integer)columnIndeces.get(pval3.getSelectedIndex()-1)).intValue();
+                }
+                if (pval4.getSelectedIndex() > 0){
+                    numPvals++;
+                    pCols[3] = ((Integer)columnIndeces.get(pval4.getSelectedIndex()-1)).intValue();
+                }
+                if (pval5.getSelectedIndex() > 0){
+                    numPvals++;
+                    pCols[4] = ((Integer)columnIndeces.get(pval5.getSelectedIndex()-1)).intValue();
+                }
+
+                if (numPvals < 2){
+                    JOptionPane.showMessageDialog(this,
+                            "Please choose at least 2 pvalue columns.",
+                            "Invalid selection",
+                            JOptionPane.ERROR_MESSAGE);
+                    return;
+                }
+
+                String cols = "";
+                for (int i = 0; i < pCols.length; i++){
+                    if (pCols[i] > -1){
+                        cols = cols + pCols[i] + " ";
+                    }
+                }
+
+                String[] inputs = {null,null,null,null,cols,null,null};
+                this.dispose();
+                hv.readWGA(inputs);
+            }
+        }
+    }
+
+    class ActiveFiltersDialog extends JDialog implements ActionListener{
+	static final long serialVersionUID = 1282427101754100571L;
+        JPanel contents, chPanel;
+        JCheckBox[] checks;
+        JComboBox gChooser, sChooser;
+        JTextField vField;
+
+        ActiveFiltersDialog(String title){
+            super(hv,title);
+            contents = new JPanel();
+            contents.setLayout(new BoxLayout(contents,BoxLayout.Y_AXIS));
+            //contents.setPreferredSize(new Dimension(300,150));
+
+            changeContents();
+            this.setLocation(this.getParent().getX() + 100,
+                    this.getParent().getY() + 100);
+            this.setModal(true);
+
+        }
+
+        public void changeContents(){
+            contents.removeAll();
+            checks = new JCheckBox[genericFilters.size()];
+            for (int i = 0; i < genericFilters.size(); i++){
+                checks[i] = new JCheckBox((String)genericFilters.get(i));
+                checks[i].setSelected(true);
+            }
+
+            chPanel = new JPanel();
+            chPanel.setLayout(new BoxLayout(chPanel,BoxLayout.Y_AXIS));
+            for (int i = 0; i < checks.length; i++){
+                chPanel.add(checks[i]);
+            }
+            JScrollPane jsp = new JScrollPane(chPanel);
+
+            JPanel filterPanel = new JPanel();
+            filterPanel.add(new JLabel("Filter:"));
+            gChooser = new JComboBox(plinkTableModel.getUnknownColumns());
+            gChooser.setSelectedIndex(-1);
+            filterPanel.add(gChooser);
+            sChooser = new JComboBox(signs);
+            sChooser.setSelectedIndex(-1);
+            filterPanel.add(sChooser);
+            vField = new JTextField(8);
+            filterPanel.add(vField);
+            JButton addFilter = new JButton("Add");
+            addFilter.addActionListener(this);
+            filterPanel.add(addFilter);
+
+
+            JPanel choicePanel = new JPanel();
+            JButton doneButton = new JButton("<html><b>Done</b>");
+            doneButton.addActionListener(this);
+            doneButton.setActionCommand("Done");
+            choicePanel.add(doneButton);
+            JButton clearButton = new JButton("Clear");
+            clearButton.addActionListener(this);
+            choicePanel.add(clearButton);
+
+            contents.add(jsp);
+            contents.add(filterPanel);
+            contents.add(choicePanel);
+
+            contents.repaint();
+            this.setContentPane(contents);
+        }
+
+        public void actionPerformed(ActionEvent e){
+            String command = e.getActionCommand();
+
+            if (command.equals("Add")){
+                String columnChoice, signChoice, val;
+
+                if (gChooser.getSelectedIndex() > 0){
+                    columnChoice = (String) gChooser.getSelectedItem();
+
+                    if (sChooser.getSelectedIndex() > 0){
+                        signChoice = (String) sChooser.getSelectedItem();
+
+                        if (!(vField.getText().equals(""))){
+                            val = vField.getText();
+                            genericFilters.add(columnChoice + "\t" + signChoice + "\t" + val);
+                            changeContents();
+                            this.setSize(this.getWidth(),this.getHeight()+25);
+
+                        }
+                    }
+                }
+            }else if (command.equals("Clear")){
+                genericFilters = new Vector();
+                changeContents();
+            }else if (command.equals("Done")){
+                for (int i = checks.length-1; i >=0; i--){
+                    if (!checks[i].isSelected()){
+                        genericFilters.remove(i);
+                    }
+                }
+                genericChooser.setSelectedIndex(0);
+                genericChooser.updateUI();
+                signChooser.setSelectedIndex(0);
+                valueField.setText("");
+                reSort();
+                plinkTableModel.filterAll(chromChoice,startPos,endPos,genericFilters);
+                countResults();
+                this.dispose();
+            }
+        }
+
+    }
+
+    class PlinkCellRenderer extends DefaultTableCellRenderer {
+        static final long serialVersionUID = -6099003102431307856L;
+        public Component getTableCellRendererComponent
+                (JTable table, Object value, boolean isSelected,
+                 boolean hasFocus, int row, int column)
+        {
+            Component cell = super.getTableCellRendererComponent
+                    (table, value, isSelected, hasFocus, row, column);
+            String thisMarker = (String)table.getValueAt(row,1);
+            cell.setForeground(Color.black);
+            cell.setBackground(Color.white);
+
+            if (isSelected){
+                cell.setBackground(table.getSelectionBackground());
+            }else{
+                cell.setBackground(table.getBackground());
+            }
+
+            if (thisMarker.equals(hv.getChosenMarker())){
+                cell.setBackground(Color.cyan);
+            }
+
+            if (table.getColumnName(column).startsWith("P_COMBINED")){
+                if (((Double)table.getValueAt(row,column)).doubleValue() == 1.0E-16){
+                    cell.setForeground(Color.red);
+                }
+            }
+            return cell;
+        }
+    }
+}
\ No newline at end of file
diff --git a/edu/mit/wi/haploview/ProportionalSpacingDialog.java b/edu/mit/wi/haploview/ProportionalSpacingDialog.java
new file mode 100755
index 0000000..e44a03a
--- /dev/null
+++ b/edu/mit/wi/haploview/ProportionalSpacingDialog.java
@@ -0,0 +1,73 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ChangeEvent;
+import java.util.Hashtable;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.Cursor;
+
+public class ProportionalSpacingDialog extends JDialog implements ActionListener,
+        ChangeListener, Constants{
+    static final long serialVersionUID = 8690815664106595822L;
+
+    private HaploView hv;
+
+    public ProportionalSpacingDialog(HaploView h, String title){
+        super (h, title);
+
+        hv = h;
+
+        JPanel contents = new JPanel();
+        contents.setLayout(new BoxLayout(contents, BoxLayout.Y_AXIS));
+
+        JSlider slider = new JSlider(0,100,
+                (int)(Options.getSpacingThreshold()*200));
+        Hashtable labeltable = new Hashtable();
+        labeltable.put(new Integer(0), new JLabel("None"));
+        labeltable.put(new Integer(100), new JLabel ("Full"));
+        slider.setLabelTable( labeltable );
+        slider.setPaintLabels(true);
+        slider.addChangeListener(this);
+        contents.add(slider);
+
+        JButton doneButton = new JButton("Done");
+        doneButton.addActionListener(this);
+        contents.add(doneButton);
+
+        this.setContentPane(contents);
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if (command.equals("Done")){
+            this.dispose();
+        }
+    }
+
+    public void stateChanged(ChangeEvent e) {
+        JSlider source = (JSlider)e.getSource();
+        if (!source.getValueIsAdjusting()) {
+            double thresh = ((double)source.getValue())/100;
+            Options.setSpacingThreshold(thresh);
+
+            //note that this will only apply to the cursor in this dialog, but java seems touchy
+            //about setting a "global" cursor
+            setCursor(new Cursor(Cursor.WAIT_CURSOR));
+
+            javax.swing.SwingUtilities.invokeLater(new Runnable() {
+                public void run() {
+                    hv.dPrimeDisplay.computePreferredSize();
+                    if (hv.dPrimeDisplay != null && hv.tabs.getSelectedIndex() == VIEW_D_NUM){
+                        hv.dPrimeDisplay.repaint();
+                    }
+                    setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+                }
+            });
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/ProxyDialog.java b/edu/mit/wi/haploview/ProxyDialog.java
new file mode 100755
index 0000000..5a1d748
--- /dev/null
+++ b/edu/mit/wi/haploview/ProxyDialog.java
@@ -0,0 +1,59 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+
+
+public class ProxyDialog extends JDialog implements ActionListener, Constants {
+    static final long serialVersionUID = 8309981884313205960L;
+
+    JTextField hostText = new JTextField("",10);
+    JTextField portText = new JTextField("",4);
+
+    public ProxyDialog (ReadDataDialog rd, String title){
+        super(rd,title);
+
+        JPanel contents = new JPanel();
+        contents.setLayout(new BoxLayout(contents,BoxLayout.Y_AXIS));
+
+        JButton okButton = new JButton("OK");
+        okButton.addActionListener(this);
+        JButton cancelButton = new JButton("Cancel");
+        cancelButton.addActionListener(this);
+
+        JPanel host = new JPanel();
+        host.add(new JLabel("HTTP Proxy:"));
+        host.add(hostText);
+        host.add(new JLabel("Port:"));
+        host.add(portText);
+        contents.add(host);
+
+        JPanel choicePanel = new JPanel();
+        choicePanel.add(okButton);
+        choicePanel.add(cancelButton);
+        contents.add(choicePanel);
+
+        setContentPane(contents);
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+        this.setResizable(false);
+
+
+
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if(command.equals("Cancel")) {
+            this.dispose();
+        }else if(command.equals("OK")){
+            Options.setProxy(hostText.getText(), portText.getText());
+            final SwingWorker worker = HaploView.showUpdatePanel();
+            worker.start();
+            this.dispose();
+        }
+    }
+
+}
diff --git a/edu/mit/wi/haploview/ReadDataDialog.java b/edu/mit/wi/haploview/ReadDataDialog.java
new file mode 100755
index 0000000..e486f73
--- /dev/null
+++ b/edu/mit/wi/haploview/ReadDataDialog.java
@@ -0,0 +1,1101 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.DocumentEvent;
+import java.awt.event.*;
+import java.awt.*;
+import java.io.*;
+import java.util.*;
+
+
+public class ReadDataDialog extends JDialog
+        implements ActionListener, DocumentListener, Constants {
+    static final long serialVersionUID = -1864177851666320697L;
+
+    static final String MARKER_DATA_EXT = ".info";
+    static final String MAP_FILE_EXT = ".map";
+    static final String BIM_FILE_EXT = ".bim";
+    static final String BROWSE_GENO = "browse for geno files";
+    static final String BROWSE_HAPS = "browse for haps files";
+    static final String BROWSE_HMP = "browse for hapmap files";
+    static final String BROWSE_PHASE = "browse for PHASE files";
+    static final String BROWSE_SAMPLE = "browse for sample files";
+    static final String BROWSE_LEGEND = "browse for legend files";
+    static final String BROWSE_INFO = "browse for info files";
+    static final String BROWSE_ASSOC = "browse for association test files";
+    static final String BROWSE_WGA = "browse for PLINK files";
+    static final String BROWSE_MAP = "browse for PLINK map files";
+
+    private int fileType;
+    private JTextField pedFileField, pedInfoField, hapsFileField, hapsInfoField, hmpFileField,
+            phaseFileField, phaseSampleField, phaseLegendField, geneCruiseField, plinkFileField, plinkMapField, testFileField;
+    private JCheckBox doAssociation, doGB, phaseDoGB, downloadDoGB, xChrom, gZip, embeddedMap, plinkChrom, selectColumns, nonSNP;
+    private JRadioButton trioButton, ccButton, standardTDT, parenTDT;
+    private JButton browseAssocButton, browsePlinkMapButton, browsePhaseButton, browseSampleButton, browseLegendButton;
+    private NumberTextField maxComparisonDistField, missingCutoffField, chromStartField, chromEndField, rangeField;
+    private JLabel testFileLabel, mapLabel, downloadLabel;
+    private JComboBox chromChooser = new JComboBox(CHROM_NAMES);
+    private JComboBox loadChromChooser = new JComboBox(CHROM_NAMES);
+    private JComboBox plinkChromChooser = new JComboBox(CHROM_NAMES);
+    private JComboBox panelChooser = new JComboBox(PANEL_NAMES);
+    private JComboBox phaseChooser = new JComboBox(RELEASE_NAMES);
+    //TODO: Uncomment all the fastPHASE stuff once it's ready
+    //private JComboBox phaseFormatChooser = new JComboBox(PHASE_FORMATS);
+    private JComboBox geneCruiseChooser = new JComboBox(GENE_DATABASES);
+    private String chromChoice, panelChoice, phaseChoice, embed, selectCols;
+    private JPanel phaseTab, downloadTab, /*phaseFormatPanel,*/ downloadChooserPanel,
+            downloadPositionPanel, downloadBrowsePanel, geneCruiserPanel, phaseGzipPanel, phaseChromPanel;
+
+    JTabbedPane dataFormatPane = new JTabbedPane(JTabbedPane.LEFT);
+    static int currTab = 0;
+    private GridBagConstraints c;
+
+    public ReadDataDialog(String title, HaploView h){
+        super(h, title);
+
+        //Ped Tab Objects
+        pedFileField = new JTextField(20);
+        JButton browsePedFileButton = new JButton("Browse");
+        browsePedFileButton.setActionCommand(BROWSE_GENO);
+        browsePedFileButton.addActionListener(this);
+        pedInfoField = new JTextField(20);
+        pedInfoField.getDocument().addDocumentListener(this);
+        JButton browsePedInfoButton = new JButton("Browse");
+        browsePedInfoButton.setActionCommand(BROWSE_INFO);
+        browsePedInfoButton.addActionListener(this);
+        JPanel assocPanel = new JPanel();
+        doAssociation = new JCheckBox("Do association test");
+        doAssociation.setSelected(false);
+        doAssociation.setEnabled(false);
+        doAssociation.setActionCommand("association");
+        doAssociation.addActionListener(this);
+        xChrom = new JCheckBox("X Chromosome");
+        xChrom.setSelected(false);
+        xChrom.setActionCommand("xChrom");
+        xChrom.addActionListener(this);
+        assocPanel.add(xChrom);
+        assocPanel.add(doAssociation);
+        JPanel tdtOptsPanel = new JPanel();
+        trioButton = new JRadioButton("Family trio data", true);
+        trioButton.setEnabled(false);
+        trioButton.setActionCommand("tdt");
+        trioButton.addActionListener(this);
+        ccButton = new JRadioButton("Case/Control data");
+        ccButton.setEnabled(false);
+        ccButton.setActionCommand("ccButton");
+        ccButton.addActionListener(this);
+        ButtonGroup group = new ButtonGroup();
+        group.add(trioButton);
+        group.add(ccButton);
+        tdtOptsPanel.add(trioButton);
+        tdtOptsPanel.add(ccButton);
+        JPanel tdtTypePanel = new JPanel();
+        standardTDT = new JRadioButton("Standard TDT", true);
+        standardTDT.setEnabled(false);
+        parenTDT = new JRadioButton("ParenTDT", true);
+        parenTDT.setEnabled(false);
+        ButtonGroup tdtGroup = new ButtonGroup();
+        tdtGroup.add(standardTDT);
+        tdtGroup.add(parenTDT);
+        tdtTypePanel.add(standardTDT);
+        tdtTypePanel.add(parenTDT);
+        testFileField = new JTextField(20);
+        testFileField.setEnabled(false);
+        testFileField.setBackground(this.getBackground());
+        browseAssocButton = new JButton("Browse");
+        browseAssocButton.setActionCommand(BROWSE_ASSOC);
+        browseAssocButton.addActionListener(this);
+        browseAssocButton.setEnabled(false);
+        testFileLabel = new JLabel("Test list file (optional):");
+        testFileLabel.setEnabled(false);
+
+        //Layout the Ped Tab
+        JPanel pedTab = new JPanel(new GridBagLayout());
+        pedTab.setPreferredSize(new Dimension(375,200));
+        c = new GridBagConstraints();
+        c.anchor = GridBagConstraints.EAST;
+        c.insets = new Insets(5,5,5,5);
+        pedTab.add(new JLabel("Data File:"),c);
+        c.gridx = 1;
+        c.anchor = GridBagConstraints.CENTER;
+        pedTab.add(pedFileField,c);
+        c.gridx = 2;
+        c.insets = new Insets(0,10,0,0);
+        pedTab.add(browsePedFileButton,c);
+        c.gridy = 1;
+        c.gridx = 0;
+        c.anchor = GridBagConstraints.EAST;
+        c.insets = new Insets(5,5,5,5);
+        pedTab.add(new JLabel("Locus Information File:"),c);
+        c.gridx = 1;
+        c.anchor = GridBagConstraints.CENTER;
+        c.insets = new Insets(0,0,0,0);
+        pedTab.add(pedInfoField,c);
+        c.gridx = 2;
+        c.insets = new Insets(0,10,0,0);
+        pedTab.add(browsePedInfoButton,c);
+        c.gridy = 2;
+        c.gridx = 0;
+        c.gridwidth = 3;
+        c.anchor = GridBagConstraints.CENTER;
+        c.insets = new Insets(0,0,0,0);
+        pedTab.add(assocPanel,c);
+        c.gridy = 3;
+        pedTab.add(tdtOptsPanel,c);
+        c.gridy = 4;
+        pedTab.add(tdtTypePanel,c);
+        c.gridy = 5;
+        c.gridwidth = 1;
+        c.anchor = GridBagConstraints.EAST;
+        c.insets = new Insets(0,0,0,5);
+        pedTab.add(testFileLabel,c);
+        c.gridx = 1;
+        c.insets = new Insets(0,0,0,0);
+        pedTab.add(testFileField,c);
+        c.gridx = 2;
+        c.insets = new Insets(0,10,0,0);
+        pedTab.add(browseAssocButton,c);
+
+
+        //Haps Tab Objects
+        hapsFileField = new JTextField(20);
+        JButton browseHapsFileButton = new JButton("Browse");
+        browseHapsFileButton.setActionCommand(BROWSE_HAPS);
+        browseHapsFileButton.addActionListener(this);
+        hapsInfoField = new JTextField(20);
+        JButton browseHapsInfoButton = new JButton("Browse");
+        browseHapsInfoButton.setActionCommand(BROWSE_INFO);
+        browseHapsInfoButton.addActionListener(this);
+
+        //Layout the Haps Tab
+        JPanel hapsTab = new JPanel(new GridBagLayout());
+        c = new GridBagConstraints();
+        c.anchor = GridBagConstraints.EAST;
+        c.insets = new Insets(5,5,5,5);
+        hapsTab.add(new JLabel("Data File:"),c);
+        c.gridx = 1;
+        c.anchor = GridBagConstraints.CENTER;
+        hapsTab.add(hapsFileField,c);
+        c.gridx = 2;
+        c.insets = new Insets(0,10,0,0);
+        hapsTab.add(browseHapsFileButton,c);
+        c.gridy = 1;
+        c.gridx = 0;
+        c.anchor = GridBagConstraints.EAST;
+        c.insets = new Insets(5,5,5,5);
+        hapsTab.add(new JLabel("Locus Information File:"),c);
+        c.gridx = 1;
+        c.anchor = GridBagConstraints.CENTER;
+        c.insets = new Insets(0,0,0,0);
+        hapsTab.add(hapsInfoField,c);
+        c.gridx = 2;
+        c.insets = new Insets(0,10,0,0);
+        hapsTab.add(browseHapsInfoButton,c);
+
+
+        //HMP Tab Objects
+        hmpFileField = new JTextField(20);
+        JButton browseHmpButton = new JButton("Browse");
+        browseHmpButton.setActionCommand(BROWSE_HMP);
+        browseHmpButton.addActionListener(this);
+        doGB = new JCheckBox("Download and show HapMap info track (requires internet connection)");
+        doGB.setSelected(false);
+
+        //Layout the HMP Tab
+        JPanel hmpTab = new JPanel(new GridBagLayout());
+        c = new GridBagConstraints();
+        c.anchor = GridBagConstraints.EAST;
+        c.insets = new Insets(5,5,5,5);
+        c.weightx = 1;
+        hmpTab.add(new JLabel("Data File:"),c);
+        c.gridx = 1;
+        c.anchor = GridBagConstraints.CENTER;
+        c.weightx = 0;
+        hmpTab.add(hmpFileField,c);
+        c.gridx = 2;
+        c.anchor = GridBagConstraints.WEST;
+        c.insets = new Insets(0,10,0,0);
+        c.weightx = 1;
+        hmpTab.add(browseHmpButton,c);
+        c.gridy = 1;
+        c.gridx = 0;
+        c.gridwidth = 3;
+        c.anchor = GridBagConstraints.CENTER;
+        c.insets = new Insets(0,0,0,0);
+        c.weightx = 0;
+        hmpTab.add(doGB,c);
+
+
+        //Phased Tab Objects
+        /* phaseFormatPanel = new JPanel();
+        phaseFormatPanel.add(new JLabel("Format:"));
+        phaseFormatChooser.addActionListener(this);
+        phaseFormatPanel.add(phaseFormatChooser);*/
+        phaseFileField = new JTextField(20);
+        browsePhaseButton = new JButton("Browse");
+        browsePhaseButton.setActionCommand(BROWSE_PHASE);
+        browsePhaseButton.addActionListener(this);
+        phaseSampleField = new JTextField(20);
+        browseSampleButton = new JButton("Browse");
+        browseSampleButton.setActionCommand(BROWSE_SAMPLE);
+        browseSampleButton.addActionListener(this);
+        phaseLegendField = new JTextField(20);
+        browseLegendButton = new JButton("Browse");
+        browseLegendButton.setActionCommand(BROWSE_LEGEND);
+        browseLegendButton.addActionListener(this);
+        phaseGzipPanel = new JPanel();
+        gZip = new JCheckBox("Files are GZIP compressed", false);
+        gZip.setEnabled(true);
+        phaseGzipPanel.add(gZip);
+        phaseChromPanel = new JPanel();
+        phaseDoGB = new JCheckBox("Download and show HapMap info track");
+        phaseDoGB.setSelected(false);
+        phaseChromPanel.add(phaseDoGB);
+        phaseChromPanel.add(new JLabel("Chromosome:"));
+        loadChromChooser.setSelectedIndex(-1);
+        phaseChromPanel.add(loadChromChooser);
+
+        //Layout the Phased Tab
+        phaseTab = new JPanel(new GridBagLayout());
+        c = new GridBagConstraints();
+        c.insets = new Insets(5,5,5,5);
+        //c.anchor = GridBagConstraints.CENTER;
+        c.weightx = 0;
+        //c.gridwidth = 3;
+        //phaseTab.add(phaseFormatPanel,c);
+        c.gridwidth = 1;
+        c.anchor = GridBagConstraints.EAST;
+        c.gridy = 1;
+        phaseTab.add(new JLabel("Data File:"),c);
+        c.anchor = GridBagConstraints.CENTER;
+        c.gridx = 1;
+        c.insets = new Insets(0,0,0,0);
+        phaseTab.add(phaseFileField,c);
+        c.gridx = 2;
+        c.insets = new Insets(0,10,0,0);
+        c.anchor = GridBagConstraints.WEST;
+        phaseTab.add(browsePhaseButton,c);
+        c.gridx = 0;
+        c.gridy = 2;
+        c.anchor = GridBagConstraints.EAST;
+        c.insets = new Insets(5,5,5,5);
+        phaseTab.add(new JLabel("Sample File:"),c);
+        c.anchor = GridBagConstraints.CENTER;
+        c.gridx = 1;
+        phaseTab.add(phaseSampleField,c);
+        c.gridx = 2;
+        c.insets = new Insets(0,10,0,0);
+        c.anchor = GridBagConstraints.WEST;
+        phaseTab.add(browseSampleButton,c);
+        c.gridx = 0;
+        c.gridy = 3;
+        c.anchor = GridBagConstraints.EAST;
+        c.insets = new Insets(5,5,5,5);
+        phaseTab.add(new JLabel("Legend File:"),c);
+        c.anchor = GridBagConstraints.CENTER;
+        c.gridx = 1;
+        phaseTab.add(phaseLegendField,c);
+        c.gridx = 2;
+        c.insets = new Insets(0,10,0,0);
+        c.anchor = GridBagConstraints.WEST;
+        phaseTab.add(browseLegendButton,c);
+        c.gridx = 0;
+        c.gridy = 4;
+        c.anchor = GridBagConstraints.CENTER;
+        c.gridwidth = 3;
+        c.insets = new Insets(0,0,0,0);
+        phaseTab.add(phaseGzipPanel,c);
+        c.gridy = 5;
+        phaseTab.add(phaseChromPanel,c);
+
+
+        //Download Tab Objects
+        downloadChooserPanel = new JPanel();
+        downloadChooserPanel.add(new JLabel("Release:"));
+        downloadChooserPanel.add(phaseChooser);
+        phaseChooser.setSelectedIndex(1);
+        downloadChooserPanel.add(new JLabel("Chromosome:"));
+        chromChooser.setSelectedIndex(-1);
+        downloadChooserPanel.add(chromChooser);
+        downloadChooserPanel.add(new JLabel("Analysis Panel:"));
+        downloadChooserPanel.add(panelChooser);
+        downloadPositionPanel = new JPanel();
+        chromStartField = new NumberTextField("",6,false,false);
+        chromStartField.setEnabled(true);
+        chromEndField = new NumberTextField("",6,false,false);
+        chromEndField.setEnabled(true);
+        downloadPositionPanel.add(new JLabel("Start kb:"));
+        downloadPositionPanel.add(chromStartField);
+        downloadPositionPanel.add(new JLabel("End kb:"));
+        downloadPositionPanel.add(chromEndField);
+        downloadBrowsePanel = new JPanel();
+        downloadDoGB = new JCheckBox("Show HapMap info track");
+        downloadDoGB.setSelected(true);
+        downloadBrowsePanel.add(downloadDoGB);
+        geneCruiserPanel = new JPanel();
+        geneCruiserPanel.add(geneCruiseChooser);
+        geneCruiserPanel.add(new JLabel("ID:"));
+        geneCruiseField = new JTextField(10);
+        geneCruiserPanel.add(geneCruiseField);
+        geneCruiserPanel.add(new JLabel("+/-"));
+        rangeField = new NumberTextField("100",6,false,false);
+        geneCruiserPanel.add(rangeField);
+        geneCruiserPanel.add(new JLabel("kb"));
+        JButton geneCruiseButton = new JButton("Go");
+        geneCruiseButton.setActionCommand("GeneCruise");
+        geneCruiseButton.addActionListener(this);
+        geneCruiserPanel.add(geneCruiseButton);
+        geneCruiserPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.lightGray),
+                "GeneCruiser"));
+        ((TitledBorder)(geneCruiserPanel.getBorder())).setTitleColor(Color.black);
+        downloadLabel = new JLabel("*Phased HapMap downloads require an active internet connection");
+
+        //Layout the Download Tab
+        downloadTab = new JPanel(new GridBagLayout());
+        c = new GridBagConstraints();
+        c.insets = new Insets(0,0,5,0);
+        c.anchor = GridBagConstraints.CENTER;
+        downloadTab.add(downloadChooserPanel,c);
+        c.gridy = 1;
+        downloadTab.add(downloadPositionPanel,c);
+        c.gridy = 2;
+        downloadTab.add(downloadBrowsePanel,c);
+        c.gridy = 3;
+        c.weightx = 1;
+        downloadTab.add(geneCruiserPanel,c);
+        c.gridy = 4;
+        downloadTab.add(downloadLabel,c);
+
+
+        //Plink Tab Objects
+        plinkFileField = new JTextField(20);
+        JButton browsePlinkFileButton = new JButton("Browse");
+        browsePlinkFileButton.setActionCommand(BROWSE_WGA);
+        browsePlinkFileButton.addActionListener(this);
+        mapLabel = new JLabel("Map File:");
+        plinkMapField = new JTextField(20);
+        browsePlinkMapButton = new JButton("Browse");
+        browsePlinkMapButton.setActionCommand(BROWSE_MAP);
+        browsePlinkMapButton.addActionListener(this);
+        JPanel inputPanel = new JPanel();
+        embeddedMap = new JCheckBox("Integrated Map Info");
+        embeddedMap.addActionListener(this);
+        embeddedMap.setSelected(false);
+        inputPanel.add(embeddedMap);
+        nonSNP = new JCheckBox("Non-SNP");
+        nonSNP.addActionListener(this);
+        nonSNP.setSelected(false);
+        inputPanel.add(nonSNP);
+        JPanel plinkChromPanel = new JPanel();
+        plinkChrom = new JCheckBox("Only load results from Chromosome");
+        plinkChrom.addActionListener(this);
+        plinkChromPanel.add(plinkChrom);
+        plinkChromChooser.setSelectedIndex(-1);
+        plinkChromPanel.add(plinkChromChooser);
+        plinkChromChooser.setEnabled(false);
+        selectColumns = new JCheckBox("Select Columns");
+        plinkChromPanel.add(selectColumns);
+
+        //Layout the Plink Tab
+        JPanel plinkTab = new JPanel(new GridBagLayout());
+        c = new GridBagConstraints();
+        c.anchor = GridBagConstraints.EAST;
+        c.insets = new Insets(5,5,5,5);
+        plinkTab.add(new JLabel("Results File:"),c);
+        c.gridx = 1;
+        c.anchor = GridBagConstraints.CENTER;
+        plinkTab.add(plinkFileField,c);
+        c.gridx = 2;
+        c.insets = new Insets(0,10,0,0);
+        plinkTab.add(browsePlinkFileButton,c);
+        c.gridy = 1;
+        c.gridx = 0;
+        c.anchor = GridBagConstraints.EAST;
+        c.insets = new Insets(5,5,5,5);
+        plinkTab.add(mapLabel,c);
+        c.gridx = 1;
+        c.anchor = GridBagConstraints.CENTER;
+        c.insets = new Insets(0,0,0,0);
+        plinkTab.add(plinkMapField,c);
+        c.gridx = 2;
+        c.insets = new Insets(0,10,0,0);
+        plinkTab.add(browsePlinkMapButton,c);
+        c.gridy = 2;
+        c.gridx = 0;
+        c.gridwidth = 3;
+        c.insets = new Insets(0,0,0,0);
+        plinkTab.add(inputPanel,c);
+        c.gridy = 3;
+        plinkTab.add(plinkChromPanel,c);
+
+
+        //Add the tabs to the tabbed pane
+        dataFormatPane.setFont(new Font("Default",Font.BOLD,12));
+        dataFormatPane.addTab("Linkage Format",pedTab);
+        dataFormatPane.addTab("Haps Format",hapsTab);
+        dataFormatPane.addTab("HapMap Format",hmpTab);
+        //dataFormatPane.addTab("Phased Formats",phaseTab);
+        dataFormatPane.addTab("HapMap PHASE",phaseTab);
+        dataFormatPane.addTab("HapMap Download",downloadTab);
+        dataFormatPane.addTab("PLINK Format",plinkTab);
+        //Set the selected Tab so you go back to the tab you were previously in
+        dataFormatPane.setSelectedIndex(currTab);
+
+        //Bottom pane with the OK button
+        JButton okButton = new JButton("OK");
+        okButton.addActionListener(this);
+        this.getRootPane().setDefaultButton(okButton);
+        JButton cancelButton = new JButton("Cancel");
+        cancelButton.addActionListener(this);
+        JButton proxyButton = new JButton("Proxy Settings");
+        proxyButton.addActionListener(this);
+        JPanel choicePanel = new JPanel();
+        choicePanel.add(okButton);
+        choicePanel.add(cancelButton);
+        JPanel proxyPanel = new JPanel();
+        proxyPanel.add(proxyButton);
+
+
+        JPanel contents = new JPanel(new GridBagLayout());
+        c = new GridBagConstraints();
+        c.gridwidth = 3;
+        contents.add(dataFormatPane, c);
+
+        JPanel compDistPanel = new JPanel();
+        compDistPanel.add(new JLabel("Ignore pairwise comparisons of markers >"));
+        maxComparisonDistField = new NumberTextField(String.valueOf(Options.getMaxDistance()/1000),6, false,false);
+        compDistPanel.add(maxComparisonDistField);
+        compDistPanel.add(new JLabel("kb apart."));
+        c.gridy = 1;
+        contents.add(compDistPanel,c);
+
+        JPanel missingCutoffPanel = new JPanel();
+        missingCutoffField = new NumberTextField(String.valueOf(Options.getMissingThreshold()*100),3, false,false);
+        missingCutoffPanel.add(new JLabel("Exclude individuals with >"));
+        missingCutoffPanel.add(missingCutoffField);
+        missingCutoffPanel.add(new JLabel("% missing genotypes."));
+        c.gridy = 2;
+        contents.add(missingCutoffPanel,c);
+        c.gridy = 3;
+        contents.add(choicePanel,c);
+        c.gridx = 2;
+        c.anchor = GridBagConstraints.EAST;
+        contents.add(proxyPanel,c);
+
+        if (h.getPhasedSelection() != null){
+            if (((String)h.getPhasedSelection().get(0)).startsWith("16")){
+                phaseChooser.setSelectedIndex(0);
+            }else if (((String)h.getPhasedSelection().get(0)).startsWith("21")){
+                phaseChooser.setSelectedIndex(1);
+            }else{
+                phaseChooser.setSelectedIndex(2);
+            }
+
+            if (((String)h.getPhasedSelection().get(1)).equals("X")){
+                chromChooser.setSelectedIndex(22);
+            }else if (((String)h.getPhasedSelection().get(1)).equals("Y")){
+                chromChooser.setSelectedIndex(23);
+            }else{
+                chromChooser.setSelectedIndex(Integer.parseInt((String)h.getPhasedSelection().get(1))-1);
+            }
+
+            if (((String)h.getPhasedSelection().get(2)).equals("YRI")){
+                panelChooser.setSelectedIndex(1);
+            }else if (((String)h.getPhasedSelection().get(2)).equals("CHB+JPT")){
+                panelChooser.setSelectedIndex(2);
+            }
+
+            chromStartField.setText((String)h.getPhasedSelection().get(3));
+            chromEndField.setText((String)h.getPhasedSelection().get(4));
+        }
+
+        this.setContentPane(contents);
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+        this.setResizable(false);
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if (command.equals(BROWSE_GENO)){
+            browse(GENO_FILE);
+        }else if (command.equals(BROWSE_HAPS)){
+            browse(HAPS_FILE);
+        }else if (command.equals(BROWSE_HMP)){
+            browse(HMP_FILE);
+        }else if (command.equals(BROWSE_PHASE)){
+            browse(PHASEHMP_FILE);
+        }else if (command.equals(BROWSE_SAMPLE)){
+            browse(SAMPLEHMP_FILE);
+        }else if (command.equals(BROWSE_LEGEND)){
+            browse(LEGENDHMP_FILE);
+        }else if (command.equals(BROWSE_INFO)){
+            browse(INFO_FILE);
+        }else if (command.equals(BROWSE_ASSOC)){
+            browse(ASSOC_FILE);
+        }else if (command.equals(BROWSE_WGA)){
+            browse(PLINK_FILE);
+        }else if (command.equals(BROWSE_MAP)){
+            browse(MAP_FILE);
+        }
+        else if (command.equals("OK")){
+
+            //Set the Tab that was last accessed
+            currTab = dataFormatPane.getSelectedIndex();
+
+
+            //workaround for dumb Swing can't requestFocus until shown bug
+            //this one seems to throw a harmless exception in certain versions of the linux JRE
+            try{
+                SwingUtilities.invokeLater( new Runnable(){
+                    public void run()
+                    {
+                        pedFileField.requestFocus();
+                    }});
+            }catch (RuntimeException re){
+            }
+
+            if (currTab == 0){
+                fileType = PED_FILE;
+            }else if (currTab == 1){
+                fileType = HAPS_FILE;
+            }else if (currTab == 2){
+                fileType = HMP_FILE;
+            }else if (currTab == 3){
+                /* if (phaseFormatChooser.getSelectedIndex() == 0){
+                fileType = PHASEHMP_FILE;
+                }else if (phaseFormatChooser.getSelectedIndex() == 1){
+                    fileType = FASTPHASE_FILE;
+                }*/
+                fileType = PHASEHMP_FILE;
+            }else if (currTab == 4){
+                fileType = HMPDL_FILE;
+            }else if (currTab == 5){
+                fileType = PLINK_FILE;
+            }
+            HaploView caller = (HaploView)this.getParent();
+            if(missingCutoffField.getText().equals("")) {
+                Options.setMissingThreshold(1);
+            } else {
+                double missingThreshold = (double)(Integer.parseInt(missingCutoffField.getText())) / 100;
+                if(missingThreshold > 1) {
+                    JOptionPane.showMessageDialog(caller,
+                            "Missing cutoff must be between 0 and 100",
+                            "Invalid value",
+                            JOptionPane.ERROR_MESSAGE);
+                    return;
+                }
+                Options.setMissingThreshold(missingThreshold);
+            }
+
+            if (doAssociation.isSelected() && fileType == PED_FILE){
+                if (trioButton.isSelected()){
+                    Options.setAssocTest(ASSOC_TRIO);
+                    if(standardTDT.isSelected()){
+                        Options.setTdtType(TDT_STD);
+                    }else if(parenTDT.isSelected()) {
+                        Options.setTdtType(TDT_PAREN);
+                    }
+                } else {
+                    Options.setAssocTest(ASSOC_CC);
+                }
+            }else{
+                Options.setAssocTest(ASSOC_NONE);
+            }
+
+            if (xChrom.isSelected() && fileType == PED_FILE){
+                Chromosome.setDataChrom("chrx");
+            }else {
+                Chromosome.setDataChrom("none");
+            }
+
+
+            if (doGB.isSelected() && fileType == HMP_FILE){
+                Options.setShowGBrowse(true);
+            }else{
+                Options.setShowGBrowse(false);
+            }
+            Options.setgBrowseLeft(0);
+            Options.setgBrowseRight(0);
+
+            if (maxComparisonDistField.getText().equals("")){
+                Options.setMaxDistance(0);
+            }else{
+                Options.setMaxDistance(Integer.parseInt(maxComparisonDistField.getText()));
+            }
+
+            if (fileType == PHASEHMP_FILE /*|| fileType == FASTPHASE_FILE*/){
+                if (gZip.isSelected()){
+                    Options.setGzip(true);
+                }else{
+                    Options.setGzip(false);
+                }
+                if (phaseDoGB.isSelected()){
+                    Options.setShowGBrowse(true);
+                    if (loadChromChooser.getSelectedIndex() == -1){
+                        JOptionPane.showMessageDialog(caller,
+                                "HapMap Info Track download requires a chromosome.",
+                                "Invalid value",
+                                JOptionPane.ERROR_MESSAGE);
+                        return;
+                    }
+                }else{
+                    Options.setShowGBrowse(false);
+                }
+                if (loadChromChooser.getSelectedIndex() == -1){
+                    chromChoice = "";
+                }else{
+                    chromChoice = (String)loadChromChooser.getSelectedItem();
+                }
+            }
+            if (fileType == HMPDL_FILE){
+
+                if (downloadDoGB.isSelected()){
+                    Options.setShowGBrowse(true);
+                }else{
+                    Options.setShowGBrowse(false);
+                }
+                if (chromChooser.getSelectedIndex() == -1){
+                    JOptionPane.showMessageDialog(caller,
+                            "Please select a chromosome.",
+                            "Invalid value",
+                            JOptionPane.ERROR_MESSAGE);
+                    return;
+                }
+                if (chromStartField.getText().equals("")){
+                    JOptionPane.showMessageDialog(caller,
+                            "Please enter a starting value.",
+                            "Invalid value",
+                            JOptionPane.ERROR_MESSAGE);
+                    return;
+                }
+                if (chromEndField.getText().equals("")){
+                    JOptionPane.showMessageDialog(caller,
+                            "Please enter an ending value.",
+                            "Invalid value",
+                            JOptionPane.ERROR_MESSAGE);
+                    return;
+                }
+                if (Integer.parseInt(chromStartField.getText()) >= Integer.parseInt(chromEndField.getText())){
+                    JOptionPane.showMessageDialog(caller,
+                            "End position must be larger then start position.",
+                            "Invalid value",
+                            JOptionPane.ERROR_MESSAGE);
+                    return;
+                }
+                chromChoice = (String)chromChooser.getSelectedItem();
+                panelChoice = (String) panelChooser.getSelectedItem();
+                phaseChoice = (String)phaseChooser.getSelectedItem();
+
+            }
+
+            if (fileType == PLINK_FILE){
+                if (embeddedMap.isSelected()){
+                    embed = "E";
+                }
+                Options.setSNPBased(!nonSNP.isSelected());
+                if (plinkChrom.isSelected()){
+                    if (plinkChromChooser.getSelectedIndex() == -1){
+                        JOptionPane.showMessageDialog(caller,
+                                "Please select a chromosome to load.",
+                                "Invalid value",
+                                JOptionPane.ERROR_MESSAGE);
+                        return;
+                    }
+                    chromChoice = new Integer((plinkChromChooser.getSelectedIndex()+1)).toString();
+                }else{
+                    chromChoice = null;
+                }
+                if (selectColumns.isSelected()){
+                    selectCols = "Y";
+                }
+            }
+
+            String[] returnStrings;
+            if (fileType == HAPS_FILE){
+                returnStrings = new String[]{hapsFileField.getText(), hapsInfoField.getText(),null};
+                if (returnStrings[1].equals("")) returnStrings[1] = null;
+            }else if (fileType == HMP_FILE){
+                returnStrings = new String[]{hmpFileField.getText(),null,null};
+            }else if (fileType == PHASEHMP_FILE){
+                returnStrings = new String[]{phaseFileField.getText(), phaseSampleField.getText(), phaseLegendField.getText(), chromChoice};
+            }/*else if (fileType == FASTPHASE_FILE){
+                returnStrings = new String[]{phaseFileField.getText(), phaseSampleField.getText(), null, chromChoice};
+            }*/else if (fileType == HMPDL_FILE){
+                returnStrings = new String[]{"Chr" + chromChoice + ":" + panelChoice + ":" + chromStartField.getText() + ".." +
+                        chromEndField.getText(), panelChoice, chromStartField.getText(), chromEndField.getText(), chromChoice, phaseChoice, "txt"};
+            }else if (fileType == PLINK_FILE){
+                returnStrings = new String[]{plinkFileField.getText(), plinkMapField.getText(),null,embed,null,chromChoice,selectCols};
+            }
+            else{
+                returnStrings = new String[]{pedFileField.getText(), pedInfoField.getText(), testFileField.getText()};
+                if (returnStrings[1].equals("")) returnStrings[1] = null;
+                if (returnStrings[2].equals("") || !doAssociation.isSelected()) returnStrings[2] = null;
+            }
+
+
+            //if a dataset was previously loaded during this session, discard the display panes for it.
+            caller.clearDisplays();
+            this.dispose();
+            if (fileType != PLINK_FILE){
+                caller.readGenotypes(returnStrings, fileType);
+            }else{
+                caller.readWGA(returnStrings);
+            }
+        }else if (command.equals("Cancel")){
+            this.dispose();
+        }else if (command.equals("association")){
+            switchAssoc(doAssociation.isSelected());
+        }else if(command.equals("tdt")){
+            standardTDT.setEnabled(true);
+            if (!xChrom.isSelected()){
+                parenTDT.setEnabled(true);
+            }
+        }else if(command.equals("ccButton")){
+            standardTDT.setEnabled(false);
+            parenTDT.setEnabled(false);
+        }else if (command.equals("xChrom")){
+            if (xChrom.isSelected()){
+                parenTDT.setEnabled(false);
+                standardTDT.setSelected(true);
+            }else if (standardTDT.isEnabled()){
+                parenTDT.setEnabled(true);
+            }
+        }else if (command.equals("Integrated Map Info")){
+            if (embeddedMap.isSelected()){
+                embeddedMap.setSelected(true);
+                mapLabel.setEnabled(false);
+                plinkMapField.setEnabled(false);
+                browsePlinkMapButton.setEnabled(false);
+            }else{
+                embeddedMap.setSelected(false);
+                mapLabel.setEnabled(true);
+                plinkMapField.setEnabled(true);
+                browsePlinkMapButton.setEnabled(true);
+            }
+        }else if (command.equals("Non-SNP")){
+            if (nonSNP.isSelected()){
+                nonSNP.setSelected(true);
+                mapLabel.setEnabled(false);
+                plinkMapField.setEnabled(false);
+                plinkMapField.setText("");
+                browsePlinkMapButton.setEnabled(false);
+                plinkChrom.setSelected(false);
+                plinkChrom.setEnabled(false);
+                plinkChromChooser.setSelectedIndex(-1);
+                plinkChromChooser.setEnabled(false);
+                embeddedMap.setSelected(false);
+                embeddedMap.setEnabled(false);
+            }else{
+                nonSNP.setSelected(false);
+                mapLabel.setEnabled(true);
+                plinkMapField.setEnabled(true);
+                browsePlinkMapButton.setEnabled(true);
+                plinkChrom.setEnabled(true);
+                embeddedMap.setEnabled(true);
+            }
+        }else if (command.equals("Only load results from Chromosome")){
+            if (plinkChrom.isSelected()){
+                plinkChrom.setSelected(true);
+                plinkChromChooser.setEnabled(true);
+            }else{
+                plinkChrom.setSelected(false);
+                plinkChromChooser.setEnabled(false);
+            }
+        }else if (command.equals("Proxy Settings")){
+            ProxyDialog pd = new ProxyDialog(this,"Proxy Settings");
+            pd.pack();
+            pd.setVisible(true);
+        }else if (command.equals("GeneCruise")){
+            setCursor(new Cursor(Cursor.WAIT_CURSOR));
+            if (rangeField.getText().length() < 1){
+                rangeField.setText("100");
+            }
+            GeneCruiser gncr = new GeneCruiser();
+            int[] data = new int[0];
+            try {
+                data = gncr.getData(geneCruiseChooser.getSelectedIndex(),geneCruiseField.getText());
+                chromChooser.setSelectedIndex(data[0]-1);
+                chromStartField.setText(String.valueOf((data[1]/1000) - Integer.parseInt(rangeField.getText())));
+                chromEndField.setText(String.valueOf((data[2]/1000) + Integer.parseInt(rangeField.getText())));
+            } catch (HaploViewException hve) {
+                JOptionPane.showMessageDialog(this.getParent(),
+                        hve.getMessage(),
+                        "GeneCruiser Error",
+                        JOptionPane.ERROR_MESSAGE);
+            }
+            setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+        }/*else if (e.getSource() instanceof JComboBox && dataFormatPane.getSelectedIndex() == 3){
+            if (phaseFormatChooser.getSelectedIndex() == 0){ //HapMap PHASE
+                phaseTab.removeAll();
+                c = new GridBagConstraints();
+                c.insets = new Insets(5,5,5,5);
+                c.anchor = GridBagConstraints.CENTER;
+                c.weightx = 0;
+                c.gridwidth = 3;
+                phaseTab.add(phaseFormatPanel,c);
+                c.gridwidth = 1;
+                c.anchor = GridBagConstraints.EAST;
+                c.gridy = 1;
+                phaseTab.add(new JLabel("Data File:"),c);
+                c.anchor = GridBagConstraints.CENTER;
+                c.gridx = 1;
+                c.insets = new Insets(0,0,0,0);
+                phaseTab.add(phaseFileField,c);
+                c.gridx = 2;
+                c.insets = new Insets(0,10,0,0);
+                c.anchor = GridBagConstraints.WEST;
+                phaseTab.add(browsePhaseButton,c);
+                c.gridx = 0;
+                c.gridy = 2;
+                c.anchor = GridBagConstraints.EAST;
+                c.insets = new Insets(5,5,5,5);
+                phaseTab.add(new JLabel("Sample File:"),c);
+                c.anchor = GridBagConstraints.CENTER;
+                c.gridx = 1;
+                phaseTab.add(phaseSampleField,c);
+                c.gridx = 2;
+                c.insets = new Insets(0,10,0,0);
+                c.anchor = GridBagConstraints.WEST;
+                phaseTab.add(browseSampleButton,c);
+                c.gridx = 0;
+                c.gridy = 3;
+                c.anchor = GridBagConstraints.EAST;
+                c.insets = new Insets(5,5,5,5);
+                phaseTab.add(new JLabel("Legend File:"),c);
+                c.anchor = GridBagConstraints.CENTER;
+                c.gridx = 1;
+                phaseTab.add(phaseLegendField,c);
+                c.gridx = 2;
+                c.insets = new Insets(0,10,0,0);
+                c.anchor = GridBagConstraints.WEST;
+                phaseTab.add(browseLegendButton,c);
+                c.gridx = 0;
+                c.gridy = 4;
+                c.anchor = GridBagConstraints.CENTER;
+                c.gridwidth = 3;
+                c.insets = new Insets(0,0,0,0);
+                phaseTab.add(phaseGzipPanel,c);
+                c.gridy = 5;
+                phaseTab.add(phaseChromPanel,c);
+                phaseFormatChooser.requestFocus();
+                dataFormatPane.repaint();
+            }else if (phaseFormatChooser.getSelectedIndex() == 1){ //fastPHASE
+                phaseTab.removeAll();
+                c = new GridBagConstraints();
+                c.insets = new Insets(5,5,5,5);
+                c.anchor = GridBagConstraints.CENTER;
+                c.weightx = 0;
+                c.gridwidth = 3;
+                phaseTab.add(phaseFormatPanel,c);
+                c.gridwidth = 1;
+                c.anchor = GridBagConstraints.EAST;
+                c.gridy = 1;
+                phaseTab.add(new JLabel("Data File:"),c);
+                c.anchor = GridBagConstraints.CENTER;
+                c.gridx = 1;
+                c.insets = new Insets(0,0,0,0);
+                phaseTab.add(phaseFileField,c);
+                c.gridx = 2;
+                c.insets = new Insets(0,10,0,0);
+                c.anchor = GridBagConstraints.WEST;
+                phaseTab.add(browsePhaseButton,c);
+                c.gridx = 0;
+                c.gridy = 2;
+                c.anchor = GridBagConstraints.EAST;
+                c.insets = new Insets(5,5,5,5);
+                phaseTab.add(new JLabel("Info File:"),c);
+                c.anchor = GridBagConstraints.CENTER;
+                c.gridx = 1;
+                phaseTab.add(phaseSampleField,c);
+                c.gridx = 2;
+                c.insets = new Insets(0,10,0,0);
+                c.anchor = GridBagConstraints.WEST;
+                phaseTab.add(browseSampleButton,c);
+                c.gridx = 0;
+                c.gridy = 3;
+                c.anchor = GridBagConstraints.CENTER;
+                c.gridwidth = 3;
+                c.insets = new Insets(0,0,0,0);
+                phaseTab.add(phaseGzipPanel,c);
+                c.gridy = 4;
+                phaseTab.add(phaseChromPanel,c);
+                phaseFormatChooser.requestFocus();
+                dataFormatPane.repaint();
+            }
+        }*/
+    }
+
+    void browse(int browseType){
+        String name;
+        String markerInfoName = "";
+        String mapFileName = "";
+        HaploView.fc.setSelectedFile(new File(""));
+        int returned = HaploView.fc.showOpenDialog(this);
+        if (returned != JFileChooser.APPROVE_OPTION) return;
+        File file = HaploView.fc.getSelectedFile();
+
+        if (browseType == GENO_FILE){
+            name = file.getName();
+            pedFileField.setText(file.getParent()+File.separator+name);
+
+            if(pedInfoField.getText().equals("")){
+                //baseName should be everything but the final ".XXX" extension
+                StringTokenizer st = new StringTokenizer(name,".");
+                String baseName = st.nextToken();
+                int numPieces = st.countTokens()-1;
+                for (int i = 0; i < numPieces; i++){
+                    baseName = baseName.concat(".").concat(st.nextToken());
+                }
+
+                //check for info file for original file sample.ped
+                //either sample.ped.info or sample.info
+                File maybeMarkers1 = new File(file.getParent(), name + MARKER_DATA_EXT);
+                File maybeMarkers2 = new File(file.getParent(), baseName + MARKER_DATA_EXT);
+                if (maybeMarkers1.exists()){
+                    markerInfoName = maybeMarkers1.getName();
+                }else if (maybeMarkers2.exists()){
+                    markerInfoName = maybeMarkers2.getName();
+                }else{
+                    return;
+                }
+                pedInfoField.setText(file.getParent()+File.separator+markerInfoName);
+            }
+        }else if (browseType == HAPS_FILE){
+            name = file.getName();
+            hapsFileField.setText(file.getParent()+File.separator+name);
+
+            if(hapsInfoField.getText().equals("")){
+                //baseName should be everything but the final ".XXX" extension
+                StringTokenizer st = new StringTokenizer(name,".");
+                String baseName = st.nextToken();
+                int numPieces = st.countTokens()-1;
+                for (int i = 0; i < numPieces; i++){
+                    baseName = baseName.concat(".").concat(st.nextToken());
+                }
+
+                //check for info file for original file sample.haps
+                //either sample.haps.info or sample.info
+                File maybeMarkers1 = new File(file.getParent(), name + MARKER_DATA_EXT);
+                File maybeMarkers2 = new File(file.getParent(), baseName + MARKER_DATA_EXT);
+                if (maybeMarkers1.exists()){
+                    markerInfoName = maybeMarkers1.getName();
+                }else if (maybeMarkers2.exists()){
+                    markerInfoName = maybeMarkers2.getName();
+                }else{
+                    return;
+                }
+                hapsInfoField.setText(file.getParent()+File.separator+markerInfoName);
+            }
+        }else if (browseType == HMP_FILE){
+            name = file.getName();
+            hmpFileField.setText(file.getParent()+File.separator+name);
+        }else if (browseType == PHASEHMP_FILE){
+            name = file.getName();
+            phaseFileField.setText(file.getParent()+File.separator+name);
+        }else if (browseType == SAMPLEHMP_FILE){
+            name = file.getName();
+            phaseSampleField.setText(file.getParent()+File.separator+name);
+        }else if (browseType == LEGENDHMP_FILE){
+            name = file.getName();
+            phaseLegendField.setText(file.getParent()+File.separator+name);
+        }else if (browseType==INFO_FILE){
+            markerInfoName = file.getName();
+            if (dataFormatPane.getSelectedIndex() == 1){
+                hapsInfoField.setText(file.getParent()+File.separator+markerInfoName);
+            }else{
+                pedInfoField.setText(file.getParent()+File.separator+markerInfoName);
+            }
+        }else if (browseType == ASSOC_FILE){
+            testFileField.setText(file.getParent() + File.separator + file.getName());
+        }else if (browseType == PLINK_FILE){
+            name = file.getName();
+            plinkFileField.setText(file.getParent()+File.separator+name);
+
+            if(plinkMapField.getText().equals("") && !nonSNP.isSelected()){
+                //baseName should be everything but the final ".XXX" extension
+                StringTokenizer st = new StringTokenizer(name,".");
+                String baseName = st.nextToken();
+                int numPieces = st.countTokens()-1;
+                for (int i = 0; i < numPieces; i++){
+                    baseName = baseName.concat(".").concat(st.nextToken());
+                }
+
+                //check for map file for original file
+                //either .map or .bim extensions
+                File maybeMap1 = new File(file.getParent(), name + MAP_FILE_EXT);
+                File maybeMap2 = new File(file.getParent(), baseName + MAP_FILE_EXT);
+                File maybeMap3 = new File(file.getParent(), name + BIM_FILE_EXT);
+                File maybeMap4 = new File(file.getParent(), baseName + BIM_FILE_EXT);
+                if (maybeMap1.exists()){
+                    mapFileName = maybeMap1.getName();
+                }else if (maybeMap2.exists()){
+                    mapFileName = maybeMap2.getName();
+                }else if (maybeMap3.exists()){
+                    mapFileName = maybeMap3.getName();
+                }else if (maybeMap4.exists()){
+                    mapFileName = maybeMap4.getName();
+                }else{
+                    return;
+                }
+                plinkMapField.setText(file.getParent()+File.separator+mapFileName);
+            }
+        }else if (browseType == MAP_FILE){
+            name = file.getName();
+            plinkMapField.setText(file.getParent()+File.separator+name);
+        }
+    }
+
+    public void insertUpdate(DocumentEvent e) {
+        checkInfo(e);
+    }
+
+    public void removeUpdate(DocumentEvent e) {
+        checkInfo(e);
+    }
+
+    public void changedUpdate(DocumentEvent e) {
+        //not fired by plain text components
+    }
+
+    private void switchAssoc(boolean b){
+        if(b){
+            doAssociation.setEnabled(true);
+            trioButton.setEnabled(true);
+            ccButton.setEnabled(true);
+            browseAssocButton.setEnabled(true);
+            testFileField.setEnabled(true);
+            testFileField.setBackground(Color.white);
+            testFileLabel.setEnabled(true);
+            if (trioButton.isSelected()){
+                standardTDT.setEnabled(true);
+                if (!xChrom.isSelected()){
+                    parenTDT.setEnabled(true);
+                }
+            }
+        }else{
+            doAssociation.setSelected(false);
+            trioButton.setEnabled(false);
+            ccButton.setEnabled(false);
+            browseAssocButton.setEnabled(false);
+            testFileField.setEnabled(false);
+            testFileField.setBackground(this.getBackground());
+            testFileLabel.setEnabled(false);
+            standardTDT.setEnabled(false);
+            parenTDT.setEnabled(false);
+        }
+    }
+
+    private void checkInfo(DocumentEvent e){
+        //the text in the info field has changed. if it is empty, disable assoc testing
+        if (pedInfoField.getText().equals("")){
+            switchAssoc(false);
+            doAssociation.setEnabled(false);
+        }else{
+            doAssociation.setEnabled(true);
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/RegionDialog.java b/edu/mit/wi/haploview/RegionDialog.java
new file mode 100755
index 0000000..405b39a
--- /dev/null
+++ b/edu/mit/wi/haploview/RegionDialog.java
@@ -0,0 +1,134 @@
+package edu.mit.wi.haploview;
+
+
+import javax.swing.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.util.Vector;
+
+
+public class RegionDialog extends JDialog implements ActionListener, Constants {
+    static final long serialVersionUID = 8970225298794816733L;
+    private HaploView hv;
+
+    private JComboBox panelChooser, phaseChooser;
+    private JCheckBox gBrowse, annotate;
+    private NumberTextField rangeInput;
+    private String chrom, marker;
+    private long markerPosition;
+    private PlinkResultsPanel prp;
+
+    public RegionDialog (HaploView h, String chr, String mark, PlinkResultsPanel prp, long position, String title) {
+        super(h,title);
+
+        hv = h;
+        chrom = chr;
+        markerPosition = position;
+        marker = mark;
+        this.prp = prp;
+
+        JPanel contents = new JPanel();
+        contents.setLayout(new BoxLayout(contents,BoxLayout.Y_AXIS));
+
+        JPanel chooserPanel = new JPanel();
+        chooserPanel.add(new JLabel("Release:"));
+        phaseChooser = new JComboBox(RELEASE_NAMES);
+        chooserPanel.add(phaseChooser);
+        phaseChooser.setSelectedIndex(2);
+        chooserPanel.add(new JLabel("Chr"+chr));
+        panelChooser = new JComboBox(PANEL_NAMES);
+        chooserPanel.add(new JLabel("Analysis Panel:"));
+        chooserPanel.add(panelChooser);
+        chooserPanel.add(new JLabel("Position: " + Long.toString(position / 1000)));
+        chooserPanel.add(new JLabel("+/-"));
+        rangeInput = new NumberTextField("100",6,false,false);
+        chooserPanel.add(rangeInput);
+        chooserPanel.add(new JLabel("kb"));
+
+        JPanel gBrowsePanel = new JPanel();
+        annotate = new JCheckBox("Annotate LD Plot?");
+        annotate.setSelected(true);
+        gBrowsePanel.add(annotate);
+        gBrowse = new JCheckBox("Show HapMap info track?");
+        gBrowse.setSelected(true);
+        gBrowsePanel.add(gBrowse);
+
+        JPanel choicePanel = new JPanel();
+        JButton goButton = new JButton("Go");
+        goButton.addActionListener(this);
+        this.getRootPane().setDefaultButton(goButton);
+        choicePanel.add(goButton);
+        JButton cancelButton = new JButton("Cancel");
+        cancelButton.addActionListener(this);
+        choicePanel.add(cancelButton);
+
+        contents.add(chooserPanel);
+        contents.add(gBrowsePanel);
+        contents.add(choicePanel);
+        setContentPane(contents);
+
+        this.getRootPane().setDefaultButton(goButton);
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if(command.equals("Cancel")) {
+            this.dispose();
+        }
+        if (command.equals("Go")){
+            if(rangeInput.getText().equals("")){
+                JOptionPane.showMessageDialog(this,
+                        "Please enter a range",
+                        "Invalid value",
+                        JOptionPane.ERROR_MESSAGE);
+                return;
+            }
+            String panel = (String) panelChooser.getSelectedItem();
+            int range = Integer.parseInt(rangeInput.getText());
+            long start = (markerPosition/1000)-range;
+            if (start < 0){
+                start = 0;
+            }
+            long end = (markerPosition/1000)+range;
+            String gotoStart = Long.toString(start);
+            String gotoEnd = Long.toString(end);
+            String phase = (String)phaseChooser.getSelectedItem();
+            prp.setChosenMarker(marker);
+
+            if (gBrowse.isSelected()){
+                Options.setShowGBrowse(true);
+            }
+
+            String[] returnStrings;
+            returnStrings = new String[]{"Chr" + chrom + ":" + panel + ":" + gotoStart + ".." +
+                    gotoEnd, panel, gotoStart, gotoEnd, chrom, phase, "txt"};
+            this.dispose();
+            hv.readGenotypes(returnStrings, HMPDL_FILE);
+            Vector chipSNPs = new Vector(prp.getSNPs());
+            if (Chromosome.getUnfilteredSize() > 0){
+                if (annotate.isSelected()){
+                    for (int i = 0; i < Chromosome.getSize(); i++){
+                        if (chipSNPs.contains(Chromosome.getMarker(i).getName())){
+                            Vector extras = new Vector();
+                            for (int j = 1; j < prp.getOriginalColumns().size(); j++){
+                                extras.add(prp.getOriginalColumns().get(j) + ": " + String.valueOf(prp.getValueAt(chipSNPs.indexOf(Chromosome.getMarker(i).getName()),j+2)));
+                            }
+                            Chromosome.getMarker(i).setExtra(extras);
+                        }
+                    }
+                }else{
+                    for (int i = 0; i < Chromosome.getSize(); i++){
+                        if (chipSNPs.contains(Chromosome.getMarker(i).getName())){
+                            Vector plink = new Vector();
+                            plink.add("PLINK");
+                            Chromosome.getMarker(i).setExtra(plink);
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/SNP.java b/edu/mit/wi/haploview/SNP.java
new file mode 100755
index 0000000..a1ea65c
--- /dev/null
+++ b/edu/mit/wi/haploview/SNP.java
@@ -0,0 +1,124 @@
+package edu.mit.wi.haploview;
+
+import java.util.Vector;
+
+public class SNP implements Comparable{
+
+    private String name;
+    private long position;
+    private double MAF;
+    private Vector extra;
+    private byte minor, major;
+    private int dup;
+
+    SNP(String n, long p, double m, byte a1, byte a2){
+        name = n;
+        position = p;
+        MAF = m;
+        major = a1;
+        minor = a2;
+    }
+
+    SNP(String n, long p, double m, byte a1, byte a2, String e){
+        name = n;
+        position = p;
+        MAF = m;
+        major = a1;
+        minor = a2;
+        if (e != null){
+            extra = new Vector();
+            extra.add(e);
+        }
+    }
+
+    public String getDisplayName(){
+        if (name != null){
+            return name;
+        }else{
+            return "Marker " + position;
+        }
+    }
+
+    public String getName(){
+        return name;
+    }
+
+    public long getPosition(){
+        return position;
+    }
+
+    public double getMAF(){
+        return MAF;
+    }
+
+    public Vector getExtra(){
+        return extra;
+    }
+
+    public void setExtra(Vector extra) {
+        this.extra = extra;
+    }
+
+    public byte getMinor(){
+        return minor;
+    }
+
+    public byte getMajor(){
+        return major;
+    }
+
+    public void setDup(int i) {
+        //0 for non dup
+        //1 for "used" dup (has higher geno% than twin)
+        //2 for "unused" dup
+        dup = i;
+    }
+
+    public int getDupStatus(){
+        return dup;
+    }
+
+    public boolean getStrandIssue(){
+        return (major == 4 && minor == 1) || (major == 1 && minor == 4) || (major == 3 && minor == 2) || (major == 2 && minor == 3);
+    }
+
+    public int compareTo(Object o) {
+        SNP s = (SNP)o;
+        if(this.equals(s)) {
+            return 0;
+        } else if(this.position == s.position) {
+            return name.compareTo(s.name);
+        } else {
+            return this.position > s.position ? 1 : -1;
+        }
+    }
+
+    public boolean equals(Object o) {
+        if(this == o) {
+            return true;
+        }
+        if(o instanceof SNP) {
+            SNP s = (SNP)o;
+            if (name != null){
+                if(this.name.equals(s.name) && this.position == s.position) {
+                    return true;
+                }
+            }else{
+                if (this.position == s.position){
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public int hashCode() {
+        //uses idea from Long hashcode to hash position
+        if (name != null){
+            return (name.hashCode() + (int)(position ^ (position >>> 32)));
+        }else{
+            //in this case all names are null and positions are unique integers from 1..N
+            return (int)position;
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/StatFunctions.java b/edu/mit/wi/haploview/StatFunctions.java
new file mode 100755
index 0000000..68ee727
--- /dev/null
+++ b/edu/mit/wi/haploview/StatFunctions.java
@@ -0,0 +1,295 @@
+package edu.mit.wi.haploview;
+
+/** * @(#)StatFunctions.java * * Copyright (c) 2000 by Sundar Dorai-Raj
+ * * @author Sundar Dorai-Raj
+ * * Email: sdoraira at vt.edu
+ * * 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,
+ * * provided that any use properly credits the author.
+ * * 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 at http://www.gnu.org * * */
+
+
+public class StatFunctions {
+
+
+    public static double qnorm(double p,boolean upper) {
+        /* Reference:
+       J. D. Beasley and S. G. Springer
+       Algorithm AS 111: "The Percentage Points of the Normal Distribution"
+       Applied Statistics
+    */
+        if(p<0 || p>1)
+            throw new IllegalArgumentException("Illegal argument "+p+" for qnorm(p).");
+        double split=0.42,
+                a0=  2.50662823884,
+                a1=-18.61500062529,
+                a2= 41.39119773534,
+                a3=-25.44106049637,
+                b1= -8.47351093090,
+                b2= 23.08336743743,
+                b3=-21.06224101826,
+                b4=  3.13082909833,
+                c0= -2.78718931138,
+                c1= -2.29796479134,
+                c2=  4.85014127135,
+                c3=  2.32121276858,
+                d1=  3.54388924762,
+                d2=  1.63706781897,
+                q=p-0.5;
+        double r,ppnd;
+        if(Math.abs(q)<=split) {
+            r=q*q;
+            ppnd=q*(((a3*r+a2)*r+a1)*r+a0)/((((b4*r+b3)*r+b2)*r+b1)*r+1);
+        }
+        else {
+            r=p;
+            if(q>0) r=1-p;
+            if(r>0) {
+                r=Math.sqrt(-Math.log(r));
+                ppnd=(((c3*r+c2)*r+c1)*r+c0)/((d2*r+d1)*r+1);
+                if(q<0) ppnd=-ppnd;
+            }
+            else {
+                ppnd=0;
+            }
+        }
+        if(upper) ppnd=1-ppnd;
+        return(ppnd);
+    }
+
+    public static double qnorm(double p,boolean upper,double mu,double sigma2) {
+        return(qnorm(p,upper)*Math.sqrt(sigma2)+mu);
+    }
+
+    public static double pnorm(double z,boolean upper) {
+        /* Reference:
+       I. D. Hill
+       Algorithm AS 66: "The Normal Integral"
+       Applied Statistics
+    */
+        double ltone=7.0,
+                utzero=18.66,
+                con=1.28,
+                a1 = 0.398942280444,
+                a2 = 0.399903438504,
+                a3 = 5.75885480458,
+                a4 =29.8213557808,
+                a5 = 2.62433121679,
+                a6 =48.6959930692,
+                a7 = 5.92885724438,
+                b1 = 0.398942280385,
+                b2 = 3.8052e-8,
+                b3 = 1.00000615302,
+                b4 = 3.98064794e-4,
+                b5 = 1.986153813664,
+                b6 = 0.151679116635,
+                b7 = 5.29330324926,
+                b8 = 4.8385912808,
+                b9 =15.1508972451,
+                b10= 0.742380924027,
+                b11=30.789933034,
+                b12= 3.99019417011;
+        double y,alnorm;
+
+        if(z<0) {
+            upper=!upper;
+            z=-z;
+        }
+        if(z<=ltone || upper && z<=utzero) {
+            y=0.5*z*z;
+            if(z>con) {
+                alnorm=b1*Math.exp(-y)/(z-b2+b3/(z+b4+b5/(z-b6+b7/(z+b8-b9/(z+b10+b11/(z+b12))))));
+            }
+            else {
+                alnorm=0.5-z*(a1-a2*y/(y+a3-a4/(y+a5+a6/(y+a7))));
+            }
+        }
+        else {
+            alnorm=0;
+        }
+        if(!upper) alnorm=1-alnorm;
+        return(alnorm);
+    }
+
+    public static double pnorm(double x,boolean upper,double mu,double sigma2) {
+        return(pnorm((x-mu)/Math.sqrt(sigma2),upper));
+    }
+
+    public static double qt(double p,double ndf,boolean lower_tail) {
+        // Algorithm 396: Student's t-quantiles by
+        // G.W. Hill CACM 13(10), 619-620, October 1970
+        if(p<=0 || p>=1 || ndf<1)
+            throw new IllegalArgumentException("Invalid p or df in call to qt(double,double,boolean).");
+        double eps=1e-12;
+        double M_PI_2=1.570796326794896619231321691640; // pi/2
+        boolean neg;
+        double P,q,prob,a,b,c,d,y,x;
+        if((lower_tail && p > 0.5) || (!lower_tail && p < 0.5)) {
+            neg = false;
+            P = 2 * (lower_tail ? (1 - p) : p);
+        }
+        else {
+            neg = true;
+            P = 2 * (lower_tail ? p : (1 - p));
+        }
+
+        if(Math.abs(ndf - 2) < eps) {   /* df ~= 2 */
+            q = Math.sqrt(2 / (P * (2 - P)) - 2);
+        }
+        else if (ndf < 1 + eps) {   /* df ~= 1 */
+            prob = P * M_PI_2;
+            q = Math.cos(prob)/Math.sin(prob);
+        }
+        else {      /*-- usual case;  including, e.g.,  df = 1.1 */
+            a = 1 / (ndf - 0.5);
+            b = 48 / (a * a);
+            c = ((20700 * a / b - 98) * a - 16) * a + 96.36;
+            d = ((94.5 / (b + c) - 3) / b + 1) * Math.sqrt(a * M_PI_2) * ndf;
+            y = Math.pow(d * P, 2 / ndf);
+            if (y > 0.05 + a) {
+                /* Asymptotic inverse expansion about normal */
+                x = qnorm(0.5 * P,false);
+                y = x * x;
+                if (ndf < 5)
+                    c += 0.3 * (ndf - 4.5) * (x + 0.6);
+                c = (((0.05 * d * x - 5) * x - 7) * x - 2) * x + b + c;
+                y = (((((0.4 * y + 6.3) * y + 36) * y + 94.5) / c - y - 3) / b + 1) * x;
+                y = a * y * y;
+                if (y > 0.002)/* FIXME: This cutoff is machine-precision dependent*/
+                    y = Math.exp(y) - 1;
+                else { /* Taylor of    e^y -1 : */
+                    y = (0.5 * y + 1) * y;
+                }
+            }
+            else {
+                y = ((1 / (((ndf + 6) / (ndf * y) - 0.089 * d - 0.822)
+                        * (ndf + 2) * 3) + 0.5 / (ndf + 4))
+                        * y - 1) * (ndf + 1) / (ndf + 2) + 1 / y;
+            }
+            q = Math.sqrt(ndf * y);
+        }
+        if(neg) q = -q;
+        return q;
+    }
+
+    public static double pt(double t,double df) {
+        // ALGORITHM AS 3  APPL. STATIST. (1968) VOL.17, P.189
+        // Computes P(T<t)
+        double a,b,idf,im2,ioe,s,c,ks,fk,k;
+        double g1=0.3183098862;// =1/pi;
+        if(df<1)
+            throw new IllegalArgumentException("Illegal argument df for pt(t,df).");
+        idf=df;
+        a=t/Math.sqrt(idf);
+        b=idf/(idf+t*t);
+        im2=df-2;
+        ioe=idf%2;
+        s=1;
+        c=1;
+        idf=1;
+        ks=2+ioe;
+        fk=ks;
+        if(im2>=2) {
+            for(k=ks;k<=im2;k+=2) {
+                c=c*b*(fk-1)/fk;
+                s+=c;
+                if(s!=idf) {
+                    idf=s;
+                    fk+=2;
+                }
+            }
+        }
+        if(ioe!=1)
+            return 0.5+0.5*a*Math.sqrt(b)*s;
+        if(df==1) s=0;
+        return 0.5+(a*b*s+Math.atan(a))*g1;
+    }
+
+    public static double pchisq(double q,double df) {
+        // Posten, H. (1989) American Statistician 43 p. 261-265
+        double df2=df*.5;
+        double q2=q*.5;
+        int n=5,k;
+        double tk,CFL,CFU,prob;
+        if(q<=0 || df<=0)
+            throw new IllegalArgumentException("Illegal argument "+q+" or "+df+" for qnorm(p).");
+        if(q<df) {
+            tk=q2*(1-n-df2)/(df2+2*n-1+n*q2/(df2+2*n));
+            for(k=n-1;k>1;k--)
+                tk=q2*(1-k-df2)/(df2+2*k-1+k*q2/(df2+2*k+tk));
+            CFL=1-q2/(df2+1+q2/(df2+2+tk));
+            prob=Math.exp(df2*Math.log(q2)-q2-Functions.lnfgamma(df2+1)-Math.log(CFL));
+        }
+        else {
+            tk=(n-df2)/(q2+n);
+            for(k=n-1;k>1;k--)
+                tk=(k-df2)/(q2+k/(1+tk));
+            CFU=1+(1-df2)/(q2+1/(1+tk));
+            prob=1-Math.exp((df2-1)*Math.log(q2)-q2-Functions.lnfgamma(df2)-Math.log(CFU));
+        }
+        return prob;
+    }
+
+    public static double betainv(double x,double p,double q) {
+        // ALGORITHM AS 63 APPL. STATIST. VOL.32, NO.1
+        // Computes P(Beta>x)
+        double beta=Functions.lnfbeta(p,q),acu=1E-14;
+        double cx,psq,pp,qq,x2,term,ai,betain,ns,rx,temp;
+        boolean indx;
+        if(p<=0 || q<=0) return(-1.0);
+        if(x<=0 || x>=1) return(-1.0);
+        psq=p+q;
+        cx=1-x;
+        if(p<psq*x) {
+            x2=cx;
+            cx=x;
+            pp=q;
+            qq=p;
+            indx=true;
+        }
+        else {
+            x2=x;
+            pp=p;
+            qq=q;
+            indx=false;
+        }
+        term=1;
+        ai=1;
+        betain=1;
+        ns=qq+cx*psq;
+        rx=x2/cx;
+        temp=qq-ai;
+        if(ns==0) rx=x2;
+        while(temp>acu && temp>acu*betain) {
+            term=term*temp*rx/(pp+ai);
+            betain=betain+term;
+            temp=Math.abs(term);
+            if(temp>acu && temp>acu*betain) {
+                ai++;
+                ns--;
+                if(ns>=0) {
+                    temp=qq-ai;
+                    if(ns==0) rx=x2;
+                }
+                else {
+                    temp=psq;
+                    psq+=1;
+                }
+            }
+        }
+        betain*=Math.exp(pp*Math.log(x2)+(qq-1)*Math.log(cx)-beta)/pp;
+        if(indx) betain=1-betain;
+        return(betain);
+    }
+
+    public static double pf(double x,double df1,double df2) {
+        // ALGORITHM AS 63 APPL. STATIST. VOL.32, NO.1
+        // Computes P(F>x)
+        return(betainv(df1*x/(df1*x+df2),0.5*df1,0.5*df2));
+    }
+}
\ No newline at end of file
diff --git a/edu/mit/wi/haploview/SwingWorker.java b/edu/mit/wi/haploview/SwingWorker.java
new file mode 100755
index 0000000..e34828e
--- /dev/null
+++ b/edu/mit/wi/haploview/SwingWorker.java
@@ -0,0 +1,132 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.SwingUtilities;
+
+/**
+ * This is the 3rd version of SwingWorker (also known as
+ * SwingWorker 3), an abstract class that you subclass to
+ * perform GUI-related work in a dedicated thread.  For
+ * instructions on using this class, see:
+ * 
+ * http://java.sun.com/docs/books/tutorial/uiswing/misc/threads.html
+ *
+ * Note that the API changed slightly in the 3rd version:
+ * You must now invoke start() on the SwingWorker after
+ * creating it.
+ */
+public abstract class SwingWorker {
+    private Object value;  // see getValue(), setValue()
+    //private Thread thread;
+
+    /** 
+     * Class to maintain reference to current worker thread
+     * under separate synchronization control.
+     */
+    private static class ThreadVar {
+        private Thread thread;
+        ThreadVar(Thread t) { thread = t; }
+        synchronized Thread get() { return thread; }
+        synchronized void clear() { thread = null; }
+    }
+
+    private ThreadVar threadVar;
+
+    /** 
+     * Get the value produced by the worker thread, or null if it 
+     * hasn't been constructed yet.
+     */
+    protected synchronized Object getValue() { 
+        return value; 
+    }
+
+    /** 
+     * Set the value produced by worker thread 
+     */
+    private synchronized void setValue(Object x) { 
+        value = x; 
+    }
+
+    /** 
+     * Compute the value to be returned by the <code>get</code> method. 
+     */
+    public abstract Object construct();
+
+    /**
+     * Called on the event dispatching thread (not on the worker thread)
+     * after the <code>construct</code> method has returned.
+     */
+    public void finished() {
+    }
+
+    /**
+     * A new method that interrupts the worker thread.  Call this method
+     * to force the worker to stop what it's doing.
+     */
+    public void interrupt() {
+        Thread t = threadVar.get();
+        if (t != null) {
+            t.interrupt();
+        }
+        threadVar.clear();
+    }
+
+    /**
+     * Return the value created by the <code>construct</code> method.  
+     * Returns null if either the constructing thread or the current
+     * thread was interrupted before a value was produced.
+     * 
+     * @return the value created by the <code>construct</code> method
+     */
+    public Object get() {
+        while (true) {  
+            Thread t = threadVar.get();
+            if (t == null) {
+                return getValue();
+            }
+            try {
+                t.join();
+            }
+            catch (InterruptedException e) {
+                Thread.currentThread().interrupt(); // propagate
+                return null;
+            }
+        }
+    }
+
+
+    /**
+     * Start a thread that will call the <code>construct</code> method
+     * and then exit.
+     */
+    public SwingWorker() {
+        final Runnable doFinished = new Runnable() {
+           public void run() { finished(); }
+        };
+
+        Runnable doConstruct = new Runnable() { 
+            public void run() {
+                try {
+                    setValue(construct());
+                }
+                finally {
+                    threadVar.clear();
+                }
+
+                SwingUtilities.invokeLater(doFinished);
+            }
+        };
+
+        Thread t = new Thread(doConstruct);
+        threadVar = new ThreadVar(t);
+    }
+
+    /**
+     * Start the worker thread.
+     */
+    public void start() {
+        Thread t = threadVar.get();
+        if (t != null) {
+            t.start();
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/TableSorter.java b/edu/mit/wi/haploview/TableSorter.java
new file mode 100755
index 0000000..8b20a09
--- /dev/null
+++ b/edu/mit/wi/haploview/TableSorter.java
@@ -0,0 +1,488 @@
+package edu.mit.wi.haploview;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+
+import javax.swing.*;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.*;
+
+/**
+ * TableSorter is a decorator for TableModels; adding sorting
+ * functionality to a supplied TableModel. TableSorter does
+ * not store or copy the data in its TableModel; instead it maintains
+ * a map from the row indexes of the view to the row indexes of the
+ * model. As requests are made of the sorter (like getValueAt(row, col))
+ * they are passed to the underlying model after the row numbers
+ * have been translated via the internal mapping array. This way,
+ * the TableSorter appears to hold another copy of the table
+ * with the rows in a different order.
+ * <p/>
+ * TableSorter registers itself as a listener to the underlying model,
+ * just as the JTable itself would. Events recieved from the model
+ * are examined, sometimes manipulated (typically widened), and then
+ * passed on to the TableSorter's listeners (typically the JTable).
+ * If a change to the model has invalidated the order of TableSorter's
+ * rows, a note of this is made and the sorter will resort the
+ * rows the next time a value is requested.
+ * <p/>
+ * When the tableHeader property is set, either by using the
+ * setTableHeader() method or the two argument constructor, the
+ * table header may be used as a complete UI for TableSorter.
+ * The default renderer of the tableHeader is decorated with a renderer
+ * that indicates the sorting status of each column. In addition,
+ * a mouse listener is installed with the following behavior:
+ * <ul>
+ * <li>
+ * Mouse-click: Clears the sorting status of all other columns
+ * and advances the sorting status of that column through three
+ * values: {NOT_SORTED, ASCENDING, DESCENDING} (then back to
+ * NOT_SORTED again).
+ * <li>
+ * SHIFT-mouse-click: Clears the sorting status of all other columns
+ * and cycles the sorting status of the column through the same
+ * three values, in the opposite order: {NOT_SORTED, DESCENDING, ASCENDING}.
+ * <li>
+ * CONTROL-mouse-click and CONTROL-SHIFT-mouse-click: as above except
+ * that the changes to the column do not cancel the statuses of columns
+ * that are already sorting - giving a way to initiate a compound
+ * sort.
+ * </ul>
+ * <p/>
+ * This is a long overdue rewrite of a class of the same name that
+ * first appeared in the swing table demos in 1997.
+ *
+ * @author Philip Milne
+ * @author Brendon McLean
+ * @author Dan van Enckevort
+ * @author Parwinder Sekhon
+ * @version 2.0 02/27/04
+ */
+
+public class TableSorter extends AbstractTableModel {
+    static final long serialVersionUID = -6957417736947812724L;
+
+    protected TableModel tableModel;
+
+    public static final int DESCENDING = -1;
+    public static final int NOT_SORTED = 0;
+    public static final int ASCENDING = 1;
+
+    private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
+
+    public static final Comparator COMPARABLE_COMAPRATOR = new Comparator() {
+        public int compare(Object o1, Object o2) {
+            return ((Comparable) o1).compareTo(o2);
+        }
+    };
+    public static final Comparator LEXICAL_COMPARATOR = new Comparator() {
+        public int compare(Object o1, Object o2) {
+            return o1.toString().compareTo(o2.toString());
+        }
+    };
+
+    private Row[] viewToModel;
+    private int[] modelToView;
+
+    private JTableHeader tableHeader;
+    private MouseListener mouseListener;
+    private TableModelListener tableModelListener;
+    private Map columnComparators = new HashMap();
+    private Vector sortingColumns = new Vector();
+
+    public TableSorter() {
+        this.mouseListener = new MouseHandler();
+        this.tableModelListener = new TableModelHandler();
+    }
+
+    public TableSorter(TableModel tableModel) {
+        this();
+        setTableModel(tableModel);
+    }
+
+    public TableSorter(TableModel tableModel, JTableHeader tableHeader) {
+        this();
+        setTableHeader(tableHeader);
+        setTableModel(tableModel);
+    }
+
+    private void clearSortingState() {
+        viewToModel = null;
+        modelToView = null;
+    }
+
+    public TableModel getTableModel() {
+        return tableModel;
+    }
+
+    public void setTableModel(TableModel tableModel) {
+        if (this.tableModel != null) {
+            this.tableModel.removeTableModelListener(tableModelListener);
+        }
+
+        this.tableModel = tableModel;
+        if (this.tableModel != null) {
+            this.tableModel.addTableModelListener(tableModelListener);
+        }
+
+        clearSortingState();
+        fireTableStructureChanged();
+    }
+
+    public JTableHeader getTableHeader() {
+        return tableHeader;
+    }
+
+    public void setTableHeader(JTableHeader tableHeader) {
+        if (this.tableHeader != null) {
+            this.tableHeader.removeMouseListener(mouseListener);
+            TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
+            if (defaultRenderer instanceof SortableHeaderRenderer) {
+                this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
+            }
+        }
+        this.tableHeader = tableHeader;
+        if (this.tableHeader != null) {
+            this.tableHeader.addMouseListener(mouseListener);
+            this.tableHeader.setDefaultRenderer(
+                    new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
+        }
+    }
+
+    public boolean isSorting() {
+        return sortingColumns.size() != 0;
+    }
+
+    private Directive getDirective(int column) {
+        for (int i = 0; i < sortingColumns.size(); i++) {
+            Directive directive = (Directive)sortingColumns.get(i);
+            if (directive.column == column) {
+                return directive;
+            }
+        }
+        return EMPTY_DIRECTIVE;
+    }
+
+    public int getSortingStatus(int column) {
+        return getDirective(column).direction;
+    }
+
+    private void sortingStatusChanged() {
+        clearSortingState();
+        fireTableDataChanged();
+        if (tableHeader != null) {
+            tableHeader.repaint();
+        }
+    }
+
+    public void setSortingStatus(int column, int status) {
+        Directive directive = getDirective(column);
+        if (directive != EMPTY_DIRECTIVE) {
+            sortingColumns.remove(directive);
+        }
+        if (status != NOT_SORTED) {
+            sortingColumns.add(new Directive(column, status));
+        }
+        sortingStatusChanged();
+    }
+
+    protected Icon getHeaderRendererIcon(int column, int size) {
+        Directive directive = getDirective(column);
+        if (directive == EMPTY_DIRECTIVE) {
+            return null;
+        }
+        return new Arrow(directive.direction == DESCENDING, size, sortingColumns.indexOf(directive));
+    }
+
+    private void cancelSorting() {
+        sortingColumns.clear();
+        sortingStatusChanged();
+    }
+
+    public void setColumnComparator(Class type, Comparator comparator) {
+        if (comparator == null) {
+            columnComparators.remove(type);
+        } else {
+            columnComparators.put(type, comparator);
+        }
+    }
+
+    protected Comparator getComparator(int column) {
+        Class columnType = tableModel.getColumnClass(column);
+        Comparator comparator = (Comparator) columnComparators.get(columnType);
+        if (comparator != null) {
+            return comparator;
+        }
+        if (Comparable.class.isAssignableFrom(columnType)) {
+            return COMPARABLE_COMAPRATOR;
+        }
+        return LEXICAL_COMPARATOR;
+    }
+
+    private Row[] getViewToModel() {
+        if (viewToModel == null) {
+            int tableModelRowCount = tableModel.getRowCount();
+            viewToModel = new Row[tableModelRowCount];
+            for (int row = 0; row < tableModelRowCount; row++) {
+                viewToModel[row] = new Row(row);
+            }
+
+            if (isSorting()) {
+                Arrays.sort(viewToModel);
+            }
+        }
+        return viewToModel;
+    }
+
+    public int modelIndex(int viewIndex) {
+        return getViewToModel()[viewIndex].modelIndex;
+    }
+
+    private int[] getModelToView() {
+        if (modelToView == null) {
+            int n = getViewToModel().length;
+            modelToView = new int[n];
+            for (int i = 0; i < n; i++) {
+                modelToView[modelIndex(i)] = i;
+            }
+        }
+        return modelToView;
+    }
+
+    // TableModel interface methods
+
+    public int getRowCount() {
+        return (tableModel == null) ? 0 : tableModel.getRowCount();
+    }
+
+    public int getColumnCount() {
+        return (tableModel == null) ? 0 : tableModel.getColumnCount();
+    }
+
+    public String getColumnName(int column) {
+        return tableModel.getColumnName(column);
+    }
+
+    public Class getColumnClass(int column) {
+        return tableModel.getColumnClass(column);
+    }
+
+    public boolean isCellEditable(int row, int column) {
+        return tableModel.isCellEditable(modelIndex(row), column);
+    }
+
+    public Object getValueAt(int row, int column) {
+        return tableModel.getValueAt(modelIndex(row), column);
+    }
+
+    public void setValueAt(Object aValue, int row, int column) {
+        tableModel.setValueAt(aValue, modelIndex(row), column);
+    }
+
+    // Helper classes
+
+    private class Row implements Comparable {
+        private int modelIndex;
+
+        public Row(int index) {
+            this.modelIndex = index;
+        }
+
+        public int compareTo(Object o) {
+            int row1 = modelIndex;
+            int row2 = ((Row) o).modelIndex;
+
+            for (Iterator it = sortingColumns.iterator(); it.hasNext();) {
+                Directive directive = (Directive) it.next();
+                int column = directive.column;
+                Object o1 = tableModel.getValueAt(row1, column);
+                Object o2 = tableModel.getValueAt(row2, column);
+
+                int comparison = 0;
+                // Define null less than everything, except null.
+                if (o1 == null && o2 == null) {
+                    comparison = 0;
+                } else if (o1 == null) {
+                    comparison = -1;
+                } else if (o2 == null) {
+                    comparison = 1;
+                } else if (o1 instanceof Double && !(o2 instanceof Double)) {
+                    comparison = 1;
+                } else if (o2 instanceof Double && !(o1 instanceof Double)) {
+                    comparison = -1;
+                }else {
+                    comparison = getComparator(column).compare(o1, o2);
+                }
+                if (comparison != 0) {
+                    return directive.direction == DESCENDING ? -comparison : comparison;
+                }
+            }
+            return 0;
+        }
+    }
+
+    private class TableModelHandler implements TableModelListener {
+        public void tableChanged(TableModelEvent e) {
+            // If we're not sorting by anything, just pass the event along.
+            if (!isSorting()) {
+                clearSortingState();
+                fireTableChanged(e);
+                return;
+            }
+
+            // If the table structure has changed, cancel the sorting; the
+            // sorting columns may have been either moved or deleted from
+            // the model.
+            if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
+                cancelSorting();
+                fireTableChanged(e);
+                return;
+            }
+
+            // We can map a cell event through to the view without widening
+            // when the following conditions apply:
+            //
+            // a) all the changes are on one row (e.getFirstRow() == e.getLastRow()) and,
+            // b) all the changes are in one column (column != TableModelEvent.ALL_COLUMNS) and,
+            // c) we are not sorting on that column (getSortingStatus(column) == NOT_SORTED) and,
+            // d) a reverse lookup will not trigger a sort (modelToView != null)
+            //
+            // Note: INSERT and DELETE events fail this test as they have column == ALL_COLUMNS.
+            //
+            // The last check, for (modelToView != null) is to see if modelToView
+            // is already allocated. If we don't do this check; sorting can become
+            // a performance bottleneck for applications where cells
+            // change rapidly in different parts of the table. If cells
+            // change alternately in the sorting column and then outside of
+            // it this class can end up re-sorting on alternate cell updates -
+            // which can be a performance problem for large tables. The last
+            // clause avoids this problem.
+            int column = e.getColumn();
+            if (e.getFirstRow() == e.getLastRow()
+                    && column != TableModelEvent.ALL_COLUMNS
+                    && getSortingStatus(column) == NOT_SORTED
+                    && modelToView != null) {
+                int viewIndex = getModelToView()[e.getFirstRow()];
+                fireTableChanged(new TableModelEvent(TableSorter.this,
+                                                     viewIndex, viewIndex,
+                                                     column, e.getType()));
+                return;
+            }
+
+            // Something has happened to the data that may have invalidated the row order.
+            clearSortingState();
+            fireTableDataChanged();
+            return;
+        }
+    }
+
+    private class MouseHandler extends MouseAdapter {
+        public void mouseClicked(MouseEvent e) {
+            JTableHeader h = (JTableHeader) e.getSource();
+            TableColumnModel columnModel = h.getColumnModel();
+            int viewColumn = columnModel.getColumnIndexAtX(e.getX());
+            int column = columnModel.getColumn(viewColumn).getModelIndex();
+            if (column != -1) {
+                int status = getSortingStatus(column);
+                if (!e.isControlDown()) {
+                    cancelSorting();
+                }
+                // Cycle the sorting states through {NOT_SORTED, ASCENDING, DESCENDING} or
+                // {NOT_SORTED, DESCENDING, ASCENDING} depending on whether shift is pressed.
+                status = status + (e.isShiftDown() ? -1 : 1);
+                status = (status + 4) % 3 - 1; // signed mod, returning {-1, 0, 1}
+                setSortingStatus(column, status);
+            }
+        }
+    }
+
+    private static class Arrow implements Icon {
+        private boolean descending;
+        private int size;
+        private int priority;
+
+        public Arrow(boolean descending, int size, int priority) {
+            this.descending = descending;
+            this.size = size;
+            this.priority = priority;
+        }
+
+        public void paintIcon(Component c, Graphics g, int x, int y) {
+            Color color = c == null ? Color.gray : c.getBackground();
+            // In a compound sort, make each succesive triangle 20%
+            // smaller than the previous one.
+            int dx = (int)(size/2*Math.pow(0.8, priority));
+            int dy = descending ? dx : -dx;
+            // Align icon (roughly) with font baseline.
+            y = y + 5*size/6 + (descending ? -dy : 0);
+            int shift = descending ? 1 : -1;
+            g.translate(x, y);
+
+            // Right diagonal.
+            g.setColor(color.darker());
+            g.drawLine(dx / 2, dy, 0, 0);
+            g.drawLine(dx / 2, dy + shift, 0, shift);
+
+            // Left diagonal.
+            g.setColor(color.brighter());
+            g.drawLine(dx / 2, dy, dx, 0);
+            g.drawLine(dx / 2, dy + shift, dx, shift);
+
+            // Horizontal line.
+            if (descending) {
+                g.setColor(color.darker().darker());
+            } else {
+                g.setColor(color.brighter().brighter());
+            }
+            g.drawLine(dx, 0, 0, 0);
+
+            g.setColor(color);
+            g.translate(-x, -y);
+        }
+
+        public int getIconWidth() {
+            return size;
+        }
+
+        public int getIconHeight() {
+            return size;
+        }
+    }
+
+    private class SortableHeaderRenderer implements TableCellRenderer {
+        private TableCellRenderer tableCellRenderer;
+
+        public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
+            this.tableCellRenderer = tableCellRenderer;
+        }
+
+        public Component getTableCellRendererComponent(JTable table,
+                                                       Object value,
+                                                       boolean isSelected,
+                                                       boolean hasFocus,
+                                                       int row,
+                                                       int column) {
+            Component c = tableCellRenderer.getTableCellRendererComponent(table,
+                    value, isSelected, hasFocus, row, column);
+            if (c instanceof JLabel) {
+                JLabel l = (JLabel) c;
+                l.setHorizontalTextPosition(JLabel.LEFT);
+                int modelColumn = table.convertColumnIndexToModel(column);
+                l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
+            }
+            return c;
+        }
+    }
+
+    private static class Directive {
+        private int column;
+        private int direction;
+
+        public Directive(int column, int direction) {
+            this.column = column;
+            this.direction = direction;
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/TreeTable/AbstractCellEditor.java b/edu/mit/wi/haploview/TreeTable/AbstractCellEditor.java
new file mode 100755
index 0000000..020cd5e
--- /dev/null
+++ b/edu/mit/wi/haploview/TreeTable/AbstractCellEditor.java
@@ -0,0 +1,113 @@
+/*
+ * %W% %E%
+ *
+ * Copyright 1997, 1998 Sun Microsystems, Inc. All Rights Reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer. 
+ *   
+ * - Redistribution in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials
+ *   provided with the distribution. 
+ *   
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.  
+ * 
+ * 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 OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
+ * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS 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 THIS SOFTWARE, EVEN IF SUN HAS 
+ * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ * 
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ */
+
+package edu.mit.wi.haploview.TreeTable;
+
+//import java.awt.Component;
+//import java.awt.event.*;
+//import java.awt.AWTEvent;
+import javax.swing.*;
+import javax.swing.event.*;
+import java.util.EventObject;
+//import java.io.Serializable;
+
+/**
+ * @version %I% %G% 
+ * 
+ * A base class for CellEditors, providing default implementations for all 
+ * methods in the CellEditor interface and support for managing a series 
+ * of listeners. 
+ *
+ * @author Philip Milne
+ */
+
+public class AbstractCellEditor implements CellEditor {
+
+    protected EventListenerList listenerList = new EventListenerList();
+
+    public Object getCellEditorValue() { return null; }
+    public boolean isCellEditable(EventObject e) { return true; }
+    public boolean shouldSelectCell(EventObject anEvent) { return false; }
+    public boolean stopCellEditing() { return true; }
+    public void cancelCellEditing() {}
+
+    public void addCellEditorListener(CellEditorListener l) {
+	listenerList.add(CellEditorListener.class, l);
+    }
+
+    public void removeCellEditorListener(CellEditorListener l) {
+	listenerList.remove(CellEditorListener.class, l);
+    }
+
+    /**
+     * Notify all listeners that have registered interest for
+     * notification on this event type.  
+     * @see EventListenerList
+     */
+    protected void fireEditingStopped() {
+	// Guaranteed to return a non-null array
+	Object[] listeners = listenerList.getListenerList();
+	// Process the listeners last to first, notifying
+	// those that are interested in this event
+	for (int i = listeners.length-2; i>=0; i-=2) {
+	    if (listeners[i]==CellEditorListener.class) {
+		((CellEditorListener)listeners[i+1]).editingStopped(new ChangeEvent(this));
+	    }	       
+	}
+    }
+
+    /**
+     * Notify all listeners that have registered interest for
+     * notification on this event type.  
+     * @see EventListenerList
+     */
+    protected void fireEditingCanceled() {
+	// Guaranteed to return a non-null array
+	Object[] listeners = listenerList.getListenerList();
+	// Process the listeners last to first, notifying
+	// those that are interested in this event
+	for (int i = listeners.length-2; i>=0; i-=2) {
+	    if (listeners[i]==CellEditorListener.class) {
+		((CellEditorListener)listeners[i+1]).editingCanceled(new ChangeEvent(this));
+	    }	       
+	}
+    }
+}
+
diff --git a/edu/mit/wi/haploview/TreeTable/AbstractTreeTableModel.java b/edu/mit/wi/haploview/TreeTable/AbstractTreeTableModel.java
new file mode 100755
index 0000000..c3463bc
--- /dev/null
+++ b/edu/mit/wi/haploview/TreeTable/AbstractTreeTableModel.java
@@ -0,0 +1,227 @@
+/*
+* %W% %E%
+*
+* Copyright 1997, 1998 Sun Microsystems, Inc. All Rights Reserved.
+*
+* Redistribution and use in source and binary forms, with or
+* without modification, are permitted provided that the following
+* conditions are met:
+*
+* - Redistributions of source code must retain the above copyright
+*   notice, this list of conditions and the following disclaimer.
+*
+* - Redistribution in binary form must reproduce the above
+*   copyright notice, this list of conditions and the following
+*   disclaimer in the documentation and/or other materials
+*   provided with the distribution.
+*
+* Neither the name of Sun Microsystems, Inc. or the names of
+* contributors may be used to endorse or promote products derived
+* from this software without specific prior written permission.
+*
+* 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 OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
+* RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS 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 THIS SOFTWARE, EVEN IF SUN HAS
+* BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+*
+* You acknowledge that this software is not designed, licensed or
+* intended for use in the design, construction, operation or
+* maintenance of any nuclear facility.
+*/
+
+package edu.mit.wi.haploview.TreeTable;
+
+import javax.swing.tree.*;
+import javax.swing.event.*;
+
+/**
+ * An abstract implementation of the TreeTableModel interface, handling 
+ * the list of listeners. 
+ *
+ * @version %I% %G%
+ *
+ * @author Philip Milne
+ */
+
+public abstract class AbstractTreeTableModel implements TreeTableModel {
+    protected Object root;     
+    protected EventListenerList listenerList = new EventListenerList();
+
+    public AbstractTreeTableModel(Object root) {
+        this.root = root; 
+    }
+
+    //
+    // Default implmentations for methods in the TreeModel interface. 
+    //
+
+    public Object getRoot() {
+        return root;
+    }
+
+    public boolean isLeaf(Object node) {
+        return getChildCount(node) == 0;
+    }
+
+    public void valueForPathChanged(TreePath path, Object newValue) {}
+
+    // This is not called in the JTree's default mode: use a naive implementation.
+    public int getIndexOfChild(Object parent, Object child) {
+        for (int i = 0; i < getChildCount(parent); i++) {
+            if (getChild(parent, i).equals(child)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public void addTreeModelListener(TreeModelListener l) {
+        listenerList.add(TreeModelListener.class, l);
+    }
+
+    public void removeTreeModelListener(TreeModelListener l) {
+        listenerList.remove(TreeModelListener.class, l);
+    }
+
+    /*
+    * Notify all listeners that have registered interest for
+    * notification on this event type.  The event instance
+    * is lazily created using the parameters passed into
+    * the fire method.
+    * @see EventListenerList
+    */
+    protected void fireTreeNodesChanged(Object source, Object[] path, 
+                                        int[] childIndices, 
+                                        Object[] children) {
+        // Guaranteed to return a non-null array
+        Object[] listeners = listenerList.getListenerList();
+        TreeModelEvent e = null;
+        // Process the listeners last to first, notifying
+        // those that are interested in this event
+        for (int i = listeners.length-2; i>=0; i-=2) {
+            if (listeners[i]==TreeModelListener.class) {
+                // Lazily create the event:
+                if (e == null)
+                    e = new TreeModelEvent(source, path,
+                            childIndices, children);
+                ((TreeModelListener)listeners[i+1]).treeNodesChanged(e);
+            }          
+        }
+    }
+
+    /*
+    * Notify all listeners that have registered interest for
+    * notification on this event type.  The event instance
+    * is lazily created using the parameters passed into
+    * the fire method.
+    * @see EventListenerList
+    */
+    protected void fireTreeNodesInserted(Object source, Object[] path,
+                                         int[] childIndices,
+                                         Object[] children) {
+        // Guaranteed to return a non-null array
+        Object[] listeners = listenerList.getListenerList();
+        TreeModelEvent e = null;
+        // Process the listeners last to first, notifying
+        // those that are interested in this event
+        for (int i = listeners.length-2; i>=0; i-=2) {
+            if (listeners[i]==TreeModelListener.class) {
+                // Lazily create the event:
+                if (e == null)
+                    e = new TreeModelEvent(source, path,
+                            childIndices, children);
+                ((TreeModelListener)listeners[i+1]).treeNodesInserted(e);
+            }          
+        }
+    }
+
+    /*
+    * Notify all listeners that have registered interest for
+    * notification on this event type.  The event instance
+    * is lazily created using the parameters passed into
+    * the fire method.
+    * @see EventListenerList
+    */
+    protected void fireTreeNodesRemoved(Object source, Object[] path, 
+                                        int[] childIndices, 
+                                        Object[] children) {
+        // Guaranteed to return a non-null array
+        Object[] listeners = listenerList.getListenerList();
+        TreeModelEvent e = null;
+        // Process the listeners last to first, notifying
+        // those that are interested in this event
+        for (int i = listeners.length-2; i>=0; i-=2) {
+            if (listeners[i]==TreeModelListener.class) {
+                // Lazily create the event:
+                if (e == null)
+                    e = new TreeModelEvent(source, path,
+                            childIndices, children);
+                ((TreeModelListener)listeners[i+1]).treeNodesRemoved(e);
+            }          
+        }
+    }
+
+    /*
+    * Notify all listeners that have registered interest for
+    * notification on this event type.  The event instance
+    * is lazily created using the parameters passed into
+    * the fire method.
+    * @see EventListenerList
+    */
+    protected void fireTreeStructureChanged(Object source, Object[] path,
+                                            int[] childIndices,
+                                            Object[] children) {
+        // Guaranteed to return a non-null array
+        Object[] listeners = listenerList.getListenerList();
+        TreeModelEvent e = null;
+        // Process the listeners last to first, notifying
+        // those that are interested in this event
+        for (int i = listeners.length-2; i>=0; i-=2) {
+            if (listeners[i]==TreeModelListener.class) {
+                // Lazily create the event:
+                if (e == null)
+                    e = new TreeModelEvent(source, path,
+                            childIndices, children);
+                ((TreeModelListener)listeners[i+1]).treeStructureChanged(e);
+            }          
+        }
+    }
+
+    //
+    // Default impelmentations for methods in the TreeTableModel interface. 
+    //
+
+    public Class getColumnClass(int column) { return Object.class; }
+
+    /** By default, make the column with the Tree in it the only editable one.
+     *  Making this column editable causes the JTable to forward mouse
+     *  and keyboard events in the Tree column to the underlying JTree.
+     */
+    public boolean isCellEditable(Object node, int column) {
+        return getColumnClass(column) == TreeTableModel.class;
+    }
+
+    public void setValueAt(Object aValue, Object node, int column) {}
+
+
+    // Left to be implemented in the subclass:
+
+    /*
+    *   public Object getChild(Object parent, int index)
+    *   public int getChildCount(Object parent)
+    *   public int getColumnCount()
+    *   public String getColumnName(Object node, int column)
+    *   public Object getValueAt(Object node, int column)
+    */
+
+}
+
diff --git a/edu/mit/wi/haploview/TreeTable/HaplotypeAssociationModel.java b/edu/mit/wi/haploview/TreeTable/HaplotypeAssociationModel.java
new file mode 100755
index 0000000..0e99e7e
--- /dev/null
+++ b/edu/mit/wi/haploview/TreeTable/HaplotypeAssociationModel.java
@@ -0,0 +1,84 @@
+package edu.mit.wi.haploview.TreeTable;
+
+import edu.mit.wi.haploview.Constants;
+
+import java.util.Vector;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Mark Daly
+ * Date: Sep 3, 2004
+ * Time: 11:07:30 AM
+ * To change this template use File | Settings | File Templates.
+ */
+
+public class HaplotypeAssociationModel extends AbstractTreeTableModel
+                             implements TreeTableModel, Constants {
+    // Names of the columns.
+    Vector colNames;
+    //this int is 0 to show ratios, 1 to show counts
+    private int countsOrRatios;
+
+
+    public HaplotypeAssociationModel(Vector colNames, HaplotypeAssociationNode han) {
+        super(han);
+        this.colNames = colNames;
+    }
+
+    public Class getColumnClass(int c){
+        if (c == 0){
+            return TreeTableModel.class;
+        }else{
+            return String.class;
+        }
+    }
+
+    public int getColumnCount() {
+        return colNames.size();
+    }
+
+    public String getColumnName(int column) {
+        return (String)colNames.elementAt(column);
+    }
+
+    public Object getValueAt(Object node,
+                             int column) {
+        HaplotypeAssociationNode n = (HaplotypeAssociationNode) node;
+        switch (column){
+            case 0:
+                return n.getName();
+            case 1:
+                return n.getFreq();
+            case 2:
+                if(this.countsOrRatios == SHOW_HAP_COUNTS) {
+                    return n.getCounts();
+                } else if(this.countsOrRatios == SHOW_HAP_RATIOS) {
+                    return n.getCCFreqs();
+                }
+            case 3:
+                return n.getChiSq();
+            case 4:
+                return n.getPVal();
+        }
+        return null;
+    }
+
+    public int getChildCount(Object parent) {
+        return ((HaplotypeAssociationNode) parent).children.size();
+    }
+
+    public Object getChild(Object parent,
+                           int index) {
+        return ((HaplotypeAssociationNode) parent).children.elementAt(index);
+    }
+
+    public int getCountsOrRatios() {
+        return countsOrRatios;
+    }
+
+    public void setCountsOrRatios(int countsOrRatios) {
+        this.countsOrRatios = countsOrRatios;
+    }
+}
+
+
diff --git a/edu/mit/wi/haploview/TreeTable/HaplotypeAssociationNode.java b/edu/mit/wi/haploview/TreeTable/HaplotypeAssociationNode.java
new file mode 100755
index 0000000..cf87383
--- /dev/null
+++ b/edu/mit/wi/haploview/TreeTable/HaplotypeAssociationNode.java
@@ -0,0 +1,69 @@
+package edu.mit.wi.haploview.TreeTable;
+
+import edu.mit.wi.haploview.association.HaplotypeAssociationResult;
+
+import java.util.Vector;
+import java.util.Locale;
+import java.text.NumberFormat;
+
+
+public class HaplotypeAssociationNode {
+    String name;
+    Double pval;
+    Vector children = new Vector();
+    double chisq;
+    NumberFormat nf = NumberFormat.getInstance(Locale.US);
+    String freqStr = "", countStr = "", freq = "";
+
+    public HaplotypeAssociationNode(String name) {
+        this.name = name;
+        this.freq = "";
+        this.chisq = -1;
+        this.pval = null;
+    }
+
+    public HaplotypeAssociationNode(HaplotypeAssociationResult ar, int index) {
+        this.name = ar.getAlleleName(index);
+        this.chisq = ar.getChiSquare(index);
+        this.pval = ar.getPValue(index);
+        this.freq = ar.getFreq(index);
+        this.freqStr = ar.getFreqString(index);
+        this.countStr = ar.getCountString(index);
+    }
+
+    public void add(HaplotypeAssociationNode child){
+        children.add(child);
+    }
+
+    public String toString(){
+        return name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getFreq() {
+        return freq;
+    }
+
+    public String getCCFreqs() {
+        return freqStr;
+    }
+
+    public String getCounts() {
+        return countStr;
+    }
+
+    public String getChiSq() {
+        if (chisq < 0){
+            return ("");
+        }else{
+            return (new Double(chisq)).toString();
+        }
+    }
+
+    public Double getPVal(){
+        return pval;
+    }
+}
diff --git a/edu/mit/wi/haploview/TreeTable/JTreeTable.java b/edu/mit/wi/haploview/TreeTable/JTreeTable.java
new file mode 100755
index 0000000..be60a73
--- /dev/null
+++ b/edu/mit/wi/haploview/TreeTable/JTreeTable.java
@@ -0,0 +1,154 @@
+/*
+* %W% %E%
+*
+* Copyright 1997, 1998 Sun Microsystems, Inc. All Rights Reserved.
+*
+* Redistribution and use in source and binary forms, with or
+* without modification, are permitted provided that the following
+* conditions are met:
+*
+* - Redistributions of source code must retain the above copyright
+*   notice, this list of conditions and the following disclaimer.
+*
+* - Redistribution in binary form must reproduce the above
+*   copyright notice, this list of conditions and the following
+*   disclaimer in the documentation and/or other materials
+*   provided with the distribution.
+*
+* Neither the name of Sun Microsystems, Inc. or the names of
+* contributors may be used to endorse or promote products derived
+* from this software without specific prior written permission.
+*
+* 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 OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
+* RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS 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 THIS SOFTWARE, EVEN IF SUN HAS
+* BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+*
+* You acknowledge that this software is not designed, licensed or
+* intended for use in the design, construction, operation or
+* maintenance of any nuclear facility.
+*/
+
+package edu.mit.wi.haploview.TreeTable;
+
+import javax.swing.*;
+import javax.swing.tree.*;
+import javax.swing.table.*;
+
+import java.awt.Component;
+import java.awt.Graphics;
+
+/**
+ * This example shows how to create a simple JTreeTable component, 
+ * by using a JTree as a renderer (and editor) for the cells in a 
+ * particular column in the JTable.  
+ *
+ * @version %I% %G%
+ *
+ * @author Philip Milne
+ * @author Scott Violet
+ */
+
+public class JTreeTable extends JTable {
+    static final long serialVersionUID = 2438273584963759245L;
+    protected TreeTableCellRenderer tree;
+
+    public JTreeTable(TreeTableModel treeTableModel) {
+        super();
+
+        // Create the tree. It will be used as a renderer and editor.
+        tree = new TreeTableCellRenderer(treeTableModel);
+
+        // Install a tableModel representing the visible rows in the tree.
+        super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
+
+        // Force the JTable and JTree to share their row selection models.
+        tree.setSelectionModel(new DefaultTreeSelectionModel() {
+	        static final long serialVersionUID = -7983407332773933546L;
+            // Extend the implementation of the constructor, as if:
+            /* public this() */ {
+                setSelectionModel(listSelectionModel);
+            }
+        });
+        // Make the tree and table row heights the same.
+        tree.setRowHeight(getRowHeight());
+
+        // Install the tree renderer and editor.
+        setDefaultRenderer(TreeTableModel.class, tree);
+        setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
+
+    }
+
+    public JTree getTree() {
+        return tree;
+    }
+
+    /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
+    * paint the editor. The UI currently uses different techniques to
+    * paint the renderers and editors and overriding setBounds() below
+    * is not the right thing to do for an editor. Returning -1 for the
+    * editing row in this case, ensures the editor is never painted.
+    */
+    public int getEditingRow() {
+        return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 : editingRow;  
+    }
+
+    //
+    // The renderer used to display the tree nodes, a JTree.  
+    //
+
+    public class TreeTableCellRenderer extends JTree implements TableCellRenderer {
+        static final long serialVersionUID = -2538618387281265863L;
+
+        protected int visibleRow;
+
+        public TreeTableCellRenderer(TreeModel model) {
+            super(model);
+        }
+
+        public void setBounds(int x, int y, int w, int h) {
+            super.setBounds(x, 0, w, JTreeTable.this.getHeight());
+        }
+
+        public void paint(Graphics g) {
+            g.translate(0, -visibleRow * getRowHeight());
+            super.paint(g);
+        }
+
+        public Component getTableCellRendererComponent(JTable table,
+                                                       Object value,
+                                                       boolean isSelected,
+                                                       boolean hasFocus,
+                                                       int row, int column) {
+            if(isSelected)
+                setBackground(table.getSelectionBackground());
+            else
+                setBackground(table.getBackground());
+
+            visibleRow = row;
+            return this;
+        }
+    }
+
+    //
+    // The editor used to interact with tree nodes, a JTree.  
+    //
+
+    public class TreeTableCellEditor extends AbstractCellEditor implements TableCellEditor {
+        public Component getTableCellEditorComponent(JTable table, Object value,
+                                                     boolean isSelected, int r, int c) {
+            return tree;
+        }
+    }
+
+}
+
diff --git a/edu/mit/wi/haploview/TreeTable/MergeSort.java b/edu/mit/wi/haploview/TreeTable/MergeSort.java
new file mode 100755
index 0000000..a8069c8
--- /dev/null
+++ b/edu/mit/wi/haploview/TreeTable/MergeSort.java
@@ -0,0 +1,109 @@
+/*
+ * %W% %E%
+ *
+ * Copyright 1997, 1998 Sun Microsystems, Inc. All Rights Reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer. 
+ *   
+ * - Redistribution in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials
+ *   provided with the distribution. 
+ *   
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.  
+ * 
+ * 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 OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
+ * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS 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 THIS SOFTWARE, EVEN IF SUN HAS 
+ * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ * 
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ */
+
+package edu.mit.wi.haploview.TreeTable;
+
+/**
+ * An implementation of MergeSort, needs to be subclassed to provide a 
+ * comparator.
+ *
+ * @version %I% %G%
+ *
+ * @author Scott Violet
+ */
+public abstract class MergeSort extends Object {
+    protected Object           toSort[];
+    protected Object           swapSpace[];
+
+    public void sort(Object array[]) {
+	if(array != null && array.length > 1)
+	{
+	    int             maxLength;
+  
+	    maxLength = array.length;
+	    swapSpace = new Object[maxLength];
+	    toSort = array;
+	    this.mergeSort(0, maxLength - 1);
+	    swapSpace = null;
+	    toSort = null;
+	}
+    }
+
+    public abstract int compareElementsAt(int beginLoc, int endLoc);
+
+    protected void mergeSort(int begin, int end) {
+	if(begin != end)
+	{
+	    int           mid;
+
+	    mid = (begin + end) / 2;
+	    this.mergeSort(begin, mid);
+	    this.mergeSort(mid + 1, end);
+	    this.merge(begin, mid, end);
+	}
+    }
+
+    protected void merge(int begin, int middle, int end) {
+	int           firstHalf, secondHalf, count;
+
+	firstHalf = count = begin;
+	secondHalf = middle + 1;
+	while((firstHalf <= middle) && (secondHalf <= end))
+	{
+	    if(this.compareElementsAt(secondHalf, firstHalf) < 0)
+		swapSpace[count++] = toSort[secondHalf++];
+	    else
+		swapSpace[count++] = toSort[firstHalf++];
+	}
+	if(firstHalf <= middle)
+	{
+	    while(firstHalf <= middle)
+		swapSpace[count++] = toSort[firstHalf++];
+	}
+	else
+	{
+	    while(secondHalf <= end)
+		swapSpace[count++] = toSort[secondHalf++];
+	}
+	for(count = begin;count <= end;count++)
+	    toSort[count] = swapSpace[count];
+    }
+}
+
diff --git a/edu/mit/wi/haploview/TreeTable/TreeTableModel.java b/edu/mit/wi/haploview/TreeTable/TreeTableModel.java
new file mode 100755
index 0000000..5c2f5eb
--- /dev/null
+++ b/edu/mit/wi/haploview/TreeTable/TreeTableModel.java
@@ -0,0 +1,94 @@
+/*
+ * %W% %E%
+ *
+ * Copyright 1997, 1998 Sun Microsystems, Inc. All Rights Reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ * 
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer. 
+ *   
+ * - Redistribution in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials
+ *   provided with the distribution. 
+ *   
+ * Neither the name of Sun Microsystems, Inc. or the names of
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.  
+ * 
+ * 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 OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
+ * RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS 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 THIS SOFTWARE, EVEN IF SUN HAS 
+ * BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ * 
+ * You acknowledge that this software is not designed, licensed or
+ * intended for use in the design, construction, operation or
+ * maintenance of any nuclear facility.
+ */
+
+package edu.mit.wi.haploview.TreeTable;
+
+
+import javax.swing.tree.TreeModel;
+
+/**
+ * TreeTableModel is the model used by a JTreeTable. It extends TreeModel
+ * to add methods for getting inforamtion about the set of columns each 
+ * node in the TreeTableModel may have. Each column, like a column in 
+ * a TableModel, has a name and a type associated with it. Each node in 
+ * the TreeTableModel can return a value for each of the columns and 
+ * set that value if isCellEditable() returns true. 
+ *
+ * @version %I% %G%
+ *
+ * @author Philip Milne 
+ * @author Scott Violet
+ */
+public interface TreeTableModel extends TreeModel
+{
+    /**
+     * Returns the number ofs availible column.
+     */
+    public int getColumnCount();
+
+    /**
+     * Returns the name for column number <code>column</code>.
+     */
+    public String getColumnName(int column);
+
+    /**
+     * Returns the type for column number <code>column</code>.
+     */
+    public Class getColumnClass(int column);
+
+    /**
+     * Returns the value to be displayed for node <code>node</code>, 
+     * at column number <code>column</code>.
+     */
+    public Object getValueAt(Object node, int column);
+
+    /**
+     * Indicates whether the the value for node <code>node</code>, 
+     * at column number <code>column</code> is editable.
+     */
+    public boolean isCellEditable(Object node, int column);
+
+    /**
+     * Sets the value for node <code>node</code>, 
+     * at column number <code>column</code>.
+     */
+    public void setValueAt(Object aValue, Object node, int column);
+}
+
diff --git a/edu/mit/wi/haploview/TreeTable/TreeTableModelAdapter.java b/edu/mit/wi/haploview/TreeTable/TreeTableModelAdapter.java
new file mode 100755
index 0000000..42fa22d
--- /dev/null
+++ b/edu/mit/wi/haploview/TreeTable/TreeTableModelAdapter.java
@@ -0,0 +1,121 @@
+/*
+* %W% %E%
+*
+* Copyright 1997, 1998 Sun Microsystems, Inc. All Rights Reserved.
+*
+* Redistribution and use in source and binary forms, with or
+* without modification, are permitted provided that the following
+* conditions are met:
+*
+* - Redistributions of source code must retain the above copyright
+*   notice, this list of conditions and the following disclaimer.
+*
+* - Redistribution in binary form must reproduce the above
+*   copyright notice, this list of conditions and the following
+*   disclaimer in the documentation and/or other materials
+*   provided with the distribution.
+*
+* Neither the name of Sun Microsystems, Inc. or the names of
+* contributors may be used to endorse or promote products derived
+* from this software without specific prior written permission.
+*
+* 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 OR LIABILITIES SUFFERED BY LICENSEE AS A RESULT OF OR
+* RELATING TO USE, MODIFICATION OR DISTRIBUTION OF THIS 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 THIS SOFTWARE, EVEN IF SUN HAS
+* BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+*
+* You acknowledge that this software is not designed, licensed or
+* intended for use in the design, construction, operation or
+* maintenance of any nuclear facility.
+*/
+
+package edu.mit.wi.haploview.TreeTable;
+
+import javax.swing.table.AbstractTableModel;
+import javax.swing.JTree;
+import javax.swing.tree.TreePath;
+import javax.swing.event.TreeExpansionEvent;
+import javax.swing.event.TreeExpansionListener;
+
+/**
+ * This is a wrapper class takes a TreeTableModel and implements 
+ * the table model interface. The implementation is trivial, with 
+ * all of the event dispatching support provided by the superclass: 
+ * the AbstractTableModel. 
+ *
+ * @version %I% %G%
+ *
+ * @author Philip Milne
+ * @author Scott Violet
+ */
+
+
+public class TreeTableModelAdapter extends AbstractTableModel
+{
+    static final long serialVersionUID = 7936294935279721570L;
+
+    JTree tree;
+    TreeTableModel treeTableModel;
+
+    public TreeTableModelAdapter(TreeTableModel treeTableModel, JTree tree) {
+        this.tree = tree;
+        this.treeTableModel = treeTableModel;
+
+        tree.addTreeExpansionListener(new TreeExpansionListener() {
+            // Don't use fireTableRowsInserted() here;
+            // the selection model would get  updated twice.
+            public void treeExpanded(TreeExpansionEvent event) {
+                fireTableDataChanged();
+            }
+            public void treeCollapsed(TreeExpansionEvent event) {
+                fireTableDataChanged();
+            }
+        });
+    }
+
+    // Wrappers, implementing TableModel interface.
+
+    public int getColumnCount() {
+        return treeTableModel.getColumnCount();
+    }
+
+    public String getColumnName(int column) {
+        return treeTableModel.getColumnName(column);
+    }
+
+    public Class getColumnClass(int column) {
+        return treeTableModel.getColumnClass(column);
+    }
+
+    public int getRowCount() {
+        return tree.getRowCount();
+    }
+
+    protected Object nodeForRow(int row) {
+        TreePath treePath = tree.getPathForRow(row);
+        return treePath.getLastPathComponent();
+    }
+
+    public Object getValueAt(int row, int column) {
+        return treeTableModel.getValueAt(nodeForRow(row), column);
+    }
+
+    public boolean isCellEditable(int row, int column) {
+        return treeTableModel.isCellEditable(nodeForRow(row), column);
+    }
+
+    public void setValueAt(Object value, int row, int column) {
+        treeTableModel.setValueAt(value, nodeForRow(row), column);
+    }
+}
+
+
diff --git a/edu/mit/wi/haploview/TweakBlockDefsDialog.java b/edu/mit/wi/haploview/TweakBlockDefsDialog.java
new file mode 100755
index 0000000..67e2d60
--- /dev/null
+++ b/edu/mit/wi/haploview/TweakBlockDefsDialog.java
@@ -0,0 +1,177 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import javax.swing.border.TitledBorder;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.*;
+
+public class TweakBlockDefsDialog extends JDialog implements ActionListener {
+    static final long serialVersionUID = 1937924544508766506L;
+
+    NumberTextField gamThresh, highLD, lowLD, highRec, informFrac, mafCut, spinedp;
+    HaploView hv;
+
+    public TweakBlockDefsDialog(String title, HaploView h){
+        super(h, title);
+        hv = h;
+
+        JPanel contents = new JPanel();
+        contents.setLayout(new BoxLayout(contents, BoxLayout.Y_AXIS));
+
+        JPanel sfsPanel = new JPanel();
+        sfsPanel.setBorder(new TitledBorder("Gabriel et al."));
+        sfsPanel.setLayout(new BoxLayout(sfsPanel, BoxLayout.Y_AXIS));
+        JPanel holdPanel = new JPanel();
+        holdPanel.add(new JLabel("Confidence interval minima for strong LD:"));
+        JPanel fieldPanel = new JPanel();
+        fieldPanel.setLayout(new BoxLayout(fieldPanel, BoxLayout.Y_AXIS));
+        JPanel top = new JPanel();
+        highLD = new NumberTextField(String.valueOf(FindBlocks.cutHighCI),4, true, false);
+        top.add(new JLabel("Upper:"));
+        top.add(highLD);
+        fieldPanel.add(top);
+        JPanel bottom = new JPanel();
+        lowLD = new NumberTextField(String.valueOf(FindBlocks.cutLowCI),4, true, false);
+        bottom.add(new JLabel("Lower:"));
+        bottom.add(lowLD);
+        fieldPanel.add(bottom);
+        holdPanel.add(fieldPanel);
+        sfsPanel.add(holdPanel);
+        holdPanel = new JPanel();
+        holdPanel.add(new JLabel("Upper confidence interval maximum for strong recombination:"));
+        highRec = new NumberTextField(String.valueOf(FindBlocks.recHighCI),4,true,false);
+        holdPanel.add(highRec);
+        sfsPanel.add(holdPanel);
+        holdPanel = new JPanel();
+        holdPanel.add(new JLabel("Fraction of strong LD in informative comparisons must be at least "));
+        informFrac = new NumberTextField(String.valueOf(FindBlocks.informFrac),4,true,false);
+        holdPanel.add(informFrac);
+        sfsPanel.add(holdPanel);
+        holdPanel = new JPanel();
+        holdPanel.add(new JLabel("Exclude markers below "));
+        mafCut = new NumberTextField(String.valueOf(FindBlocks.mafThresh),4,true,false);
+        holdPanel.add(mafCut);
+        holdPanel.add(new JLabel(" MAF."));
+        sfsPanel.add(holdPanel);
+
+        JPanel gamPanel = new JPanel();
+        gamPanel.setBorder(new TitledBorder("4 Gamete Rule"));
+        gamPanel.add(new JLabel("4th gamete must be observed at frequency > "));
+        gamThresh = new NumberTextField(String.valueOf(FindBlocks.fourGameteCutoff), 5, true,false);
+        gamPanel.add(gamThresh);
+
+        JPanel spinePanel = new JPanel();
+        spinePanel.setBorder(new TitledBorder("Strong LD Spine"));
+        spinePanel.add(new JLabel("Extend spine if D' > "));
+        spinedp = new NumberTextField(String.valueOf(FindBlocks.spineDP), 4, true,false);
+        spinePanel.add(spinedp);
+
+        JPanel butPanel = new JPanel();
+        JButton button = new JButton("OK");
+        button.addActionListener(this);
+        butPanel.add(button);
+        button = new JButton("Cancel");
+        button.addActionListener(this);
+        butPanel.add(button);
+
+        contents.add(sfsPanel);
+        contents.add(gamPanel);
+        contents.add(spinePanel);
+        contents.add(butPanel);
+        this.setContentPane(contents);
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if (command.equals("OK")){
+            doTweak();
+            this.dispose();
+        }else if (command.equals("Cancel")){
+            this.dispose();
+        }
+    }
+
+    private void doTweak() {
+        String gt = gamThresh.getText();
+        if (gt.equals("")){
+            gt = "0";
+        }
+        double gtVal = Double.parseDouble(gt);
+        if (gtVal < 1){
+            FindBlocks.fourGameteCutoff = gtVal;
+        }
+
+        String cih = highLD.getText();
+        if (cih.equals("")){
+            cih = "0";
+        }
+        double cihVal = Double.parseDouble(cih);
+        if (cihVal < 1){
+            FindBlocks.cutHighCI = cihVal;
+        }
+
+        String  cil = lowLD.getText();
+        if (cil.equals("")){
+            cil = "0";
+        }
+        double cilVal = Double.parseDouble(cil);
+        if (cilVal < 1){
+            FindBlocks.cutLowCI = cilVal;
+        }
+
+        String cirec = highRec.getText();
+        if (cirec.equals("")){
+             cirec = "0";
+        }
+        double  cirecVal = Double.parseDouble(cirec);
+        if (cirecVal < 1){
+            FindBlocks.recHighCI = cirecVal;
+        }
+
+        String  ifs = informFrac.getText();
+        if (ifs.equals("")){
+            ifs = "0";
+        }
+        double ifsV = Double.parseDouble(ifs);
+        if (ifsV < 1){
+            FindBlocks.informFrac = ifsV;
+        }
+
+        String mc = mafCut.getText();
+        if (mc.equals("")){
+            mc = "0";
+        }
+        double mcV = Double.parseDouble(mc);
+        if (mcV < 1){
+            FindBlocks.mafThresh = mcV;
+        }
+
+        String sdp = spinedp.getText();
+        if (sdp.equals("")){
+            sdp = "0";
+        }
+        double sdpV = Double.parseDouble(sdp);
+        if (sdpV < 1){
+            FindBlocks.spineDP = sdpV;
+        }
+
+
+        //note that this will only apply to the cursor in this dialog, but java seems touchy
+        //about setting a "global" cursor
+        setCursor(new Cursor(Cursor.WAIT_CURSOR));
+
+        javax.swing.SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                hv.theData.guessBlocks(hv.getCurrentBlockDef());
+                hv.dPrimeDisplay.colorDPrime();
+                hv.changeBlocks(hv.getCurrentBlockDef());
+                setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
+            }
+        });
+    }
+
+}
diff --git a/edu/mit/wi/haploview/UpdateChecker.java b/edu/mit/wi/haploview/UpdateChecker.java
new file mode 100755
index 0000000..b782990
--- /dev/null
+++ b/edu/mit/wi/haploview/UpdateChecker.java
@@ -0,0 +1,119 @@
+package edu.mit.wi.haploview;
+
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+//import java.io.InputStream;
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: julian
+ * Date: Aug 24, 2004
+ * Time: 3:33:11 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class UpdateChecker {
+    private boolean newVersionAvailable;
+    private boolean finalReleaseAvailable;
+    private double newVersion;
+    private int newBetaVersion = -1;
+
+    public UpdateChecker() {
+
+    }
+
+    public boolean isNewVersionAvailable() {
+        return newVersionAvailable;
+    }
+
+    public boolean isFinalVersionAvailable() {
+        return finalReleaseAvailable;
+    }
+
+    public void setNewVersionAvailable(boolean newVersionAvailable) {
+        this.newVersionAvailable = newVersionAvailable;
+    }
+
+    public double getNewVersion() {
+        return newVersion;
+    }
+
+    public int getNewBetaVersion() {
+        return newBetaVersion;
+    }
+
+    public void setNewVersion(double newVersion) {
+        this.newVersion = newVersion;
+    }
+
+    public boolean checkForUpdate() throws IOException{
+
+        try {
+            URL url = new URL("http://www.broad.mit.edu/mpg/haploview/uc/newversion.txt");
+            HttpURLConnection con = (HttpURLConnection)url.openConnection();
+            con.setRequestProperty("User-agent",Constants.USER_AGENT);
+            con.connect();
+
+            int response = con.getResponseCode();
+
+            if ((response != HttpURLConnection.HTTP_ACCEPTED) && (response != HttpURLConnection.HTTP_OK)) {
+                //if something went wrong
+                throw new IOException("Could not connect to update server.");
+            }else {
+                //all is well
+                BufferedReader betaReader = new BufferedReader(new InputStreamReader(con.getInputStream()));
+                String versionLine = betaReader.readLine();
+                String betaLine = betaReader.readLine();
+                double newestVersion;
+                int newestBetaVersion;
+                try{
+                    newestVersion = Double.parseDouble(versionLine);
+                    newestBetaVersion = Integer.parseInt(betaLine);
+                }catch(NumberFormatException nfe){
+                    return false;
+                }
+
+
+                if(Constants.VERSION < newestVersion) { //you're using an older final version
+                    this.newVersion = newestVersion;
+                    this.newVersionAvailable = true;
+                }else if (Constants.VERSION == newestVersion){
+                    if(Constants.BETA_VERSION == 0){ //you're using the current final version
+                        this.newVersion = Constants.VERSION;
+                        this.newVersionAvailable = false;
+                    }else{ //you're using a beta of the current final version
+                        this.newVersion = Constants.VERSION;
+                        this.newVersionAvailable = true;
+                        this.finalReleaseAvailable = true;
+                    }
+                }else if (Constants.VERSION > newestVersion) {
+                    if(Constants.BETA_VERSION == 0){ //you're using a newer final version
+                        this.newVersionAvailable = false;
+                        this.newVersion = Constants.VERSION;
+                    }else{
+                        if (newestBetaVersion == -1 || Constants.BETA_VERSION >= newestBetaVersion){ //you're using a beta of a newer final version
+                            this.newVersionAvailable = false;
+                            this.newVersion = Constants.VERSION;
+                        }else if (Constants.BETA_VERSION < newestBetaVersion){ //you're using an older beta of a newer final version
+                            this.newVersion = Constants.VERSION;
+                            this.newVersionAvailable = true;
+                            this.newBetaVersion = newestBetaVersion;
+                        }
+                    }
+                }
+            }
+            con.disconnect();
+
+        } catch(MalformedURLException mue) {
+            //System.err.println("the following url exception occured:" + mue);
+        }
+
+
+        return this.newVersionAvailable;
+    }
+
+
+}
diff --git a/edu/mit/wi/haploview/UpdateDisplayDialog.java b/edu/mit/wi/haploview/UpdateDisplayDialog.java
new file mode 100755
index 0000000..7ca0fd9
--- /dev/null
+++ b/edu/mit/wi/haploview/UpdateDisplayDialog.java
@@ -0,0 +1,84 @@
+package edu.mit.wi.haploview;
+
+import javax.swing.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.*;
+import java.io.IOException;
+import java.net.URL;
+
+public class UpdateDisplayDialog extends JDialog implements ActionListener, Constants{
+    static final long serialVersionUID = -5149693758436261851L;
+
+    public UpdateDisplayDialog(HaploView h, String title, UpdateChecker uc) {
+        super(h, title);
+
+        JPanel contents = new JPanel();
+        contents.setLayout(new BoxLayout(contents, BoxLayout.Y_AXIS));
+
+        Font bigguns = new Font("Default", Font.BOLD, 14);
+
+        JTextArea announceArea = new JTextArea();
+        announceArea.setFont(bigguns);
+        if (uc.getNewBetaVersion() != -1 && !uc.isFinalVersionAvailable()){
+            announceArea.append("A newer BETA version of Haploview is available: " + uc.getNewVersion() + "beta" + uc.getNewBetaVersion() + "\n");
+            announceArea.append("\n" + BETA_WEBSITE_STRING + "\n");
+        }else if (uc.isFinalVersionAvailable()){
+            announceArea.append("The final release of version " + uc.getNewVersion() + " is now available:\n");
+            announceArea.append("\n" + WEBSITE_STRING + "\n");
+        }else{
+            announceArea.append("A newer version of Haploview is available: " + uc.getNewVersion() + "\n");
+            announceArea.append("\n" + WEBSITE_STRING + "\n");
+        }
+        announceArea.setAlignmentX(Component.CENTER_ALIGNMENT);
+        announceArea.setEditable(false);
+        announceArea.setOpaque(false);
+        JPanel announcePanel = new JPanel();
+        announcePanel.add(announceArea);
+        JScrollPane changeScroller = null;
+
+        try {
+            JEditorPane changePane = new JEditorPane();
+            changePane.setEditable(false);
+            if (uc.getNewBetaVersion() != -1 && !uc.isFinalVersionAvailable()){
+                changePane.setPage(new URL("http://www.broad.mit.edu/mpg/haploview/uc/betachanges.html"));
+            }else{
+                changePane.setPage(new URL("http://www.broad.mit.edu/mpg/haploview/uc/changes.html"));
+            }
+            changePane.setOpaque(false);
+            changeScroller = new JScrollPane(changePane);
+            changeScroller.setPreferredSize(new Dimension(250,150));
+        } catch(IOException ioe) {
+            //if were here then we were able to check for an update, so well just show them a dialog
+            //without listing the changes
+        }
+
+        contents.add(announcePanel);
+        if(changeScroller != null) {
+            changeScroller.setAlignmentX(Component.CENTER_ALIGNMENT);
+            JPanel scrollHolder = new JPanel();
+            scrollHolder.add(changeScroller);
+            contents.add(scrollHolder);
+        }
+
+
+        JButton okButton = new JButton("OK");
+        okButton.addActionListener(this);
+        okButton.setAlignmentX(Component.CENTER_ALIGNMENT);
+        contents.add(okButton);
+
+
+
+        this.setContentPane(contents);
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if(command.equals("OK")) {
+            this.dispose();
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/Util.java b/edu/mit/wi/haploview/Util.java
new file mode 100755
index 0000000..54ecc23
--- /dev/null
+++ b/edu/mit/wi/haploview/Util.java
@@ -0,0 +1,28 @@
+package edu.mit.wi.haploview;
+
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.util.Locale;
+
+public abstract class Util {
+
+    public static String formatPValue(double pval){
+         DecimalFormat df;
+        //java truly sucks for simply restricting the number of sigfigs but still
+        //using scientific notation when appropriate
+        if (pval < 0.0001){
+            df = new DecimalFormat("0.0000E0", new DecimalFormatSymbols(Locale.US));
+        }else{
+            df = new DecimalFormat("0.0000", new DecimalFormatSymbols(Locale.US));
+        }
+        String formattedNumber =  df.format(pval, new StringBuffer(), new FieldPosition(NumberFormat.INTEGER_FIELD)).toString();
+        return formattedNumber;
+    }
+
+    public static double roundDouble (double d, int places){
+        double factor = Math.pow(10,places);
+        return Math.rint(d*factor)/factor;
+    }
+}
diff --git a/edu/mit/wi/haploview/association/AssociationResult.java b/edu/mit/wi/haploview/association/AssociationResult.java
new file mode 100755
index 0000000..3f4d0d6
--- /dev/null
+++ b/edu/mit/wi/haploview/association/AssociationResult.java
@@ -0,0 +1,262 @@
+package edu.mit.wi.haploview.association;
+
+import edu.mit.wi.haploview.Haplotype;
+import edu.mit.wi.haploview.Options;
+import edu.mit.wi.haploview.Constants;
+import edu.mit.wi.haploview.Util;
+import edu.mit.wi.pedfile.*;
+
+import java.text.NumberFormat;
+import java.util.Locale;
+import java.util.Vector;
+import java.util.Iterator;
+
+
+public abstract class AssociationResult implements Constants{
+    static NumberFormat nf = NumberFormat.getInstance(Locale.US);
+
+    //alleles contains Haplotype objects for the alleles of the current site
+    protected Vector alleles = new Vector();
+
+    //filteredAlleles contains the alleles which pass the frequency cutoff
+    protected Vector filteredAlleles = new Vector();
+
+    //chiSquares contains Doubles storing the chiSquare values for the alleles
+    protected Vector chiSquares;
+
+    //pValues contains Double storing the p-values for the alleles
+    protected Vector pValues;
+
+    //frequencyCutoff is the minimum frequency for an allele to have statistics calculated
+    protected double frequencyCutoff;
+
+    //name is the name
+    protected String name;
+
+    /**
+     *
+     * @return name of block or marker
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     *
+     * @param i allele number
+     * @return name of i-th allele
+     */
+    public String getAlleleName(int i) {
+        return ((Haplotype) filteredAlleles.get(i)).toString();
+    }
+
+    public String getNumericAlleleName(int i){
+        return ((Haplotype) filteredAlleles.get(i)).toNumericString();
+    }
+
+    public abstract String getDisplayName(int i);
+
+    /**
+     *
+     * @return number of alleles for this block or marker
+     */
+    public int getAlleleCount(){
+        return filteredAlleles.size();
+    }
+
+    public double getChiSquare(int i) {
+        if (chiSquares == null || i >= chiSquares.size()){
+            return 0;
+        }
+        double formattedCS = Util.roundDouble(((Double)chiSquares.get(i)).doubleValue(),3);
+        return formattedCS;
+    }
+
+    public abstract String getCountString(int i);
+
+    public abstract String getFreqString(int i);
+
+    public void filterByFrequency(double f) {
+        filteredAlleles.removeAllElements();
+        if(f >= 0 && f<=1) {
+            frequencyCutoff = f;
+            for(int i=0;i<alleles.size();i++) {
+                Haplotype curHap = (Haplotype) alleles.get(i);
+                if(curHap.getPercentage() >= frequencyCutoff) {
+                    filteredAlleles.add(curHap);
+                }
+            }
+        }
+        calculateChiSquares();
+    }
+
+    protected void calculateChiSquares() {
+        chiSquares = new Vector();
+        pValues = new Vector();
+        if (Options.getAssocTest() == ASSOC_TRIO) {
+            Iterator faitr = filteredAlleles.iterator();
+            while(faitr.hasNext()) {
+                if(Options.getTdtType() == TDT_STD) {
+                    Haplotype curHap = (Haplotype) faitr.next();
+                    double chiSq = Math.pow(curHap.getTransCount() - curHap.getUntransCount(),2) / (curHap.getTransCount() + curHap.getUntransCount());
+                    chiSquares.add(new Double(chiSq));
+                }else if (Options.getTdtType() == TDT_PAREN) {
+                    Haplotype curHap = (Haplotype) faitr.next();
+                    double[] counts = curHap.getDiscordantAlleleCounts();
+                    //statistic is [T+d+h+2g - (U+b+f+2c)]^2  /  [T+U+d+h+b+f+4(c+g)] distributed as a chi-square
+                    double numr =  Math.pow(curHap.getTransCount() + counts[3] + counts[7] + 2*counts[6]
+                            - (curHap.getUntransCount() + counts[1] + counts[5] + 2*counts[2]),2);
+                    double denom = (curHap.getTransCount() + curHap.getUntransCount() + counts[3] + counts[7] + counts[1] + counts[5] + 4*(counts[2] + counts[6])); 
+
+                    double chiSq = numr / denom;
+                    chiSquares.add(new Double(chiSq));
+                }
+            }
+        } else if(Options.getAssocTest() == ASSOC_CC) {
+            double caseSum =0;
+            double controlSum = 0;
+            Iterator aitr = alleles.iterator();
+            while(aitr.hasNext()) {
+                Haplotype curHap = (Haplotype) aitr.next();
+                caseSum += curHap.getCaseCount();
+                controlSum += curHap.getControlCount();
+            }
+
+            double chiSq;
+            double tempCaseSum, tempControlSum;
+            double totalSum = caseSum + controlSum;
+            Iterator faitr = filteredAlleles.iterator();
+            while(faitr.hasNext()) {
+                chiSq = 0;
+                Haplotype curHap = (Haplotype) faitr.next();
+                tempCaseSum = caseSum - curHap.getCaseCount();
+                tempControlSum = controlSum - curHap.getControlCount();
+
+                double nij = (caseSum * (curHap.getCaseCount() + curHap.getControlCount())) / totalSum;
+                chiSq += Math.pow(curHap.getCaseCount() - nij,2) / nij;
+
+                nij = (caseSum * (tempCaseSum + tempControlSum)) / totalSum;
+                chiSq += Math.pow(tempCaseSum - nij,2) / nij;
+
+                nij = (controlSum * (curHap.getCaseCount() + curHap.getControlCount())) / totalSum;
+                chiSq += Math.pow(curHap.getControlCount() - nij,2) / nij;
+
+                nij = (controlSum * (tempCaseSum + tempControlSum)) / totalSum;
+                chiSq += Math.pow(tempControlSum - nij,2) / nij;
+
+                chiSquares.add(new Double(chiSq));
+            }
+        }
+
+        for(int i=0;i<chiSquares.size();i++) {
+            pValues.add(new Double(MathUtil.gammq(.5,.5*(((Double)chiSquares.get(i)).doubleValue()))));
+        }
+    }
+
+    public Double getPValue(int i) {
+        if (pValues == null || i >= pValues.size()){
+            return new Double(0);
+        }
+        return new Double(Util.formatPValue(((Double)pValues.get(i)).doubleValue()));
+    }
+
+    public String getFreq(int i) {
+        double freq = ((Haplotype)alleles.get(i)).getPercentage();
+
+        if (freq < 0){
+            return ("");
+        }else{
+            nf.setMinimumFractionDigits(3);
+            nf.setMaximumFractionDigits(3);
+            return nf.format(freq);
+        }
+    }
+
+    protected static class TallyTrio {
+        int allele1=0, allele2=0;
+        int[][] counts = new int[2][2];
+        boolean tallyHet = true;
+
+        //for parenTDT
+        // values are {a b c d e f g h i} from matrix of values
+        int[] discordantAlleleCounts = new int[9];
+        int discordantTallied=0;
+
+        public void tallyTrioInd(byte alleleT, byte alleleU){
+            if(alleleT >= 5 && alleleU >= 5) {
+                if(tallyHet){
+                    counts[0][0]++;
+                    counts[1][1]++;
+                    counts[0][1]++;
+                    counts[1][0]++;
+                    this.tallyHet = false;
+                }
+                else {
+                    this.tallyHet = true;
+                }
+            }
+            else if( (alleleT != alleleU) && (alleleT!=0) && (alleleU!=0) ) {
+                if(allele1==0 && allele2==0 ) {
+                    allele1 = alleleT;
+                    allele2 = alleleU;
+                }
+
+                if(alleleT == allele1) {
+                    counts[0][0]++;
+                }
+                else if(alleleT==allele2) {
+                    counts[0][1]++;
+                }
+                if(alleleU == allele1){
+                    counts[1][0]++;
+                }
+                else if(alleleU == allele2) {
+                    counts[1][1]++;
+                }
+
+            }
+        }
+
+        public void tallyDiscordantParents(byte affected1, byte affected2, byte unaffected1, byte unaffected2) {
+            if(affected1 >= 5 && affected2 >= 5 && unaffected1 >= 5 && unaffected2 >=5) {
+                return;
+            }
+            if(affected1 == affected2 && unaffected1 == unaffected2 && affected1 == unaffected1){
+                return;
+            }
+            if(affected1 == 0 || affected2 == 0 || unaffected1 == 0 || unaffected2 == 0) {
+                return;
+            }
+            discordantTallied++;
+
+
+            if(affected1 != affected2 ) {
+                if(unaffected1 == allele1 && unaffected2 == allele1) {
+                    discordantAlleleCounts[1]++;
+                }else if (unaffected1 == allele2 && unaffected2 == allele2) {
+                    discordantAlleleCounts[7]++;
+                }
+            }else if(affected1 == allele1 && affected2 == allele1) {
+                if(unaffected1 != unaffected2 ) {
+                    discordantAlleleCounts[3]++;
+                }else if(unaffected1 == allele2 && unaffected2 == allele2) {
+                    discordantAlleleCounts[6]++;
+                }
+            }else if(affected1 == allele2 && affected2 == allele2) {
+                if(unaffected1 != unaffected2) {
+                    discordantAlleleCounts[5]++;
+                }else if(unaffected1 == allele1 && unaffected2 == allele1) {
+                    discordantAlleleCounts[2]++;
+                }
+            }
+        }
+
+        public int[] getDiscordantCountsAllele2() {
+            int[] counts = new int[9];
+            for(int i=0;i<9;i++) {
+                counts[i] = discordantAlleleCounts[8-i];
+            }
+            return counts;
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/association/AssociationTestSet.java b/edu/mit/wi/haploview/association/AssociationTestSet.java
new file mode 100755
index 0000000..14be62b
--- /dev/null
+++ b/edu/mit/wi/haploview/association/AssociationTestSet.java
@@ -0,0 +1,934 @@
+package edu.mit.wi.haploview.association;
+
+import edu.mit.wi.haploview.*;
+import edu.mit.wi.pedfile.PedFile;
+import edu.mit.wi.pedfile.Individual;
+import edu.mit.wi.pedfile.Family;
+import edu.mit.wi.pedfile.PedFileException;
+
+import java.util.*;
+import java.io.*;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+public class AssociationTestSet implements Constants{
+
+    private Vector tests;
+    private Vector results;
+    private HashSet whitelist;
+    private Vector filterAlleles;
+    private boolean permTests = false;
+
+    public AssociationTestSet(){
+        results = new Vector();
+        whitelist = new HashSet();
+    }
+
+    public AssociationTestSet(PedFile pf, Vector permute, Vector permuteDiscPar, Vector snpsToBeTested) throws PedFileException{
+        whitelist = new HashSet();
+
+        if (Options.getAssocTest() == ASSOC_TRIO){
+            if(Options.getTdtType() == TDT_STD) {
+                buildTrioSet(pf, permute, new TreeSet(snpsToBeTested));
+            }else if(Options.getTdtType() == TDT_PAREN) {
+                buildParenTDTTrioSet(pf,permute,permuteDiscPar,new TreeSet(snpsToBeTested));
+            }
+        }else if (Options.getAssocTest() == ASSOC_CC){
+            buildCCSet(pf, permute, new TreeSet(snpsToBeTested));
+        }
+    }
+
+    private void buildCCSet(PedFile pf, Vector affectedStatus, TreeSet snpsToBeTested){
+        ArrayList results = new ArrayList();
+
+        int numMarkers = Chromosome.getUnfilteredSize();
+
+        Vector indList = pf.getUnrelatedIndividuals();
+        int numInds = indList.size();
+
+        if(affectedStatus == null || affectedStatus.size() != indList.size()) {
+            affectedStatus = new Vector(indList.size());
+            for(int i=0;i<indList.size();i++) {
+                Individual tempInd = ((Individual)indList.get(i));
+                affectedStatus.add(new Integer(tempInd.getAffectedStatus()));
+            }
+        }
+
+
+        boolean[] useable = new boolean[indList.size()];
+        Arrays.fill(useable, false);
+
+        //this loop determines who is eligible to be used for the case/control association test
+        for(int i=0;i<useable.length;i++) {
+
+            Individual tempInd = ((Individual)indList.get(i));
+            Family tempFam = pf.getFamily(tempInd.getFamilyID());
+
+            //need to check to make sure we don't include both parents and kids of trios
+            //so, we only set useable[i] to true if Individual at index i is not the child of a trio in the indList
+            if (!(tempFam.containsMember(tempInd.getMomID()) &&
+                    tempFam.containsMember(tempInd.getDadID()))){
+                useable[i] = true;
+            } else{
+                try{
+                    if (!(indList.contains(tempFam.getMember(tempInd.getMomID())) ||
+                            indList.contains(tempFam.getMember(tempInd.getDadID())))){
+                        useable[i] = true;
+                    }
+                }catch (PedFileException pfe){
+                }
+            }
+        }
+
+        for (int i = 0; i < numMarkers; i++){
+            SNP currentMarker = Chromosome.getUnfilteredMarker(i);
+            if (snpsToBeTested.contains(currentMarker)){
+                byte allele1 = 0, allele2 = 0;
+                int[][] counts = new int[2][2];
+                Individual currentInd;
+                for (int j = 0; j < numInds; j++){
+                    if(useable[j]) {
+                        currentInd = (Individual)indList.get(j);
+                        if (currentInd.getZeroed(i)){
+                            continue;
+                        }
+                        int cc = ((Integer)affectedStatus.get(j)).intValue();
+
+                        if (cc == 0) continue;
+                        if (cc == 2) cc = 0;
+                        byte a1 = currentInd.getAllele(i,0);
+                        byte a2 = currentInd.getAllele(i,1);
+
+                        if (a1 >= 5 && a2 >= 5){
+                            counts[cc][0]++;
+                            counts[cc][1]++;
+                            if (allele1 == 0){
+                                allele1 = (byte)(a1 - 4);
+                                allele2 = (byte)(a2 - 4);
+                            }
+                        }else{
+                            //seed the alleles as soon as they're found
+                            if (allele1 == 0){
+                                allele1 = a1;
+                                if (a1 != a2){
+                                    allele2 = a2;
+                                }
+                            }else if (allele2 == 0){
+                                if (a1 != allele1){
+                                    allele2 = a1;
+                                }else if (a2 != allele1){
+                                    allele2 = a2;
+                                }
+                            }
+
+                            if (a1 != 0){
+                                if (a1 == allele1){
+                                    counts[cc][0] ++;
+                                }else{
+                                    counts[cc][1] ++;
+                                }
+                            }
+                            if (currentInd.getGender() == 2 || !Chromosome.getDataChrom().equalsIgnoreCase("chrx")){
+                                if (a2 != 0){
+                                    if (a2 == allele1){
+                                        counts[cc][0]++;
+                                    }else{
+                                        counts[cc][1]++;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                int[] g1 = {allele1};
+                int[] g2 = {allele2};
+                int[] m  = {i};
+
+                Haplotype thisSNP1 = new Haplotype(g1, 0, m, null);
+                thisSNP1.setCaseCount(counts[0][0]);
+                thisSNP1.setControlCount(counts[1][0]);
+                Haplotype thisSNP2 = new Haplotype(g2, 0, m, null);
+                thisSNP2.setCaseCount(counts[0][1]);
+                thisSNP2.setControlCount(counts[1][1]);
+
+
+                Haplotype[] daBlock = {thisSNP1, thisSNP2};
+                results.add(new MarkerAssociationResult(daBlock, currentMarker.getDisplayName(), currentMarker));
+            }
+        }
+
+        this.results = new Vector(results);
+    }
+
+    private void buildTrioSet(PedFile pf, Vector permuteInd, TreeSet snpsToBeTested) throws PedFileException{
+        Vector results = new Vector();
+
+        Vector indList = pf.getAllIndividuals();
+
+        if(permuteInd == null || permuteInd.size() != indList.size()) {
+            permuteInd = new Vector();
+            for (int i = 0; i < indList.size(); i++){
+                permuteInd.add(Boolean.FALSE);
+            }
+        }
+
+        int numMarkers = Chromosome.getUnfilteredSize();
+        for (int i = 0; i < numMarkers; i++){
+            SNP currentMarker = Chromosome.getUnfilteredMarker(i);
+            if (snpsToBeTested.contains(currentMarker)){
+                Individual currentInd;
+                Family currentFam;
+                AssociationResult.TallyTrio tt = new AssociationResult.TallyTrio();
+                for (int j = 0; j < indList.size(); j++){
+                    currentInd = (Individual)indList.elementAt(j);
+                    currentFam = pf.getFamily(currentInd.getFamilyID());
+                    if (currentFam.containsMember(currentInd.getMomID()) &&
+                            currentFam.containsMember(currentInd.getDadID()) &&
+                            currentInd.getAffectedStatus() == 2){
+                        //if he has both parents, and is affected, we can get a transmission
+                        Individual mom = currentFam.getMember(currentInd.getMomID());
+                        Individual dad = currentFam.getMember(currentInd.getDadID());
+                        if(currentInd.getZeroed(i) || dad.getZeroed(i) || mom.getZeroed(i)) {
+                            continue;
+                        }
+                        byte kid1 = currentInd.getAllele(i,0);
+                        byte kid2 = currentInd.getAllele(i,1);
+                        byte dad1 = dad.getAllele(i,0);
+                        byte dad2 = dad.getAllele(i,1);
+                        byte mom1 = mom.getAllele(i,0);
+                        byte mom2 = mom.getAllele(i,1);
+                        byte momT=0, momU=0, dadT=0, dadU=0;
+
+                        if (kid1 == 0 || kid2 == 0 || dad1 == 0 || dad2 == 0 || mom1 == 0 || mom2 == 0) {
+                            continue;
+                        }
+
+                        if (kid1 == kid2) {
+                            //kid homozygous
+                            if (dad1 == kid1) {
+                                dadT = dad1;
+                                dadU = dad2;
+                            } else {
+                                dadT = dad2;
+                                dadU = dad1;
+                            }
+
+                            if (mom1 == kid1) {
+                                momT = mom1;
+                                momU = mom2;
+                            } else {
+                                momT = mom2;
+                                momU = mom1;
+                            }
+                        } else {
+                            if (dad1 == dad2 && mom1 != mom2) {
+                                //dad hom mom het
+                                dadT = dad1;
+                                dadU = dad2;
+                                if (kid1 == dad1) {
+                                    momT = kid2;
+                                    momU = kid1;
+                                } else {
+                                    momT = kid1;
+                                    momU = kid2;
+                                }
+                            } else if (mom1 == mom2 && dad1 != dad2) {
+                                //dad het mom hom
+                                momT = mom1;
+                                momU = mom2;
+                                if (kid1 == mom1) {
+                                    dadT = kid2;
+                                    dadU = kid1;
+                                } else {
+                                    dadT = kid1;
+                                    dadU = kid2;
+                                }
+                            } else if (dad1 == dad2 && mom1 == mom2) {
+                                //mom & dad hom
+                                dadT = dad1;
+                                dadU = dad1;
+                                momT = mom1;
+                                momU = mom1;
+                            } else {
+                                //everybody het
+                                dadT = (byte)(4+dad1);
+                                dadU = (byte)(4+dad2);
+                                momT = (byte)(4+mom1);
+                                momU = (byte)(4+mom2);
+                            }
+
+                        }
+                        if(((Boolean)permuteInd.get(j)).booleanValue()) {
+                            tt.tallyTrioInd(dadU, dadT);
+                            tt.tallyTrioInd(momU, momT);
+                        } else {
+                            tt.tallyTrioInd(dadT, dadU);
+                            tt.tallyTrioInd(momT, momU);
+                        }
+                    }
+                }
+                int[] g1 = {tt.allele1};
+                int[] g2 = {tt.allele2};
+                int[] m  = {i};
+
+                Haplotype thisSNP1 = new Haplotype(g1, 0, m, null);
+                thisSNP1.setTransCount(tt.counts[0][0]);
+                thisSNP1.setUntransCount(tt.counts[1][0]);
+                Haplotype thisSNP2 = new Haplotype(g2, 0, m, null);
+                thisSNP2.setTransCount(tt.counts[0][1]);
+                thisSNP2.setUntransCount(tt.counts[1][1]);
+
+                Haplotype[] daBlock = {thisSNP1, thisSNP2};
+                results.add(new MarkerAssociationResult(daBlock, currentMarker.getDisplayName(), currentMarker));
+            }
+        }
+        this.results = results;
+    }
+
+    private void buildParenTDTTrioSet(PedFile pf, Vector permuteInd, Vector permuteDiscPar, TreeSet snpsToBeTested) throws PedFileException{
+        Vector results = new Vector();
+        //TODO: implement for X chrom
+
+        Vector indList = pf.getAllIndividuals();
+
+        if(permuteInd == null || permuteInd.size() != indList.size()) {
+            permuteInd = new Vector();
+            for (int i = 0; i < indList.size(); i++){
+                permuteInd.add(Boolean.FALSE);
+            }
+        }
+
+        if(permuteDiscPar == null || permuteDiscPar.size() != indList.size()){
+            permuteDiscPar = new Vector();
+            for (int i = 0; i < indList.size(); i++){
+                permuteDiscPar.add(Boolean.FALSE);
+            }
+        }
+
+        int numMarkers = Chromosome.getUnfilteredSize();
+        for (int i = 0; i < numMarkers; i++){
+            SNP currentMarker = Chromosome.getUnfilteredMarker(i);
+            if (snpsToBeTested.contains(currentMarker)){
+                int discordantNotTallied=0;
+                int discordantTallied = 0;
+                Individual currentInd;
+                Family currentFam;
+                HashSet usedParents = new HashSet();
+
+                AssociationResult.TallyTrio tt = new AssociationResult.TallyTrio();
+                for (int j = 0; j < indList.size(); j++){
+                    currentInd = (Individual)indList.elementAt(j);
+                    currentFam = pf.getFamily(currentInd.getFamilyID());
+                    if (currentFam.containsMember(currentInd.getMomID()) &&
+                            currentFam.containsMember(currentInd.getDadID()) &&
+                            currentInd.getAffectedStatus() == 2){
+                        //if he has both parents, and is affected, we can get a transmission
+                        Individual mom = currentFam.getMember(currentInd.getMomID());
+                        Individual dad = currentFam.getMember(currentInd.getDadID());
+
+                         if(currentInd.getZeroed(i) || dad.getZeroed(i) || mom.getZeroed(i)) {
+                            continue;
+                        }
+                        byte kid1 = currentInd.getAllele(i,0);
+                        byte kid2 = currentInd.getAllele(i,1);
+                        byte dad1 = dad.getAllele(i,0);
+                        byte dad2 = dad.getAllele(i,1);
+                        byte mom1 = mom.getAllele(i,0);
+                        byte mom2 = mom.getAllele(i,1);
+                        byte momT=0, momU=0, dadT=0, dadU=0;
+                        if (kid1 == 0 || kid2 == 0 || dad1 == 0 || dad2 == 0 || mom1 == 0 || mom2 == 0) {
+                            continue;
+                        } else if (kid1 == kid2) {
+                            //kid homozygous
+                            if (dad1 == kid1) {
+                                dadT = dad1;
+                                dadU = dad2;
+                            } else {
+                                dadT = dad2;
+                                dadU = dad1;
+                            }
+
+                            if (mom1 == kid1) {
+                                momT = mom1;
+                                momU = mom2;
+                            } else {
+                                momT = mom2;
+                                momU = mom1;
+                            }
+                        } else {
+                            if (dad1 == dad2 && mom1 != mom2) {
+                                //dad hom mom het
+                                dadT = dad1;
+                                dadU = dad2;
+                                if (kid1 == dad1) {
+                                    momT = kid2;
+                                    momU = kid1;
+                                } else {
+                                    momT = kid1;
+                                    momU = kid2;
+                                }
+                            } else if (mom1 == mom2 && dad1 != dad2) {
+                                //dad het mom hom
+                                momT = mom1;
+                                momU = mom2;
+                                if (kid1 == mom1) {
+                                    dadT = kid2;
+                                    dadU = kid1;
+                                } else {
+                                    dadT = kid1;
+                                    dadU = kid2;
+                                }
+                            } else if (dad1 == dad2 && mom1 == mom2) {
+                                //mom & dad hom
+                                dadT = dad1;
+                                dadU = dad1;
+                                momT = mom1;
+                                momU = mom1;
+                            } else {
+                                //everybody het
+                                dadT = (byte)(4+dad1);
+                                dadU = (byte)(4+dad2);
+                                momT = (byte)(4+mom1);
+                                momU = (byte)(4+mom2);
+                            }
+                        }
+
+                        if(((Boolean)permuteInd.get(j)).booleanValue()) {
+                            tt.tallyTrioInd(dadU, dadT);
+                            tt.tallyTrioInd(momU, momT);
+                        } else {
+                            tt.tallyTrioInd(dadT, dadU);
+                            tt.tallyTrioInd(momT, momU);
+                        }
+                        if(mom.getAffectedStatus() != dad.getAffectedStatus()) {
+                            //discordant parental phenotypes
+                            if(usedParents.contains(mom) || usedParents.contains(dad)) {
+                                continue;
+                            }
+                            int momAffected = mom.getAffectedStatus();
+                            int dadAffected = dad.getAffectedStatus();
+                            if(permuteDiscPar.get(j) != null) {
+                                if(((Boolean)permuteDiscPar.get(j)).booleanValue()) {
+                                    momAffected = dad.getAffectedStatus();
+                                    dadAffected = mom.getAffectedStatus();
+                                }
+                            }
+                            if(!(dad1 == mom1 && dad2 == mom2) && !(dad1 == mom2 && dad2 == mom1)) {
+                                if(momAffected == 2) {
+                                    tt.tallyDiscordantParents(momT,momU,dadT,dadU);
+
+                                } else if(dadAffected == 2) {
+                                    tt.tallyDiscordantParents(dadT,dadU,momT,momU);
+                                }
+                                discordantTallied++;
+                            }else {
+                                discordantNotTallied++;
+                            }
+                        }
+                        usedParents.add(mom);
+                        usedParents.add(dad);
+
+                    }
+                }
+                int[] g1 = {tt.allele1};
+                int[] g2 = {tt.allele2};
+                int[] m  = {i};
+
+                Haplotype thisSNP1 = new Haplotype(g1, 0, m, null);
+                thisSNP1.setTransCount(tt.counts[0][0]);
+                thisSNP1.setUntransCount(tt.counts[1][0]);
+                thisSNP1.setDiscordantAlleleCounts(tt.discordantAlleleCounts);
+                Haplotype thisSNP2 = new Haplotype(g2, 0, m, null);
+                thisSNP2.setTransCount(tt.counts[0][1]);
+                thisSNP2.setUntransCount(tt.counts[1][1]);
+                thisSNP2.setDiscordantAlleleCounts(tt.getDiscordantCountsAllele2());
+
+
+                Haplotype[] daBlock = {thisSNP1, thisSNP2};
+                results.add(new MarkerAssociationResult(daBlock, currentMarker.getDisplayName(), currentMarker));
+
+            }
+        }
+        this.results = results;
+    }
+
+    public AssociationTestSet(Haplotype[][] haplos, Vector names){
+        //use this constructor for default hap tests so you can filter by display freq
+        whitelist = new HashSet();
+        Vector results = new Vector();
+        if (haplos != null){
+            for (int i = 0; i < haplos.length; i++){
+                String blockname;
+                if (names == null){
+                    blockname = "Block " + (i+1);
+                }else{
+                    blockname = (String) names.get(i);
+                }
+                results.add(new HaplotypeAssociationResult(haplos[i], Options.getHaplotypeDisplayThreshold(),blockname));
+            }
+        }
+        this.results = results;
+    }
+
+    public AssociationTestSet(Haplotype[][] haplos, Vector names, Vector alleles) throws HaploViewException{
+        //use this constructor for custom tests so you can filter on alleles
+        whitelist = new HashSet();
+        this.filterAlleles = alleles;
+        Vector results = new Vector();
+        if (haplos != null){
+            boolean missing = false;
+            Vector missingAlleles =new Vector();
+            for (int i = 0; i < haplos.length; i++){
+                try {
+
+                    String blockname;
+                    if (names == null){
+                        blockname = "Block " + (i+1);
+                    }else{
+                        blockname = (String) names.get(i);
+                    }
+                    results.add(new HaplotypeAssociationResult(haplos[i], (String)alleles.get(i),blockname));
+
+                }catch(HaploViewException hve) {
+                    missing = true;
+                    missingAlleles.add((String)names.get(i) + "\t " + (String)alleles.get(i));
+
+                }
+            }
+
+            if(missing) {
+                for(int i=0;i<missingAlleles.size();i++) {
+                    System.out.println(missingAlleles.get(i));
+
+                }
+                throw new HaploViewException("alleles missing");
+            }
+        }
+        this.results = results;
+    }
+
+    public AssociationTestSet(String name) throws IOException, HaploViewException{
+        tests = new Vector();
+        whitelist = new HashSet();
+        BufferedReader in;
+        try{
+            URL inURL = new URL(name);
+            in = new BufferedReader(new InputStreamReader(inURL.openStream()));
+        }catch(MalformedURLException mfe){
+            File testListFile = new File(name);
+            if (testListFile.length() < 1){
+                throw new HaploViewException("Custom tests file is empty or missing.");
+            }
+            in = new BufferedReader(new FileReader(testListFile));
+        }catch(IOException ioe){
+            throw new HaploViewException("Could not connect to " + name);
+        }
+
+        String currentLine;
+        int lineCount = 0;
+
+        //we need to be able to identify marker index by name
+        Hashtable indicesByName = new Hashtable();
+        Iterator mitr = Chromosome.getAllMarkers().iterator();
+        int count = 0;
+        while (mitr.hasNext()){
+            SNP n = (SNP) mitr.next();
+            indicesByName.put(n.getDisplayName(),new Integer(count));
+            count++;
+        }
+
+        while ((currentLine = in.readLine()) != null){
+            lineCount++;
+            //first determine if a tab specifies a specific allele for a multi-marker test
+            StringTokenizer st = new StringTokenizer(currentLine, "\t");
+            String markerNames;
+            String alleles;
+            if (st.countTokens() == 0){
+                //skip blank lines
+                continue;
+            }else if (st.countTokens() == 1){
+                //this is just a list of markers
+                markerNames = st.nextToken();
+                //check to make sure somebody didn't try uploading a tests file with multimarker
+                //tests but no alleles:
+                StringTokenizer test = new StringTokenizer(markerNames,", ");
+                if (test.countTokens() > 1){
+                    throw new HaploViewException("Multi-marker tests in tests file appear to be missing alleles.");
+                }
+                alleles = null;
+            }else if (st.countTokens() == 2){
+                //this has markers and alleles
+                markerNames = st.nextToken();
+                alleles = st.nextToken();
+            }else{
+                //this is *!&#ed up
+                markerNames = null;
+                alleles = null;
+                throw new HaploViewException("Format error on line " + lineCount + " of tests file.");
+            }
+
+            StringTokenizer mst = new StringTokenizer(markerNames, ", ");
+            AssociationTest t = new AssociationTest();
+            while (mst.hasMoreTokens()) {
+                String currentToken = mst.nextToken();
+                if (!indicesByName.containsKey(currentToken)){
+                    throw new HaploViewException("I don't know anything about marker " + currentToken +
+                            " in custom tests file.");
+                }
+
+                Integer nt = (Integer) indicesByName.get(currentToken);
+                t.addMarker(nt);
+            }
+            tests.add(t);
+
+            if (alleles != null){
+                //we have alleles
+                StringBuffer asb = new StringBuffer();
+                StringTokenizer ast = new StringTokenizer(alleles, ", ");
+                if (ast.countTokens() != t.getNumMarkers()){
+                    throw new HaploViewException("Allele and marker name mismatch on line " + lineCount + " of tests file.");
+                }
+                while (ast.hasMoreTokens()){
+                    asb.append(ast.nextToken());
+                }
+                t.specifyAllele(asb.toString());
+            }
+        }
+
+    }
+
+    public void runFileTests(HaploData theData, Vector inputSNPResults) throws HaploViewException {
+        Vector res = new Vector();
+        if(tests == null || theData == null) {
+            return;
+        }
+
+        Vector blocks = new Vector();
+        Vector names = new Vector();
+        Vector alleles = new Vector();
+        Hashtable blockHash = new Hashtable();
+        int multiMarkerTestcount =0;
+
+        for(int i=0;i<tests.size();i++) {
+            //first go through and get all the multimarker tests to package up to hand to theData.generateHaplotypes()
+            AssociationTest currentTest = (AssociationTest) tests.get(i);
+            if(currentTest.getNumMarkers() > 1) {
+                names.add(currentTest.getName());
+                alleles.add(currentTest.getAllele());
+                if(!blockHash.containsKey(currentTest)){
+                    blocks.add(currentTest.getFilteredMarkerArray());
+
+                    blockHash.put(currentTest,new Integer(blocks.size()-1));
+                }
+                multiMarkerTestcount++;
+
+            }
+        }
+
+        this.filterAlleles = alleles;
+        Haplotype[][] blockHaps = theData.generateHaplotypes(blocks, permTests);
+
+        Haplotype[][] realBlockHaps = new Haplotype[multiMarkerTestcount][];
+        int multiMarkerCountTemp=0;
+        for(int i=0;i<tests.size();i++) {
+            AssociationTest currentTest = (AssociationTest) tests.get(i);
+            if(currentTest.getNumMarkers() > 1) {
+                realBlockHaps[multiMarkerCountTemp] = blockHaps[((Integer)blockHash.get(currentTest)).intValue()];
+                multiMarkerCountTemp++;
+            }
+        }
+
+
+        Vector blockResults = new AssociationTestSet(realBlockHaps, names, alleles).getResults();
+        Iterator britr = blockResults.iterator();
+
+        for (int i = 0; i < tests.size(); i++){
+            AssociationTest currentTest = (AssociationTest) tests.get(i);
+            if(currentTest.getNumMarkers() > 1) {
+                //grab the next block result from above
+                HaplotypeAssociationResult har = (HaplotypeAssociationResult) britr.next();
+                res.add(har);
+            }else if (currentTest.getNumMarkers() == 1){
+                //grab appropriate single marker result.
+                res.add(inputSNPResults.get(currentTest.getMarkerArray()[0]));
+            }
+        }
+
+        results =  res;
+    }
+
+    public Vector getResults() {
+        return results;
+    }
+
+    public Vector getFilteredResults(){
+        //return the results but without any single snps which are filtered out.
+        Vector filt = new Vector();
+
+        TreeMap unFilteredMarkers = new TreeMap();
+        for (int i = 0; i < Chromosome.getSize(); i++){
+            unFilteredMarkers.put(Chromosome.getMarker(i), null);
+        }
+
+        Iterator itr = results.iterator();
+        while (itr.hasNext()){
+            Object o = itr.next();
+            if (o instanceof HaplotypeAssociationResult){
+                filt.add(o);
+            }else{
+                if (unFilteredMarkers.containsKey(((MarkerAssociationResult)o).getSnp())){
+                    //only add it if it's not filtered.
+                    filt.add(o);
+                }
+            }
+        }
+
+        return filt;
+    }
+
+    public Vector getMarkerAssociationResults(){
+        Vector ret = new Vector();
+
+        Iterator itr = results.iterator();
+        while (itr.hasNext()){
+            Object o = itr.next();
+            if (o instanceof MarkerAssociationResult){
+                ret.add(o);
+            }
+        }
+
+        return ret;
+    }
+
+    public Vector getHaplotypeAssociationResults(){
+        Vector ret = new Vector();
+
+        Iterator itr = results.iterator();
+        while (itr.hasNext()){
+            Object o = itr.next();
+            if (o instanceof HaplotypeAssociationResult){
+                ret.add(o);
+            }
+        }
+
+        return ret;
+    }
+
+    public Vector getFilterAlleles() {
+        return filterAlleles;
+    }
+
+    public HashSet getWhitelist() {
+        return whitelist;
+    }
+
+    public void cat(AssociationTestSet ats){
+        if (ats != null && ats.getResults() != null){
+            results.addAll(ats.getResults());
+        }
+    }
+
+    public boolean isCustom() {
+        return tests != null;
+    }
+
+    class AssociationTest {
+        Vector markers;
+        String allele;
+
+        public AssociationTest() {
+            markers = new Vector();
+        }
+
+        void addMarker(Integer m) {
+            if(m != null) {
+                markers.add(m);
+            }
+            whitelist.add(Chromosome.getUnfilteredMarker(m.intValue()));
+        }
+
+        int getNumMarkers() {
+            return markers.size();
+        }
+
+        int[] getMarkerArray() {
+            int[] tempArray = new int[markers.size()];
+
+            for(int i =0; i<tempArray.length;i++) {
+                tempArray[i] = ((Integer)markers.get(i)).intValue();
+            }
+            return tempArray;
+        }
+
+        int[] getFilteredMarkerArray(){
+            //it is important that none of the markers in any of the cust blocks have been filtered or else this
+            //method chokes.
+            int[] tempArray = new int[markers.size()];
+            for(int i = 0; i < tempArray.length; i++){
+                tempArray[i] = Chromosome.filterIndex[((Integer)markers.get(i)).intValue()];
+            }
+            return tempArray;
+        }
+
+        String getName(){
+            StringBuffer sb = new StringBuffer();
+            for (int i = 0; i < markers.size() - 1; i++){
+                sb.append(Chromosome.getUnfilteredMarker(((Integer)markers.get(i)).intValue()).getDisplayName());
+                sb.append(",");
+            }
+            sb.append(Chromosome.getUnfilteredMarker(((Integer)markers.get(markers.size()-1)).intValue()).getDisplayName());
+
+            return sb.toString();
+        }
+
+        public void specifyAllele(String s) {
+            allele = s;
+        }
+
+        public String getAllele() {
+            return allele;
+        }
+
+        public boolean equals(Object o) {
+            if(o instanceof AssociationTest) {
+                AssociationTest at = (AssociationTest) o;
+                if(markers.equals(at.markers) && allele.equals(at.allele)){
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public int hashCode() {
+            return markers.hashCode() + allele.hashCode();
+        }
+    }
+
+    public void saveResultsToText(File outputFile) throws IOException{
+        if(results == null) {
+            return;
+        }
+
+        if(Options.getAssocTest() != ASSOC_TRIO && Options.getAssocTest() != ASSOC_CC) {
+            return;
+        }
+
+        FileWriter fw;
+        fw = new FileWriter(outputFile);
+
+        StringBuffer result = new StringBuffer();
+        if(Options.getAssocTest() == ASSOC_TRIO) {
+            result.append("Test\tAllele\tFreq.\tT:U\tChi Square\tP Value\n");
+        } else if(Options.getAssocTest() == ASSOC_CC) {
+            result.append("Test\tAllele\tFreq.\tCase, Control Ratios\tChi Square\tP Value\n");
+        }
+
+        for (int i = 0; i < results.size(); i++){
+            AssociationResult ar = (AssociationResult) results.elementAt(i);
+            for (int j = 0; j < ar.getAlleleCount(); j++){
+                result.append(ar.getName()).append("\t");
+                if (ar instanceof MarkerAssociationResult){
+                    result.append(((MarkerAssociationResult)ar).getOverTransmittedAllele()).append("\t");
+                    result.append("\t");
+                }else{
+                    result.append(ar.getAlleleName(j)).append("\t");
+                    result.append(ar.getFreq(j)).append("\t");
+                }
+                result.append(ar.getCountString(j)).append("\t");
+                result.append(ar.getChiSquare(j)).append("\t");
+                result.append(ar.getPValue(j)).append("\n");
+
+                if (ar instanceof MarkerAssociationResult){
+                    //only show one line for SNPs instead of one line per allele
+                    break;
+                }
+            }
+        }
+
+        fw.write(result.toString().toCharArray());
+        fw.close();
+    }
+
+    public void saveHapsToText(File outputFile) throws IOException{
+        if(results == null) {
+            return;
+        }
+
+        if(Options.getAssocTest() != ASSOC_TRIO && Options.getAssocTest() != ASSOC_CC) {
+            return;
+        }
+
+        FileWriter fw;
+        fw = new FileWriter(outputFile);
+
+        StringBuffer result = new StringBuffer();
+        if(Options.getAssocTest() == ASSOC_TRIO) {
+            result.append("Block\tHaplotype\tFreq.\tT:U\tChi Square\tP Value\n");
+        } else if(Options.getAssocTest() == ASSOC_CC) {
+            result.append("Block\tHaplotype\tFreq.\tCase, Control Ratio Counts\tCase,Control Frequencies\tChi Square\tP Value\n");
+        }
+
+        for (int i = 0; i < results.size(); i++){
+            if (results.elementAt(i) instanceof HaplotypeAssociationResult){
+                HaplotypeAssociationResult ar = (HaplotypeAssociationResult) results.elementAt(i);
+                result.append("Block " + (i+1)).append("\n");
+                for (int j = 0; j < ar.getAlleleCount(); j++){
+                    result.append(ar.getAlleleName(j)).append("\t");
+                    result.append(ar.getFreq(j)).append("\t");
+                    result.append(ar.getCountString(j)).append("\t");
+                    if (Options.getAssocTest() == ASSOC_CC){
+                        result.append(ar.getFreqString(j)).append("\t");
+                    }
+                    result.append(ar.getChiSquare(j)).append("\t");
+                    result.append(ar.getPValue(j)).append("\n");
+                }
+            }
+        }
+
+        fw.write(result.toString().toCharArray());
+        fw.close();
+    }
+
+    public void saveSNPsToText(File outputFile) throws IOException{
+        if(results == null) {
+            return;
+        }
+
+        if(Options.getAssocTest() != ASSOC_TRIO && Options.getAssocTest() != ASSOC_CC) {
+            return;
+        }
+
+        FileWriter fw;
+        fw = new FileWriter(outputFile);
+
+        StringBuffer result = new StringBuffer();
+        if(Options.getAssocTest() == ASSOC_TRIO) {
+            result.append("#\tName\tOvertransmitted\tT:U\tChi square\tP value\n");
+        } else if(Options.getAssocTest() == ASSOC_CC) {
+            result.append("#\tName\tAssoc Allele\tCase,Control Ratio Counts\tCase,Control Frequencies\tChi square\tP value\n");
+        }
+
+        //only output assoc results for markers which werent filtered
+        for(int i=0;i<Chromosome.getSize();i++) {
+            if (results.get(Chromosome.realIndex[i]) instanceof MarkerAssociationResult){
+                MarkerAssociationResult currentResult = (MarkerAssociationResult) results.get(Chromosome.realIndex[i]);
+                result.append((Chromosome.realIndex[i] + 1)).append("\t");
+                result.append(currentResult.getName()).append("\t");
+                result.append(currentResult.getOverTransmittedAllele()).append("\t");
+                result.append(currentResult.getCountString()).append("\t");
+                if (Options.getAssocTest() == ASSOC_CC){
+                    result.append(currentResult.getFreqString()).append("\t");
+                }
+                result.append(currentResult.getChiSquare(0)).append("\t");
+                result.append(currentResult.getPValue(0)).append("\n");
+            }
+        }
+
+        fw.write(result.toString().toCharArray());
+        fw.close();
+    }
+
+    public void setPermTests(boolean permTests) {
+        this.permTests = permTests;
+    }
+}
diff --git a/edu/mit/wi/haploview/association/CustomAssocPanel.java b/edu/mit/wi/haploview/association/CustomAssocPanel.java
new file mode 100755
index 0000000..898823f
--- /dev/null
+++ b/edu/mit/wi/haploview/association/CustomAssocPanel.java
@@ -0,0 +1,83 @@
+package edu.mit.wi.haploview.association;
+
+import edu.mit.wi.haploview.*;
+
+import javax.swing.*;
+import java.util.Vector;
+import java.util.Iterator;
+import java.awt.*;
+
+public class CustomAssocPanel extends HaploviewTab implements Constants {
+    static final long serialVersionUID = -4371438707739822028L;
+
+    AssociationTestSet testSet;
+
+    public CustomAssocPanel(AssociationTestSet ats){
+        testSet = ats;
+
+        Vector colNames = new Vector();
+        colNames.add("Test");
+        colNames.add("Allele");
+        colNames.add("Frequency");
+        if (Options.getAssocTest() == ASSOC_TRIO){
+            colNames.add("T:U");
+        }else{
+            colNames.add("Case, Control Ratios");
+        }
+        colNames.add("Chi Square");
+        colNames.add("p value");
+
+        Vector data = new Vector();
+        Vector results = ats.getResults();
+        Iterator vitr = results.iterator();
+        while (vitr.hasNext()){
+            AssociationResult ar = (AssociationResult) vitr.next();
+            for (int i = 0; i < ar.getAlleleCount(); i++){
+                Vector fields = new Vector();
+                fields.add(ar.getName());
+                if (ar instanceof MarkerAssociationResult){
+                    fields.add(((MarkerAssociationResult)ar).getOverTransmittedAllele());
+                    fields.add("");
+                }else{
+                    fields.add(ar.getAlleleName(i));
+                    fields.add(ar.getFreq(i));
+                }
+                fields.add(ar.getCountString(i));
+                fields.add(String.valueOf(ar.getChiSquare(i)));
+                fields.add(ar.getPValue(i));
+                data.add(fields);
+                if (ar instanceof MarkerAssociationResult){
+                    //only show one line for SNPs instead of one line per allele
+                    break;
+                }
+            }
+        }
+
+        BasicTableModel btm = new BasicTableModel(colNames, data);
+        JTable jt = new JTable(btm);
+        jt.getColumnModel().getColumn(0).setPreferredWidth(100);
+        jt.getColumnModel().getColumn(1).setPreferredWidth(100);
+        jt.getColumnModel().getColumn(2).setPreferredWidth(50);
+
+        //we need more space for the CC counts in the third column
+        if(Options.getAssocTest() == ASSOC_CC) {
+            jt.getColumnModel().getColumn(3).setPreferredWidth(200);
+            jt.getColumnModel().getColumn(4).setPreferredWidth(75);
+            jt.getColumnModel().getColumn(5).setPreferredWidth(75);
+        } else {
+            jt.getColumnModel().getColumn(3).setPreferredWidth(150);
+            jt.getColumnModel().getColumn(4).setPreferredWidth(100);
+            jt.getColumnModel().getColumn(5).setPreferredWidth(100);
+        }
+        jt.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+        jt.setPreferredScrollableViewportSize(new Dimension(600,jt.getPreferredScrollableViewportSize().height));
+
+
+        JScrollPane tableScroller = new JScrollPane(jt);
+        add(tableScroller);
+    }
+
+    public AssociationTestSet getTestSet() {
+        return testSet;
+    }
+}
\ No newline at end of file
diff --git a/edu/mit/wi/haploview/association/HaploAssocPanel.java b/edu/mit/wi/haploview/association/HaploAssocPanel.java
new file mode 100755
index 0000000..170939d
--- /dev/null
+++ b/edu/mit/wi/haploview/association/HaploAssocPanel.java
@@ -0,0 +1,182 @@
+package edu.mit.wi.haploview.association;
+
+import java.util.Vector;
+import java.awt.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+
+import edu.mit.wi.haploview.TreeTable.*;
+import edu.mit.wi.haploview.Constants;
+import edu.mit.wi.haploview.Options;
+import edu.mit.wi.haploview.HaploviewTab;
+
+import javax.swing.*;
+import javax.swing.tree.DefaultTreeCellRenderer;
+
+
+public class HaploAssocPanel extends HaploviewTab implements Constants,ActionListener {
+    static final long serialVersionUID = -8632659640252839488L;
+    public double initialHaplotypeDisplayThreshold;
+    private AssociationTestSet testSet;
+    public JTreeTable jtt;
+    private boolean expand = true;
+
+
+    public HaploAssocPanel(AssociationTestSet ats){
+        testSet = ats;
+
+        this.setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
+
+        makeTable(ats);
+    }
+
+    public void makeTable(AssociationTestSet ats) {
+        testSet = ats;
+
+        this.removeAll();
+
+        if(ats == null) {
+            return ;
+        }
+
+        Vector results = ats.getResults();
+
+        initialHaplotypeDisplayThreshold = Options.getHaplotypeDisplayThreshold();
+        Vector colNames = new Vector();
+
+        colNames.add("Haplotype");
+        colNames.add("Freq.");
+        if (Options.getAssocTest() == ASSOC_TRIO){
+            if(Options.getTdtType() == TDT_STD) {
+                colNames.add("T:U");
+            } else if(Options.getTdtType() == TDT_PAREN) {
+                colNames.add("T:U, PA:PU");
+            }
+
+        }else{
+            colNames.add("Case, Control Ratios");
+        }
+        colNames.add("Chi Square");
+        colNames.add("p value");
+
+        HaplotypeAssociationNode root = new HaplotypeAssociationNode("Haplotype Associations");
+
+        for(int i=0; i < results.size(); i++){
+            HaplotypeAssociationResult ar = (HaplotypeAssociationResult) results.get(i);
+            HaplotypeAssociationNode han = new HaplotypeAssociationNode(ar.getName());
+
+            for(int j=0;j< ar.getAlleleCount(); j++) {
+                han.add(new HaplotypeAssociationNode(ar,j));
+            }
+            root.add(han);
+        }
+        int countsOrRatios = SHOW_HAP_COUNTS;
+        if(jtt != null) {
+            //if were just updating the table, then we want to retain the current status of countsOrRatios
+            HaplotypeAssociationModel ham = (HaplotypeAssociationModel) jtt.getTree().getModel();
+            countsOrRatios = ham.getCountsOrRatios();
+        }
+
+        jtt = new JTreeTable(new HaplotypeAssociationModel(colNames, root));
+
+        ((HaplotypeAssociationModel)(jtt.getTree().getModel())).setCountsOrRatios(countsOrRatios);
+
+        jtt.getColumnModel().getColumn(0).setPreferredWidth(200);
+        jtt.getColumnModel().getColumn(1).setPreferredWidth(50);
+
+        //we need more space for the CC counts in the third column
+        if(Options.getAssocTest() == ASSOC_CC) {
+            jtt.getColumnModel().getColumn(2).setPreferredWidth(200);
+            jtt.getColumnModel().getColumn(3).setPreferredWidth(75);
+            jtt.getColumnModel().getColumn(4).setPreferredWidth(75);
+        } else {
+            jtt.getColumnModel().getColumn(2).setPreferredWidth(150);
+            jtt.getColumnModel().getColumn(3).setPreferredWidth(100);
+            jtt.getColumnModel().getColumn(4).setPreferredWidth(100);
+        }
+        jtt.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+
+        Font monoFont = new Font("Monospaced",Font.PLAIN,12);
+        jtt.setFont(monoFont);
+        JTree theTree = jtt.getTree();
+        theTree.setFont(monoFont);
+
+        DefaultTreeCellRenderer r = new DefaultTreeCellRenderer();
+        r.setLeafIcon(null);
+        r.setOpenIcon(null);
+        r.setClosedIcon(null);
+        theTree.setCellRenderer(r);
+
+        //jtt.setPreferredScrollableViewportSize(new Dimension(600,jtt.getPreferredScrollableViewportSize().height));
+        jtt.setPreferredScrollableViewportSize(new Dimension(600, 1000));
+
+        JScrollPane treeScroller = new JScrollPane(jtt);
+        treeScroller.setMaximumSize(treeScroller.getPreferredSize());
+        add(treeScroller);
+
+        if(Options.getAssocTest() == ASSOC_CC) {
+            JRadioButton countsButton = new JRadioButton("Show CC counts");
+            JRadioButton ratiosButton = new JRadioButton("Show CC frequencies");
+            JButton expandCollapseAll = new JButton("Expand/Collapse All");
+
+            ButtonGroup bg = new ButtonGroup();
+
+            bg.add(countsButton);
+            bg.add(ratiosButton);
+            countsButton.addActionListener(this);
+            ratiosButton.addActionListener(this);
+            expandCollapseAll.addActionListener(this);
+            JPanel butPan = new JPanel();
+            butPan.add(countsButton);
+            butPan.add(ratiosButton);
+            butPan.add(expandCollapseAll);
+            add(butPan);
+            if(countsOrRatios == SHOW_HAP_RATIOS) {
+                ratiosButton.setSelected(true);
+            }else{
+                countsButton.setSelected(true);
+            }
+        }else{
+            JPanel butPanB = new JPanel();
+            JButton expandCollaseAllB = new JButton("Expand/Collapse All");
+            expandCollaseAllB.addActionListener(this);
+            butPanB.add(expandCollaseAllB);
+            add(butPanB);
+        }
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if(command.equals("Show CC counts")) {
+            HaplotypeAssociationModel ham = (HaplotypeAssociationModel)jtt.getTree().getModel();
+            ham.setCountsOrRatios(SHOW_HAP_COUNTS);
+            jtt.repaint();
+        }
+        else if (command.equals("Show CC frequencies")) {
+            HaplotypeAssociationModel ham = (HaplotypeAssociationModel)jtt.getTree().getModel();
+            ham.setCountsOrRatios(SHOW_HAP_RATIOS);
+            jtt.repaint();
+        }
+        else if (command.equals("Expand/Collapse All")){
+            if (expand){
+                for (int i = 0; i < jtt.getTree().getHeight(); i++){
+                    jtt.getTree().expandRow(i);
+                }
+                expand = !expand;
+                jtt.repaint();
+            }else {
+                for (int i = 1; i < jtt.getTree().getHeight(); i++){
+                    jtt.getTree().collapsePath(jtt.getTree().getPathForRow(i));
+                }
+                expand = !expand;
+                jtt.repaint();
+            }
+        }
+
+
+    }
+
+    public AssociationTestSet getTestSet() {
+        return testSet;
+    }
+}
diff --git a/edu/mit/wi/haploview/association/HaplotypeAssociationResult.java b/edu/mit/wi/haploview/association/HaplotypeAssociationResult.java
new file mode 100755
index 0000000..14c04d9
--- /dev/null
+++ b/edu/mit/wi/haploview/association/HaplotypeAssociationResult.java
@@ -0,0 +1,108 @@
+package edu.mit.wi.haploview.association;
+
+import edu.mit.wi.haploview.Haplotype;
+import edu.mit.wi.haploview.Options;
+import edu.mit.wi.haploview.HaploViewException;
+
+public class HaplotypeAssociationResult extends AssociationResult{
+
+    Haplotype[] haps;
+
+    public HaplotypeAssociationResult(Haplotype[] locusHaplos, double freqCutoff, String n) {
+        nf.setGroupingUsed(false);
+
+        for (int i = 0; i < locusHaplos.length; i++){
+            alleles.add(locusHaplos[i]);
+        }
+        filterByFrequency(freqCutoff);
+        name = n;
+
+        haps = locusHaplos;
+    }
+
+    public HaplotypeAssociationResult(Haplotype[] locusHaplos, String allele, String name) throws HaploViewException{
+        this.name = name;
+        nf.setGroupingUsed(false);
+        for (int i = 0; i < locusHaplos.length; i++){
+            alleles.add(locusHaplos[i]);
+        }
+        filterByAllele(allele);
+
+        haps = locusHaplos;
+    }
+
+    public String getDisplayName(int i) {
+        return this.getName() + ": " + this.getAlleleName(i);
+    }
+
+    public String getCountString(int i){
+        nf.setMinimumFractionDigits(1);
+        nf.setMaximumFractionDigits(1);
+
+        Haplotype h = (Haplotype) filteredAlleles.get(i);
+        StringBuffer countSB = new StringBuffer();
+        if(Options.getAssocTest() == ASSOC_TRIO) {
+            countSB.append(nf.format(h.getTransCount())).append(" : ").append(nf.format(h.getUntransCount()));
+            if(Options.getTdtType()==TDT_PAREN) {
+                double[] d = h.getDiscordantAlleleCounts();
+                countSB.append(",").append(nf.format( d[3] + d[7] + 2*d[6])).append(":").append(nf.format(d[1] + d[5] + 2*d[2]));
+            }
+        } else if(Options.getAssocTest() == ASSOC_CC) {
+            double caseSum = 0, controlSum = 0;
+            for (int j = 0; j < alleles.size(); j++){
+                if (h != alleles.get(j)){
+                    caseSum += ((Haplotype)alleles.get(j)).getCaseCount();
+                    controlSum += ((Haplotype)alleles.get(j)).getControlCount();
+                }
+            }
+            countSB.append(nf.format(h.getCaseCount())).append(" : ").append(nf.format(caseSum)).append(", ");
+            countSB.append(nf.format(h.getControlCount())).append(" : ").append(nf.format(controlSum));
+        }
+
+        return countSB.toString();
+    }
+
+    public String getFreqString(int i ){
+        if (Options.getAssocTest() == ASSOC_TRIO){
+            return "";
+        }
+
+        nf.setMinimumFractionDigits(3);
+        nf.setMaximumFractionDigits(3);
+
+        StringBuffer countSB = new StringBuffer();
+        Haplotype h = (Haplotype) filteredAlleles.get(i);
+        double caseSum = 0, controlSum = 0;
+        for (int j = 0; j < alleles.size(); j++){
+            caseSum += ((Haplotype)alleles.get(j)).getCaseCount();
+            controlSum += ((Haplotype)alleles.get(j)).getControlCount();
+        }
+        countSB.append(nf.format(h.getCaseCount()/caseSum)).append(", ");
+        countSB.append(nf.format(h.getControlCount()/controlSum));
+
+        return countSB.toString();
+    }
+
+    public void filterByAllele(String allele) throws HaploViewException{
+        if (allele == null){
+            filterByFrequency(Options.getHaplotypeDisplayThreshold());
+        }else{
+            filteredAlleles.removeAllElements();
+            for(int i=0;i<alleles.size();i++) {
+                Haplotype curHap = (Haplotype) alleles.get(i);
+                if(curHap.toNumericString().equals(allele)){
+                    filteredAlleles.add(curHap);
+                }
+            }
+            if (filteredAlleles.size() == 0){
+                throw new HaploViewException(allele + ": no such allele for test:\n" +
+                        getName());
+            }
+            calculateChiSquares();
+        }
+    }
+
+    public Haplotype[] getHaps() {
+        return haps;
+    }
+}
diff --git a/edu/mit/wi/haploview/association/MarkerAssociationResult.java b/edu/mit/wi/haploview/association/MarkerAssociationResult.java
new file mode 100755
index 0000000..b6ed76d
--- /dev/null
+++ b/edu/mit/wi/haploview/association/MarkerAssociationResult.java
@@ -0,0 +1,129 @@
+package edu.mit.wi.haploview.association;
+
+import edu.mit.wi.haploview.Haplotype;
+import edu.mit.wi.haploview.Options;
+import edu.mit.wi.haploview.SNP;
+
+public class MarkerAssociationResult extends AssociationResult{
+
+    SNP snp;
+
+    public MarkerAssociationResult(Haplotype[] locusHaplos, String n, SNP snp) {
+        nf.setGroupingUsed(false);
+
+        for (int i = 0; i < locusHaplos.length; i++){
+            alleles.add(locusHaplos[i]);
+        }
+        filterByFrequency(0);
+        name = n;
+
+        this.snp = snp;
+    }
+
+    public String getCountString(){
+        return getCountString(0);
+    }
+
+    public String getFreqString(){
+        return getFreqString(0);
+    }
+
+    public String getDisplayName(int i) {
+        return this.getName();
+    }
+
+    public String getCountString(int i){
+        Haplotype h1 = (Haplotype) alleles.get(0);
+        Haplotype h2 = (Haplotype) alleles.get(1);
+
+        if (Options.getAssocTest() == ASSOC_TRIO){
+            if(Options.getTdtType() == TDT_STD) {
+                if (h1.getTransCount() > h2.getTransCount()){
+                    return (int)h1.getTransCount() + ":" + (int)h2.getTransCount();
+                }else{
+                    return (int)h2.getTransCount() + ":" + (int)h1.getTransCount();
+                }
+            }else{ // if(Options.getTdtType() == TDT_PAREN) {
+                StringBuffer sb = new StringBuffer();
+                double[] counts;
+                if (h1.getTransCount() > h2.getTransCount()){
+                    counts = h1.getDiscordantAlleleCounts();
+                    sb.append((int)h1.getTransCount());
+                    sb.append(":");
+                    sb.append((int)h2.getTransCount());
+                }else{
+                    counts = h2.getDiscordantAlleleCounts();
+                    sb.append((int)h2.getTransCount());
+                    sb.append(":");
+                    sb.append((int)h1.getTransCount());
+                }
+                sb.append(",");
+                sb.append(counts[1] + 2*counts[2] + counts[5]).append(":");
+                sb.append(counts[3] + 2*counts[6] + counts[7]);
+                return sb.toString();
+            }
+        }else{
+            if (h1.getCaseCount()/(h1.getCaseCount()+h2.getCaseCount()) >
+                    h1.getControlCount()/(h1.getControlCount()+h2.getControlCount())){
+                    return (int)h1.getCaseCount() + ":" + (int)h2.getCaseCount() +
+                            ", " + (int)h1.getControlCount() + ":" + (int)h2.getControlCount();
+            }else{
+                    return (int)h2.getCaseCount() + ":" + (int)h1.getCaseCount() +
+                            ", " + (int)h2.getControlCount() + ":" + (int)h1.getControlCount();
+            }
+        }
+    }
+
+    public String getFreqString(int i){
+        Haplotype h1 = (Haplotype) alleles.get(0);
+        Haplotype h2 = (Haplotype) alleles.get(1);
+
+        if(Options.getAssocTest() == ASSOC_TRIO){
+            return "";
+        }
+
+        nf.setMinimumFractionDigits(3);
+        nf.setMaximumFractionDigits(3);
+        if (h1.getCaseCount()/(h1.getCaseCount()+h2.getCaseCount()) >
+                    h1.getControlCount()/(h1.getControlCount()+h2.getControlCount())){
+                return nf.format(h1.getCaseCount()/ (h1.getCaseCount() + h2.getCaseCount())) +
+                        ", " + nf.format(h1.getControlCount() / ( h1.getControlCount() + h2.getControlCount()));
+        }else{
+                return nf.format(h2.getCaseCount()/ (h2.getCaseCount() + h1.getCaseCount())) +
+                        ", " + nf.format(h2.getControlCount() / ( h2.getControlCount() + h1.getControlCount()));
+        }
+    }
+
+    public String getOverTransmittedAllele() {
+        Haplotype h1 = (Haplotype) alleles.get(0);
+        Haplotype h2 = (Haplotype) alleles.get(1);
+
+        String retStr;
+
+        if (Options.getAssocTest() == ASSOC_TRIO){
+            if (h1.getTransCount() > h2.getTransCount()){
+                retStr = getAlleleName(0);
+            }else if (h1.getTransCount() == h2.getTransCount()){
+                retStr = "-";
+            }else{
+                retStr = getAlleleName(1);
+            }
+        }else{
+            if (h1.getCaseCount()/(h1.getCaseCount()+h2.getCaseCount()) >
+                    h1.getControlCount()/(h1.getControlCount()+h2.getControlCount())){
+                retStr = getAlleleName(0);
+            }else if (h1.getCaseCount()/(h1.getCaseCount()+h2.getCaseCount()) ==
+                    h1.getControlCount()/(h1.getControlCount()+h2.getControlCount())){
+                retStr = "-";
+            }else{
+                retStr = getAlleleName(1);
+            }
+        }
+        return retStr;
+    }
+
+    public SNP getSnp() {
+        return snp;
+    }
+
+}
diff --git a/edu/mit/wi/haploview/association/PermutationTestPanel.java b/edu/mit/wi/haploview/association/PermutationTestPanel.java
new file mode 100755
index 0000000..f0c4fd0
--- /dev/null
+++ b/edu/mit/wi/haploview/association/PermutationTestPanel.java
@@ -0,0 +1,291 @@
+package edu.mit.wi.haploview.association;
+
+import edu.mit.wi.haploview.*;
+
+import javax.swing.*;
+import javax.swing.table.TableModel;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.*;
+import java.util.Vector;
+import java.io.File;
+import java.io.IOException;
+
+import org.jfree.data.statistics.HistogramDataset;
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.XYPlot;
+
+public class PermutationTestPanel extends HaploviewTab
+        implements Constants,ActionListener {
+    static final long serialVersionUID = -8287708770858358590L;
+    private JLabel bestPermutationValueLabel;
+    private JLabel blocksChangedLabel;
+    private NumberTextField permCountField;
+
+    private JProgressBar permProgressBar;
+    private JButton doPermutationsButton;
+    private JButton stopPermutationsButton;
+
+    private PermutationTestSet testSet;
+    private PermutationThread permThread;
+    private ProgressBarUpdater progressUpdater;
+    private Vector colNames;
+    private JPanel resultsPanel;
+    private JLabel bestObsValueLabel;
+
+    private JLabel scoreBoardNumPassLabel;
+    private JLabel scoreBoardNumTotalLabel;
+    private JPanel scoreBoardPanel;
+    private boolean finishedPerms;
+    private JPanel bestObsPanel;
+    private JPanel bestPermPanel;
+    private ButtonGroup selectionGroup;
+
+    public PermutationTestPanel(PermutationTestSet pts) {
+        if(pts == null) {
+            throw new NullPointerException();
+        }
+
+        testSet = pts;
+
+        this.setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
+
+        JPanel permCountPanel = new JPanel();
+
+        JPanel selectionPanel = new JPanel();
+        selectionGroup = new ButtonGroup();
+        selectionPanel.setLayout(new BoxLayout(selectionPanel, BoxLayout.Y_AXIS));
+        JRadioButton singleOnlyButton = new JRadioButton("Single Markers Only");
+        singleOnlyButton.setActionCommand(String.valueOf(PermutationTestSet.SINGLE_ONLY));
+        selectionPanel.add(singleOnlyButton);
+        selectionGroup.add(singleOnlyButton);
+        singleOnlyButton.setSelected(true);
+        JRadioButton singlesPlusBlocksButton = new JRadioButton("Single Markers and Haplotypes in Blocks");
+        singlesPlusBlocksButton.setActionCommand(String.valueOf(PermutationTestSet.SINGLE_PLUS_BLOCKS));
+        selectionPanel.add(singlesPlusBlocksButton);
+        selectionGroup.add(singlesPlusBlocksButton);
+        JRadioButton blocksOnlyButton = new JRadioButton("Haplotypes in Blocks Only");
+        blocksOnlyButton.setActionCommand(String.valueOf(PermutationTestSet.BLOCKS_ONLY));
+        selectionPanel.add(blocksOnlyButton);
+        selectionGroup.add(blocksOnlyButton);
+        if (testSet.isCustom()){
+            JRadioButton customFileButton = new JRadioButton("Custom Tests from File");
+            customFileButton.setActionCommand(String.valueOf(PermutationTestSet.CUSTOM));
+            selectionPanel.add(customFileButton);
+            selectionGroup.add(customFileButton);
+            customFileButton.setSelected(true);
+        }
+        permCountPanel.add(selectionPanel);
+
+        JLabel permCountTextLabel = new JLabel("Number of Permutations: ");
+        permCountField = new NumberTextField("", 10, false, false);
+        permCountPanel.add(permCountTextLabel);
+        permCountPanel.add(permCountField);
+
+        permCountPanel.setMaximumSize(permCountPanel.getPreferredSize());
+        this.add(permCountPanel);
+
+
+        JPanel buttonPanel = new JPanel();
+        doPermutationsButton = new JButton("Do Permutations");
+        doPermutationsButton.addActionListener(this);
+        stopPermutationsButton = new JButton("Stop");
+        stopPermutationsButton.addActionListener(this);
+        stopPermutationsButton.setEnabled(false);
+        buttonPanel.add(doPermutationsButton);
+        buttonPanel.add(stopPermutationsButton);
+        buttonPanel.setMaximumSize(buttonPanel.getPreferredSize());
+        this.add(buttonPanel);
+
+        bestObsPanel = new JPanel();
+        JLabel bestObsTextLabel = new JLabel("Best Observed Chi-Square: ");
+        bestObsValueLabel = new JLabel("");
+        bestObsPanel.add(bestObsTextLabel);
+        bestObsPanel.add(bestObsValueLabel);
+        bestObsPanel.setMaximumSize(new Dimension(400,bestObsPanel.getPreferredSize().height));
+        this.add(bestObsPanel);
+
+        bestPermPanel = new JPanel();
+        JLabel bestPermTextLabel = new JLabel("Best Permutation Chi-Square: ");
+        bestPermutationValueLabel = new JLabel("");
+        bestPermPanel.add(bestPermTextLabel);
+        bestPermPanel.add(bestPermutationValueLabel);
+        bestPermPanel.setMaximumSize(new Dimension(400,bestPermPanel.getPreferredSize().height));
+        this.add(bestPermPanel);
+
+
+        scoreBoardPanel = new JPanel();
+        scoreBoardNumPassLabel = new JLabel();
+        scoreBoardNumTotalLabel = new JLabel();
+        scoreBoardPanel.add(scoreBoardNumPassLabel);
+        scoreBoardPanel.add(new JLabel("permutations out of"));
+        scoreBoardPanel.add(scoreBoardNumTotalLabel);
+        scoreBoardPanel.add(new JLabel("exceed highest observed chi square."));
+        scoreBoardPanel.setMaximumSize(new Dimension(500,60));
+        scoreBoardPanel.setVisible(false);
+        add(scoreBoardPanel);
+
+        colNames = new Vector();
+        colNames.add("Name");
+        colNames.add("Chi Square");
+        colNames.add("Permutation p-value");
+
+        blocksChangedLabel = new JLabel("The current blocks may have changed, so these values may not be accurate!");
+        blocksChangedLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
+        blocksChangedLabel.setFont(new Font("SansSerif", Font.BOLD, 14));
+        blocksChangedLabel.setForeground(Color.red);
+
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if(command.equals("Do Permutations") ) {
+            startPerms();
+        }else if(command.equals("Stop")) {
+            stopPerms();
+        }
+    }
+
+    public void setTestSet(PermutationTestSet pts){
+        testSet = pts;
+    }
+
+    public void startPerms() {
+        if (permCountField.getText().equals("") || Integer.parseInt(permCountField.getText()) < 1){
+            JOptionPane.showMessageDialog(this,
+                    "Please specify a non-zero number of permutations.",
+                    "Number of Permutations?",
+                    JOptionPane.INFORMATION_MESSAGE);
+            return;
+        }
+        scoreBoardPanel.setVisible(true);
+        finishedPerms = false;
+
+        if (resultsPanel != null){
+            remove(resultsPanel);
+            repaint();
+        }
+        this.remove(blocksChangedLabel);
+
+        bestPermutationValueLabel.setText("");
+
+        testSet.setPermutationCount(Integer.parseInt(permCountField.getText()));
+
+        permThread = new PermutationThread(testSet);
+
+        doPermutationsButton.setEnabled(false);
+        stopPermutationsButton.setEnabled(true);
+
+        permProgressBar = new JProgressBar(0,testSet.getPermutationCount());
+        permProgressBar.setMaximumSize(new Dimension(200,30));
+        permProgressBar.setStringPainted(true);
+        this.add(permProgressBar);
+
+        permThread.start();
+
+        progressUpdater = new ProgressBarUpdater();
+        progressUpdater.start();
+    }
+
+    public void stopPerms() {
+        testSet.stopProcessing = true;
+        progressUpdater.interrupt();
+    }
+
+    public void finishedPerms() {
+        scoreBoardNumTotalLabel.setText(String.valueOf(testSet.getPermutationsPerformed()));
+        scoreBoardNumPassLabel.setText(String.valueOf(testSet.getBestExceededCount()));
+        doPermutationsButton.setEnabled(true);
+        stopPermutationsButton.setEnabled(false);
+        this.remove(permProgressBar);
+        bestObsValueLabel.setText(testSet.getBestObsChiSq() + " (" + testSet.getBestObsName() + ")");
+        bestPermutationValueLabel.setText(String.valueOf(testSet.getBestPermChiSquare()));
+        makeTable();
+        resultsPanel.revalidate();
+        finishedPerms = true;
+    }
+
+    public void makeTable() {
+        Vector tableData = testSet.getResults();
+        TableModel tm = new BasicTableModel(colNames, tableData);
+        JTable jt = new JTable(tm);
+        JScrollPane tableScroller = new JScrollPane(jt);
+        tableScroller.setMaximumSize(tableScroller.getPreferredSize());
+        resultsPanel = new JPanel();
+        resultsPanel.setLayout(new BoxLayout(resultsPanel,BoxLayout.Y_AXIS));
+        resultsPanel.add(tableScroller);
+
+        resultsPanel.add(Box.createRigidArea(new Dimension(0,5)));
+
+        HistogramDataset resHist = new HistogramDataset();
+        resHist.addSeries("Chi Squares", testSet.getPermBestChiSq(),100);
+        JFreeChart jfc =  ChartFactory.createHistogram(null,
+                "Chi Square",
+                "Number Permutations",resHist,PlotOrientation.VERTICAL,false,false,false);
+        jfc.setBorderVisible(true);
+        XYPlot xyp = jfc.getXYPlot();
+        xyp.getRenderer().setSeriesPaint(0,Color.blue);
+        ChartPanel cp = new ChartPanel(jfc);
+        cp.setMaximumSize(new Dimension(400, cp.getPreferredSize().height));
+        resultsPanel.add(cp);
+        add(resultsPanel);
+    }
+
+    public void setBlocksChanged() {
+        if (resultsPanel != null){
+            this.add(blocksChangedLabel);
+        }
+    }
+
+    public void export(File outfile) throws IOException{
+        //if a crazy user tries to export the data when the perms are running,
+        //we have a problem. So this stops the perms and waits for it to finish up
+        //before writing the output.
+        if (testSet.getPermutationCount() != testSet.getPermutationsPerformed()){
+            stopPerms();
+
+            //don't let it run off forever!
+            while (!finishedPerms){
+                try{
+                    Thread.sleep(100);
+                }catch (InterruptedException e){
+                }
+            }
+        }
+        testSet.writeResultsToFile(outfile);
+    }
+
+    private class PermutationThread extends Thread{
+        PermutationTestSet testSet;
+
+        public PermutationThread(PermutationTestSet pts) {
+            testSet = pts;
+        }
+
+        public void run() {
+            testSet.doPermutations(Integer.valueOf(selectionGroup.getSelection().getActionCommand()).intValue());
+            finishedPerms();
+        }
+    }
+
+    public PermutationTestSet getTestSet() {
+        return testSet;
+    }
+
+    private class ProgressBarUpdater extends Thread{
+        public void run() {
+            try {
+                while(testSet.getPermutationCount() - testSet.getPermutationsPerformed() != 0) {
+                    permProgressBar.setValue(testSet.getPermutationsPerformed());
+                    scoreBoardNumTotalLabel.setText(String.valueOf(testSet.getPermutationsPerformed()));
+                    scoreBoardNumPassLabel.setText(String.valueOf(testSet.getBestExceededCount()));
+                    bestPermutationValueLabel.setText(String.valueOf(testSet.getBestPermChiSquare()));
+                    sleep(200);
+                }
+            } catch(InterruptedException ie) {}
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/association/PermutationTestSet.java b/edu/mit/wi/haploview/association/PermutationTestSet.java
new file mode 100755
index 0000000..565534f
--- /dev/null
+++ b/edu/mit/wi/haploview/association/PermutationTestSet.java
@@ -0,0 +1,386 @@
+
+package edu.mit.wi.haploview.association;
+
+import edu.mit.wi.pedfile.PedFile;
+import edu.mit.wi.pedfile.PedFileException;
+import edu.mit.wi.pedfile.Individual;
+import edu.mit.wi.haploview.*;
+
+import java.util.*;
+import java.io.File;
+import java.io.IOException;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+
+
+public class PermutationTestSet implements Constants{
+    public static final int SINGLE_ONLY = 0;
+    public static final int SINGLE_PLUS_BLOCKS = 1;
+    public static final int CUSTOM = 2;
+    public static final int BLOCKS_ONLY = 3;
+
+    private int permutationCount;
+    private int bestExceededCount;
+    private double bestObsChiSq;
+    private String bestObsName;
+
+    private PedFile pedFile;
+    private AssociationTestSet activeAssocTestSet, custAssocTestSet, defaultAssocTestSet;
+
+    private double[] permBestChiSq;
+
+
+    public boolean stopProcessing;
+
+    //this variable is updated as permutations are performed. Once
+    //this variable reaches the value of permutationCount, permutation tests have all completed.
+    private int permutationsPerformed;
+    private double permBestOverallChiSq;
+    private int selectionType;
+
+    public PermutationTestSet(int permCount, PedFile pf, AssociationTestSet cats, AssociationTestSet dats){
+        if(permCount > 0) {
+            permutationCount = permCount;
+        } else {
+            permCount = 0;
+        }
+
+        pedFile = pf;
+        custAssocTestSet = cats;
+        defaultAssocTestSet = dats;
+        if (custAssocTestSet != null){
+            activeAssocTestSet = custAssocTestSet;
+        }else{
+            activeAssocTestSet = defaultAssocTestSet;
+        }
+    }
+
+    public void doPermutations(int selection) {
+        selectionType = selection;
+        stopProcessing = false;
+        Vector curResults = null;
+
+        if (selectionType == CUSTOM){
+            activeAssocTestSet = custAssocTestSet;
+        }else{
+            activeAssocTestSet = defaultAssocTestSet;
+        }
+
+        double curBest = 0;
+        String curName = "";
+        Vector filteredResults = activeAssocTestSet.getFilteredResults();
+        for(int i=0;i<filteredResults.size();i++) {
+            AssociationResult tmpRes = (AssociationResult) filteredResults.get(i);
+            if (selectionType == SINGLE_ONLY && tmpRes instanceof HaplotypeAssociationResult ||
+                    selectionType == BLOCKS_ONLY && tmpRes instanceof MarkerAssociationResult){
+                //if we're not permuting the haps or not permtuing the single SNPs,
+                // don't count them as best observed association
+                continue;
+            }
+            for (int j = 0; j < tmpRes.getAlleleCount(); j++){
+                if (tmpRes.getChiSquare(j) > curBest){
+                    curName = tmpRes.getDisplayName(j);
+                    curBest = tmpRes.getChiSquare(j);
+                }
+            }
+        }
+        bestObsChiSq = curBest;
+        bestObsName = curName;
+
+
+        Haplotype[][] haplotypes = new Haplotype[activeAssocTestSet.getHaplotypeAssociationResults().size()][];
+        Iterator hitr = activeAssocTestSet.getHaplotypeAssociationResults().iterator();
+        int count = 0;
+        while (hitr.hasNext()){
+            haplotypes[count] = ((HaplotypeAssociationResult)hitr.next()).getHaps();
+            count++;
+        }
+
+        Vector snpSet = new Vector();
+        Iterator sitr = filteredResults.iterator();
+        while (sitr.hasNext()){
+            Object o = sitr.next();
+            if (o instanceof MarkerAssociationResult){
+                SNP snp = ((MarkerAssociationResult)o).getSnp();
+                snpSet.add(snp);
+            }
+        }
+
+        //we need to make fake Haplotype objects so that we can use the getcounts() and getChiSq() methods of
+        //AssociationResult. kludgetastic!
+        Haplotype[][] fakeHaplos;
+
+        if(haplotypes != null) {
+            fakeHaplos = new Haplotype[haplotypes.length][];
+            for(int j=0;j<haplotypes.length;j++) {
+
+                fakeHaplos[j] =  new Haplotype[haplotypes[j].length];
+                for(int k=0;k<haplotypes[j].length;k++) {
+                    fakeHaplos[j][k] = new Haplotype(haplotypes[j][k].getGeno(),haplotypes[j][k].getPercentage(),
+                            haplotypes[j][k].getMarkers(), haplotypes[j][k].getEM());
+                }
+            }
+        } else {
+            fakeHaplos = new Haplotype[0][0];
+        }
+
+        //need to use the same affected status for marker and haplotype association tests in case control,
+        //so affectedStatus stores the shuffled affected status
+        Vector affectedStatus = null;
+
+        if(Options.getAssocTest() == ASSOC_CC) {
+            Vector indList = pedFile.getUnrelatedIndividuals();
+            affectedStatus = new Vector(indList.size());
+            for(int j=0;j<indList.size();j++) {
+                affectedStatus.add(new Integer(((Individual)indList.get(j)).getAffectedStatus()));
+            }
+        }
+
+        //need to use the same coin toss for marker and haplotype association tests in trio tdt,
+        //so permuteInd stores whether each individual is permuted
+        //we start by creating a vector of the right size, but with a bunch of nulls
+        //since ea. permutation (below) will set the values in the vector
+        Vector permuteInd = new Vector();
+        Vector permuteDiscPar = new Vector();
+        for (int i = 0; i < pedFile.getAllIndividuals().size(); i++){
+            permuteInd.add(null);
+            permuteDiscPar.add(null);
+        }
+
+        permutationsPerformed = 0;
+        bestExceededCount = 0;
+        double[] chiSqs = new double[permutationCount];
+        permBestOverallChiSq = 0;
+        //start the permuting!
+        for(int i=0;i<permutationCount; i++) {
+            //this variable gets set by the thread we're running if it wants us to stop
+            if(stopProcessing) {
+                break;
+            }
+
+            //shuffle up and deal.
+            if (Options.getAssocTest() == ASSOC_TRIO){
+                for(int j =0;j<pedFile.getAllIndividuals().size();j++) {
+                    if(Math.random() < .5) {
+                        permuteInd.set(j,Boolean.valueOf(true));
+                    } else {
+                        permuteInd.set(j,Boolean.valueOf(false));
+                    }
+                }
+                if(Options.getTdtType() == TDT_PAREN) {
+                    for(int j =0;j<pedFile.getAllIndividuals().size();j++) {
+                        if(Math.random() < .5) {
+                            permuteDiscPar.set(j,Boolean.valueOf(true));
+                        } else {
+                            permuteDiscPar.set(j,Boolean.valueOf(false));
+                        }
+                    }
+                }
+            }else if (Options.getAssocTest() == ASSOC_CC){
+                Collections.shuffle(affectedStatus);
+            }
+
+            if (selectionType != BLOCKS_ONLY){
+                //begin single marker association test
+                try{
+                    if (Options.getAssocTest() == ASSOC_TRIO){
+                        curResults = new AssociationTestSet(pedFile, permuteInd,permuteDiscPar, snpSet).getMarkerAssociationResults();
+                    }else if (Options.getAssocTest() == ASSOC_CC){
+                        curResults = new AssociationTestSet(pedFile,affectedStatus, null,snpSet).getMarkerAssociationResults();
+                    }
+                } catch(PedFileException pfe) {
+                }
+                //end of marker association test
+            }else{
+                //reset it so we can add this round's hap assoc results
+                curResults = new Vector();
+            }
+
+
+            if (selectionType != SINGLE_ONLY && selectionType != CUSTOM){
+                //begin haplotype association test
+                if(Options.getAssocTest() == ASSOC_TRIO) {
+                    for(int j=0;j<fakeHaplos.length;j++) {
+                        EM curEM = fakeHaplos[j][0].getEM();
+                        curEM.doAssociationTests(null,permuteInd,permuteDiscPar, null);
+                        for(int k=0;k<fakeHaplos[j].length;k++) {
+                            fakeHaplos[j][k].setTransCount(curEM.getTransCount(k));
+                            fakeHaplos[j][k].setUntransCount(curEM.getUntransCount(k));
+                            if(Options.getTdtType() == TDT_PAREN) {
+                                fakeHaplos[j][k].setDiscordantAlleleCounts(curEM.getDiscordantCounts(k));
+                            }
+                        }
+                    }
+                } else if(Options.getAssocTest() == ASSOC_CC) {
+                    for(int j=0;j<fakeHaplos.length;j++) {
+                        EM curEM = fakeHaplos[j][0].getEM();
+                        curEM.doAssociationTests(affectedStatus,null,null, null);
+                        for(int k=0;k<fakeHaplos[j].length;k++) {
+                            fakeHaplos[j][k].setCaseCount(curEM.getCaseCount(k));
+                            fakeHaplos[j][k].setControlCount(curEM.getControlCount(k));
+                        }
+                    }
+                }
+                if(Options.getAssocTest() == ASSOC_TRIO || Options.getAssocTest() == ASSOC_CC) {
+                    AssociationTestSet ats = null;
+                    if (activeAssocTestSet.getFilterAlleles() == null){
+                        ats = new AssociationTestSet(fakeHaplos, null);
+                    }else{
+                        try{
+                            ats = new AssociationTestSet(fakeHaplos,null,activeAssocTestSet.getFilterAlleles());
+                        }catch (HaploViewException hve){
+                        }
+                    }
+                    curResults.addAll(ats.getResults());
+                }
+                //end of haplotype association test
+            }
+
+            //find the best chi square from all the tests
+            double tempBestChiSquare = 0;
+            for(int j=0; j<curResults.size();j++) {
+                AssociationResult tempResult = (AssociationResult) curResults.elementAt(j);
+                for (int k = 0; k < tempResult.getAlleleCount(); k++){
+                    if(tempResult.getChiSquare(k) > tempBestChiSquare) {
+                        tempBestChiSquare = tempResult.getChiSquare(k);
+                    }
+                }
+            }
+
+            chiSqs[i] = tempBestChiSquare;
+
+            if (chiSqs[i] >= bestObsChiSq){
+                bestExceededCount++;
+            }
+
+            if(chiSqs[i] > permBestOverallChiSq){
+                permBestOverallChiSq = chiSqs[i];
+            }
+            permutationsPerformed++;
+        }
+
+        permBestChiSq = new double[permutationsPerformed];
+        for (int i = 0; i < permutationsPerformed; i++){
+            permBestChiSq[i] = chiSqs[i];
+        }
+        Arrays.sort(permBestChiSq);
+    }
+
+    public double getBestPermChiSquare() {
+        return permBestOverallChiSq;
+    }
+
+    public double getPermPValue(double obsChiSq) {
+        int observedExceeds =0;
+
+        for (int i = 0; i < permutationsPerformed; i++){
+            if (permBestChiSq[i] < obsChiSq){
+                observedExceeds++;
+            }else{
+                break;
+            }
+        }
+
+        return 1-((double)observedExceeds / (double)permutationsPerformed);
+    }
+
+    public int getPermutationsPerformed() {
+        return permutationsPerformed;
+    }
+
+    public void setPermutationCount(int c) {
+        if(c >= 0) {
+            permutationCount = c;
+        } else {
+            permutationCount = 0;
+        }
+    }
+
+    public int getPermutationCount() {
+        return permutationCount;
+    }
+
+    public double getBestObsChiSq(){
+        return bestObsChiSq;
+    }
+
+    public String getBestObsName(){
+        return bestObsName;
+    }
+
+    public Vector getResults() {
+        Vector results = new Vector();
+        //dont loop through if we haven't done any permutations yet
+        if(permutationsPerformed > 0) {
+            Vector filteredResults = activeAssocTestSet.getFilteredResults();
+            for(int i=0;i<filteredResults.size();i++) {
+                AssociationResult tmpRes = (AssociationResult) filteredResults.get(i);
+                if (selectionType == SINGLE_ONLY && tmpRes instanceof HaplotypeAssociationResult ||
+                        selectionType == BLOCKS_ONLY && tmpRes instanceof MarkerAssociationResult){
+                    //if we're not permuting the haps or not permtuing the single SNPs, don't add them to results
+                    continue;
+                }
+                for (int j = 0; j < tmpRes.getAlleleCount(); j++){
+                    Vector fieldValues = new Vector();
+                    fieldValues.add(tmpRes.getDisplayName(j));
+                    fieldValues.add(String.valueOf(tmpRes.getChiSquare(j)));
+                    fieldValues.add(Util.formatPValue(getPermPValue(tmpRes.getChiSquare(j))));
+
+                    results.add(fieldValues);
+                    if (tmpRes instanceof MarkerAssociationResult){
+                        break;
+                    }
+                }
+            }
+
+            Collections.sort(results,new SigResComparator());
+        }
+        return results;
+    }
+
+    public int getBestExceededCount() {
+        return bestExceededCount;
+    }
+
+    public double[] getPermBestChiSq() {
+        return permBestChiSq;
+    }
+
+    public void writeResultsToFile(File outFile) throws IOException {
+        Vector results = getResults();
+
+        BufferedWriter out = new BufferedWriter(new FileWriter(outFile));
+
+        out.write("#"+getPermutationsPerformed()+" permutations performed.");
+        out.newLine();
+
+        if (getPermutationsPerformed() > 0){
+            out.write("Name\tChi Square\tPermutation p-value");
+            out.newLine();
+            for(int i=0;i<results.size();i++) {
+                Vector tempRes = (Vector) results.get(i);
+                StringBuffer line = new StringBuffer();
+                for(int j=0;j<tempRes.size()-1;j++) {
+                    line.append(tempRes.get(j));
+                    line.append("\t");
+                }
+                line.append(tempRes.get(tempRes.size()-1));
+                out.write(line.toString());
+                out.newLine();
+            }
+        }
+        out.close();
+    }
+
+    public boolean isCustom() {
+        return activeAssocTestSet.isCustom();
+    }
+
+    class SigResComparator implements Comparator{
+        public int compare(Object o1, Object o2) {
+            Double d1 = Double.valueOf(((String)((Vector)o1).elementAt(2)));
+            Double d2 = Double.valueOf(((String)((Vector)o2).elementAt(2)));
+            return d1.compareTo(d2);
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/association/TDTPanel.java b/edu/mit/wi/haploview/association/TDTPanel.java
new file mode 100755
index 0000000..34e740a
--- /dev/null
+++ b/edu/mit/wi/haploview/association/TDTPanel.java
@@ -0,0 +1,148 @@
+package edu.mit.wi.haploview.association;
+
+import edu.mit.wi.haploview.*;
+
+import javax.swing.*;
+import java.util.Vector;
+import java.util.Iterator;
+import java.util.Hashtable;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.Dimension;
+
+public class TDTPanel extends HaploviewTab
+        implements Constants, ActionListener {
+
+    static final long serialVersionUID = -3994613221202977718L;
+    private AssociationTestSet assocSet;
+    private JTable table;
+    private Vector tableColumnNames = new Vector();
+    //countsorfreqs stores the users current choice for displaying counts or frequencies.
+    //values are SHOW_SINGLE_COUNTS or SHOW_SINGLE_FREQS
+    //default is counts
+    private int countsOrFreqs;
+
+    public TDTPanel(AssociationTestSet ats){
+        this.setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
+
+        assocSet = ats;
+
+        tableColumnNames.add("#");
+        tableColumnNames.add("Name");
+        if (Options.getAssocTest() == ASSOC_TRIO){
+            tableColumnNames.add("Overtransmitted");
+            if(Options.getTdtType() == TDT_STD) {
+                tableColumnNames.add("T:U");
+            }else if(Options.getTdtType() == TDT_PAREN) {
+                tableColumnNames.add("T:U,PA:PU");
+            }
+        }else{
+            tableColumnNames.add("Assoc Allele");
+            tableColumnNames.add("Case, Control Ratios");
+        }
+        tableColumnNames.add("Chi Square");
+        tableColumnNames.add("p value");
+
+        refreshTable();
+    }
+
+    public void refreshNames() {
+        for (int i = 0; i < table.getRowCount(); i++){
+            table.setValueAt(Chromosome.getMarker(i).getDisplayName(),i,1);
+        }
+    }
+
+	public JTable getTable(){
+		return table;
+	}
+
+    public void refreshTable(){
+        this.removeAll();
+        Vector tableData = new Vector();
+        Iterator itr = assocSet.getMarkerAssociationResults().iterator();
+        Hashtable markerResultHash = new Hashtable();
+        while (itr.hasNext()){
+            MarkerAssociationResult m = (MarkerAssociationResult) itr.next();
+            markerResultHash.put(m.getSnp(), m);
+        }
+
+        for (int i = 0; i < Chromosome.getSize(); i++){
+            Vector tempVect = new Vector();
+            SNP currentMarker = Chromosome.getMarker(i);
+            MarkerAssociationResult currentResult = (MarkerAssociationResult)markerResultHash.get(currentMarker);
+            tempVect.add(new Integer(Chromosome.realIndex[i]+1));
+            tempVect.add(currentResult.getName());
+            tempVect.add(currentResult.getOverTransmittedAllele());
+            if(this.countsOrFreqs == SHOW_SINGLE_FREQS) {
+                tempVect.add(currentResult.getFreqString());
+            } else if (this.countsOrFreqs == SHOW_SINGLE_COUNTS) {
+                tempVect.add(currentResult.getCountString());
+            }
+
+            tempVect.add(new Double(currentResult.getChiSquare(0)));
+            tempVect.add(currentResult.getPValue(0));
+
+            tableData.add(tempVect.clone());
+        }
+
+        TableSorter sorter = new TableSorter(new BasicTableModel(tableColumnNames, tableData));
+        table = new JTable(sorter);
+        sorter.setTableHeader(table.getTableHeader());
+
+        table.getColumnModel().getColumn(0).setPreferredWidth(50);
+        table.getColumnModel().getColumn(1).setPreferredWidth(100);
+        if (Options.getAssocTest() != ASSOC_TRIO){
+            table.getColumnModel().getColumn(3).setPreferredWidth(160);
+        }
+        table.getColumnModel().getColumn(2).setPreferredWidth(100);
+
+
+        JScrollPane tableScroller = new JScrollPane(table);
+        //tableScroller.setMaximumSize(tableScroller.getPreferredSize());
+        tableScroller.setMaximumSize(new Dimension(600, Integer.MAX_VALUE));
+        add(tableScroller);
+
+         if(Options.getAssocTest() == ASSOC_CC) {
+            JRadioButton countsButton = new JRadioButton("Show CC counts");
+            JRadioButton ratiosButton = new JRadioButton("Show CC frequencies");
+
+            ButtonGroup bg = new ButtonGroup();
+
+            bg.add(countsButton);
+            bg.add(ratiosButton);
+            countsButton.addActionListener(this);
+            ratiosButton.addActionListener(this);
+            JPanel buttPan = new JPanel();
+            buttPan.add(countsButton);
+            buttPan.add(ratiosButton);
+            add(buttPan);
+            if(countsOrFreqs == SHOW_SINGLE_FREQS) {
+                ratiosButton.setSelected(true);
+            }else{
+                countsButton.setSelected(true);
+            }
+        }
+
+
+    }
+
+    public AssociationTestSet getTestSet() {
+        return assocSet;
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if(command.equals("Show CC counts")) {
+            this.countsOrFreqs = SHOW_SINGLE_COUNTS;
+            this.refreshTable();
+        }
+        else if (command.equals("Show CC frequencies")) {
+            this.countsOrFreqs = SHOW_SINGLE_FREQS;
+            this.refreshTable();
+        }
+    }
+}
+
+
+
+
diff --git a/edu/mit/wi/haploview/tagger/HaploviewAlleleCorrelator.java b/edu/mit/wi/haploview/tagger/HaploviewAlleleCorrelator.java
new file mode 100755
index 0000000..eb07f20
--- /dev/null
+++ b/edu/mit/wi/haploview/tagger/HaploviewAlleleCorrelator.java
@@ -0,0 +1,241 @@
+package edu.mit.wi.haploview.tagger;
+
+import edu.mit.wi.tagger.*;
+import edu.mit.wi.tagger.SNP;
+import edu.mit.wi.haploview.*;
+
+import java.util.Hashtable;
+import java.util.Vector;
+import java.util.HashSet;
+import java.util.Iterator;
+
+public class HaploviewAlleleCorrelator implements AlleleCorrelator{
+    private Hashtable indicesByVarSeq;
+    private DPrimeTable dpTable;
+    private HaploData theData;
+    private Haplotype[] phasedCache;
+    private Hashtable phasedCacheIndicesByVarSeq;
+    private Hashtable lcByComparison;
+
+    public HaploviewAlleleCorrelator(Hashtable indices, HaploData hd) {
+        indicesByVarSeq = indices;
+        dpTable = hd.dpTable;
+        theData = hd;
+        lcByComparison = new Hashtable();
+    }
+
+    public LocusCorrelation getCorrelation(VariantSequence v1, VariantSequence v2) {
+        if(v1 == v2) {
+            return new LocusCorrelation(null,1);
+        }
+
+        if (v1 instanceof SNP && v2 instanceof SNP){
+            //we are comparing two snps
+            int v1Index = ((Integer)indicesByVarSeq.get(v1)).intValue();
+            int v2Index = ((Integer)indicesByVarSeq.get(v2)).intValue();
+
+            double rsq;
+            PairwiseLinkage pl;
+            if (v1Index > v2Index){
+                pl = dpTable.getLDStats(v2Index,v1Index);
+            }else{
+                pl = dpTable.getLDStats(v1Index,v2Index);
+            }
+
+            if(pl == null) {
+                rsq = 0;
+            } else {
+                rsq =pl.getRSquared();
+            }
+
+            LocusCorrelation lc = new LocusCorrelation(null,rsq);
+            return lc;
+        }else{
+            //we are comparing a snp vs. a block
+            SNP theSNP;
+            Block theBlock;
+            if (v1 instanceof SNP){
+                theSNP = (SNP) v1;
+                theBlock = (Block) v2;
+            }else{
+                theSNP = (SNP) v2;
+                theBlock = (Block) v1;
+            }
+
+            Comparison c = new Comparison(theSNP, theBlock);
+            if (lcByComparison.containsKey(c)){
+                return (LocusCorrelation) lcByComparison.get(c);
+            }
+
+            
+            Allele curBestAllele = null;
+            double curBestRsq = 0;
+            if(HaploData.isPhasedData()){
+                double[] rsquareds;
+                int snpid = ((Integer)indicesByVarSeq.get(theSNP)).intValue();
+                int[] block = null;
+                if(theBlock.getMarkerCount() == 2){
+                    block = new int[2];
+                    block[0] = ((Integer)indicesByVarSeq.get(theBlock.getSNP(0))).intValue();
+                    block[1] = ((Integer)indicesByVarSeq.get(theBlock.getSNP(1))).intValue();
+                    HaploData.RSquared rsquaredWrapper = theData.getPhasedRSquared(snpid, block);
+                    rsquareds = rsquaredWrapper.getRsquareds();
+
+                    for(int i=0;i<rsquareds.length;i++){
+                        if(rsquareds[i] > curBestRsq) {
+                            curBestRsq = rsquareds[i];
+                            String allele = "";
+
+                            if(i < 2){
+                                allele += Chromosome.getMarker(block[0]).getMajor();
+                            }else{
+                                allele += Chromosome.getMarker(block[0]).getMinor();
+                            }
+                            if(i % 2 == 0){
+                                allele += Chromosome.getMarker(block[1]).getMajor();
+                            }else{
+                                allele += Chromosome.getMarker(block[1]).getMinor();
+                            }
+                            curBestAllele = new Allele(theBlock,allele);
+                        }
+
+                    }
+                }else if (theBlock.getMarkerCount() == 3){
+                    block = new int[3];
+                    block[0] = ((Integer)indicesByVarSeq.get(theBlock.getSNP(0))).intValue();
+                    block[1] = ((Integer)indicesByVarSeq.get(theBlock.getSNP(1))).intValue();
+                    block[2] = ((Integer)indicesByVarSeq.get(theBlock.getSNP(2))).intValue();
+                    HaploData.RSquared rsquaredWrapper = theData.getPhasedRSquared(snpid, block);
+                    rsquareds = rsquaredWrapper.getRsquareds();
+
+                    for(int i=0;i<rsquareds.length;i++){
+                        if(rsquareds[i] > curBestRsq) {
+                            curBestRsq = rsquareds[i];
+                            String allele = "";
+                            
+                            if(i < 4 ){
+                                allele += Chromosome.getMarker(block[0]).getMajor();
+                            }else{
+                                allele += Chromosome.getMarker(block[0]).getMinor();
+                            }
+                            if(i % 4 < 2){
+                                allele += Chromosome.getMarker(block[1]).getMajor();
+                            }else{
+                                allele += Chromosome.getMarker(block[1]).getMinor();
+                            }
+                            if(i % 2 == 0){
+                                allele += Chromosome.getMarker(block[2]).getMajor();
+                            }else{
+                                allele += Chromosome.getMarker(block[2]).getMinor();
+                            }
+
+                            curBestAllele = new Allele(theBlock,allele);
+                        }
+                    }
+                }
+
+            }else{
+                int[][] genos = new int[phasedCache.length][theBlock.getMarkerCount()+1];
+                for (int i = 0; i < phasedCache.length; i++){
+                    //create a temporary set of mini hap genotypes with theSNP as the first marker and theBlock's markers as the rest
+                    genos[i][0] = phasedCache[i].getGeno()[((Integer)phasedCacheIndicesByVarSeq.get(theSNP)).intValue()];
+                    for (int j = 1; j < theBlock.getMarkerCount()+1; j++){
+                        genos[i][j] = phasedCache[i].getGeno()[((Integer)phasedCacheIndicesByVarSeq.get(theBlock.getSNP(j-1))).intValue()];
+                    }
+                }
+                for (int i = 0; i < genos.length; i++){
+                    double aa=0,ab=0,bb=0,ba=0;
+                    for (int j = 0; j < genos.length; j++){
+                        if (genos[j][0] == genos[0][0]){
+                            if(!sameHap(genos[i], genos[j])){
+                                ab += phasedCache[j].getPercentage();
+                            }else{
+                                aa += phasedCache[j].getPercentage();
+                            }
+                        }else{
+                            if(!sameHap(genos[i], genos[j])){
+                                bb += phasedCache[j].getPercentage();
+                            }else{
+                                ba += phasedCache[j].getPercentage();
+                            }
+                        }
+                    }
+                    //p is snp's freq, q is hap's freq
+                    double p = aa+ab;
+                    double q = ba+aa;
+                    //round to 5 decimal places.
+                    double rsq = Util.roundDouble(Math.pow((aa*bb - ab*ba),2)/(p*(1-p)*q*(1-q)),3);
+                    if (rsq > curBestRsq){
+                        StringBuffer sb = new StringBuffer();
+                        for (int j = 1; j < genos[i].length; j++){
+                            sb.append(genos[i][j]);
+                        }
+                        curBestAllele = new Allele(theBlock,sb.toString());
+                        curBestRsq = rsq;
+                    }
+                }
+
+            }
+            LocusCorrelation lc = new LocusCorrelation(curBestAllele, curBestRsq);
+            lcByComparison.put(new Comparison(theSNP, theBlock),lc);
+
+            return (lc);
+        }
+    }
+
+    private boolean sameHap(int[] a, int[] b) {
+        if(a == null || b == null) {
+            throw new NullPointerException("blah");
+        }
+        if(a.length != b.length) {
+            return false;
+        }
+        for(int i=1;i<a.length;i++) {
+            if(a[i] != b[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void phaseAndCache(HashSet snpList){
+        phasedCacheIndicesByVarSeq = new Hashtable();
+        if(!HaploData.isPhasedData()){
+            try{
+                //create a pseudo block of the SNP to be correlated and the markers from the multi-test
+                int[] blockArray = new int[snpList.size()];
+                Iterator itr = snpList.iterator();
+                for (int i = 0; i < blockArray.length; i++){
+                    SNP n = (SNP) itr.next();
+                    blockArray[i] = ((Integer)indicesByVarSeq.get(n)).intValue();
+                    phasedCacheIndicesByVarSeq.put(n,new Integer(i));
+                }
+
+                Vector victor = new Vector();
+                victor.add(blockArray);
+                phasedCache = theData.generateHaplotypes(victor, true)[0];
+            }catch (HaploViewException hve){
+                throw new RuntimeException("PC_LOADLETTER.\n" + hve.getMessage());
+            }
+        }
+    }
+
+    class Comparison{
+        SNP s;
+        Block b;
+
+        public Comparison(SNP s, Block b){
+            this.s = s;
+            this.b = b;
+        }
+
+        public boolean equals(Object o){
+            Comparison c = (Comparison) o;
+            return (s.equals(c.s) && b.equals(c.b));
+        }
+
+        public int hashCode(){
+            return s.hashCode() + b.hashCode();
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/tagger/TaggerConfigPanel.java b/edu/mit/wi/haploview/tagger/TaggerConfigPanel.java
new file mode 100755
index 0000000..bfdd189
--- /dev/null
+++ b/edu/mit/wi/haploview/tagger/TaggerConfigPanel.java
@@ -0,0 +1,604 @@
+package edu.mit.wi.haploview.tagger;
+
+import edu.mit.wi.haploview.*;
+import edu.mit.wi.tagger.Tagger;
+import edu.mit.wi.tagger.TaggerException;
+
+import javax.swing.*;
+import javax.swing.event.TableModelListener;
+import javax.swing.event.TableModelEvent;
+import javax.swing.table.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.Dimension;
+import java.awt.Color;
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+import java.io.*;
+
+public class TaggerConfigPanel extends HaploviewTab
+        implements TableModelListener, ActionListener {
+    static final long serialVersionUID = 1616877753933097437L;
+    private JTable table;
+    private TableSorter sorter;
+    private TaggerController tagControl;
+
+    private final static int NUM_COL = 0;
+    private final static int NAME_COL = 1;
+    private final static int POSITION_COL = 2;
+    private final static int DESIGN_COL = 3;
+    private final static int INCLUDE_COL = 4;
+    private final static int EXCLUDE_COL = 5;
+    private final static int CAPTURE_COL = 6;
+
+    private JButton runTaggerButton;
+    public static JFileChooser fc;
+    private Timer timer;
+    private HaploData theData;
+    private Hashtable snpsByName, designScores;
+    private NumberTextField rsqField, minDesignField, lodField, maxNumTagsField, minDistField;
+    private ButtonGroup aggressiveGroup;
+    private JPanel bottomButtonPanel = new JPanel();
+    private JPanel taggerProgressPanel = new JPanel();
+    JProgressBar taggerProgress = new JProgressBar();
+    private JLabel taggerProgressLabel = new JLabel("Tagging...");
+    private boolean plinkExists = false;
+
+    public TaggerConfigPanel(HaploData hd, boolean plink)  {
+        theData = hd;
+        plinkExists = plink;
+        refreshTable();
+        try{
+            fc = new JFileChooser(System.getProperty("user.dir"));
+        }catch(NullPointerException n){
+            try{
+                UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
+                fc = new JFileChooser(System.getProperty("user.dir"));
+                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+            }catch(Exception e){
+                JOptionPane.showMessageDialog(this,
+                        e.getMessage(),
+                        "Error",
+                        JOptionPane.ERROR_MESSAGE);
+            }
+        }
+    }
+
+    public void tableChanged(TableModelEvent e) {
+        if (e.getColumn() == INCLUDE_COL){
+            //if they check force include for some row, then we uncheck force exclude for that row
+            if(((Boolean)table.getValueAt(e.getFirstRow(),e.getColumn())).booleanValue()) {
+                table.setValueAt(new Boolean(false),e.getFirstRow(),EXCLUDE_COL);
+            }
+        }
+        else if(e.getColumn() == EXCLUDE_COL) {
+            //if they check force exclude for some row, then we uncheck force include for that row
+            if(((Boolean)table.getValueAt(e.getFirstRow(),e.getColumn())).booleanValue()) {
+                table.setValueAt(new Boolean(false),e.getFirstRow(),INCLUDE_COL);
+            }
+        }else if(e.getColumn() == CAPTURE_COL) {
+            if(!((Boolean)table.getValueAt(e.getFirstRow(),e.getColumn())).booleanValue()) {
+                table.setValueAt(new Boolean(false),e.getFirstRow(),EXCLUDE_COL);
+                table.setValueAt(new Boolean(false),e.getFirstRow(),INCLUDE_COL);
+            }
+        }
+    }
+
+    public void refreshTable(){
+        this.removeAll();
+
+        snpsByName = new Hashtable();
+
+        this.setLayout(new BoxLayout(this,BoxLayout.Y_AXIS));
+        Vector columnNames = new Vector();
+        Vector tableData = new Vector();
+
+        columnNames.add("#");
+        columnNames.add("Name");
+        columnNames.add("Position");
+        columnNames.add("Design Score");
+        columnNames.add("Force Include");
+        columnNames.add("Force Exclude");
+        columnNames.add("Capture this Allele?");
+
+        for (int i = 0; i < Chromosome.getSize(); i++){
+            SNP tempSNP = Chromosome.getMarker(i);
+            snpsByName.put(tempSNP.getDisplayName(), tempSNP);
+            Vector tempData = new Vector();
+
+            tempData.add(Integer.toString(Chromosome.realIndex[i]+1));
+            tempData.add(tempSNP.getDisplayName());
+            tempData.add(String.valueOf(tempSNP.getPosition()));
+            tempData.add("0");
+            tempData.add(new Boolean(false));
+            tempData.add(new Boolean(false));
+            tempData.add(new Boolean(true));
+
+            tableData.add(tempData);
+        }
+        TagConfigTableModel tableModel = new TagConfigTableModel(columnNames,tableData);
+        tableModel.addTableModelListener(this);
+        sorter = new TableSorter(tableModel);
+        table = new JTable(sorter);
+        sorter.setTableHeader(table.getTableHeader());
+        table.getColumnModel().getColumn(NUM_COL).setPreferredWidth(30);
+        table.getColumnModel().getColumn(POSITION_COL).setPreferredWidth(60);
+        table.getColumnModel().getColumn(DESIGN_COL).setPreferredWidth(60);
+        table.getColumnModel().getColumn(INCLUDE_COL).setPreferredWidth(65);
+        table.getColumnModel().getColumn(EXCLUDE_COL).setPreferredWidth(65);
+        table.getColumnModel().getColumn(CAPTURE_COL).setPreferredWidth(90);
+        table.getTableHeader().setReorderingAllowed(false);
+
+
+        JScrollPane scrollPane = new JScrollPane(table);
+        scrollPane.setPreferredSize(new Dimension(600, 700));
+        scrollPane.setMaximumSize(scrollPane.getPreferredSize());
+        //scrollPane.getViewport().setBackground(Color.WHITE);
+        add(scrollPane);
+
+        runTaggerButton = new JButton("<html><b>Run Tagger</b>");
+        runTaggerButton.addActionListener(this);
+        runTaggerButton.setActionCommand("Run Tagger");
+        JButton includeResultsButton = new JButton("Force in PLINK SNPs");
+        includeResultsButton.addActionListener(this);
+        JButton resetTableButton = new JButton("Reset Table");
+        resetTableButton.addActionListener(this);
+        JButton forceIncludeButton = new JButton("Load Includes");
+        forceIncludeButton.addActionListener(this);
+        JButton includeAllButton = new JButton("Include All");
+        includeAllButton.addActionListener(this);
+        JButton forceExcludeButton = new JButton("Load Excludes");
+        forceExcludeButton.addActionListener(this);
+        JButton excludeAllButton = new JButton("Exclude All");
+        excludeAllButton.addActionListener(this);
+        JButton excludeATCGButton = new JButton("Exclude A/T & C/G SNPs");
+        excludeATCGButton.addActionListener(this);
+        JButton uncaptureAllButton = new JButton("Uncapture All");
+        uncaptureAllButton.addActionListener(this);
+        JButton designScoresButton = new JButton("Design Scores");
+        designScoresButton.addActionListener(this);
+        JButton allelesCapturedButton = new JButton("Alleles to Capture");
+        allelesCapturedButton.addActionListener(this);
+        JButton resetThresholdsButton = new JButton("Reset Thresholds");
+        resetThresholdsButton.addActionListener(this);
+
+        JPanel buttonPanel = new JPanel();
+        //buttonPanel.setMaximumSize(new Dimension(600,100));
+        buttonPanel.add(includeAllButton);
+        buttonPanel.add(excludeAllButton);
+        buttonPanel.add(uncaptureAllButton);
+        buttonPanel.add(excludeATCGButton);
+        buttonPanel.add(resetTableButton);
+        add(buttonPanel);
+
+        JPanel optsRightPanel = new JPanel();
+        optsRightPanel.setLayout(new BoxLayout(optsRightPanel, BoxLayout.Y_AXIS));
+
+        JPanel rsqPanel = new JPanel();
+        JLabel rsqLabel = new JLabel("r\u00b2 threshold");
+        rsqPanel.add(rsqLabel);
+        rsqField = new NumberTextField(String.valueOf(Options.getTaggerRsqCutoff()),5,true,false);
+        rsqPanel.add(rsqField);
+        JLabel mdsLabel = new JLabel("Min design score");
+        rsqPanel.add(mdsLabel);
+        minDesignField = new NumberTextField("",5,true,false);
+        rsqPanel.add(minDesignField);
+        optsRightPanel.add(rsqPanel);
+
+        JPanel lodPanel = new JPanel();
+        JLabel lodLabel = new JLabel("LOD threshold for multi-marker tests");
+        lodPanel.add(lodLabel);
+        lodField = new NumberTextField(String.valueOf(Options.getTaggerLODCutoff()),5,true,false);
+        lodPanel.add(lodField);
+        optsRightPanel.add(lodPanel);
+
+        JPanel maxNumPanel = new JPanel();
+        maxNumPanel.add(new JLabel("Max tags"));
+        maxNumTagsField = new NumberTextField("",5,false,false);
+        maxNumPanel.add(maxNumTagsField);
+        maxNumPanel.add(new JLabel("Min distance between tags"));
+        minDistField = new NumberTextField("0",5,false,false);
+        maxNumPanel.add(minDistField);
+        maxNumPanel.add(new JLabel("bp"));
+
+        optsRightPanel.add(maxNumPanel);
+
+        JPanel optsLeftPanel = new JPanel();
+        optsLeftPanel.setLayout(new BoxLayout(optsLeftPanel, BoxLayout.Y_AXIS));
+        JRadioButton pairwiseButton = new JRadioButton("pairwise tagging only");
+        pairwiseButton.setActionCommand(String.valueOf(Tagger.PAIRWISE_ONLY));
+        optsLeftPanel.add(pairwiseButton);
+        JRadioButton dupleButton = new JRadioButton("aggressive tagging: use 2-marker haplotypes");
+        dupleButton.setActionCommand(String.valueOf(Tagger.AGGRESSIVE_DUPLE));
+        optsLeftPanel.add(dupleButton);
+        JRadioButton tripleButton = new JRadioButton("aggressive tagging: use 2- and 3-marker haplotypes");
+        tripleButton.setActionCommand(String.valueOf(Tagger.AGGRESSIVE_TRIPLE));
+        optsLeftPanel.add(tripleButton);
+        aggressiveGroup = new ButtonGroup();
+        aggressiveGroup.add(pairwiseButton);
+        aggressiveGroup.add(dupleButton);
+        aggressiveGroup.add(tripleButton);
+        pairwiseButton.setSelected(true);
+
+        JPanel optsPanel = new JPanel();
+        //optsPanel.setMaximumSize(new Dimension(800,600));
+        //preferredViewPortsize
+        optsPanel.add(optsLeftPanel);
+        optsPanel.add(optsRightPanel);
+        add(optsPanel);
+
+        bottomButtonPanel = new JPanel();
+        bottomButtonPanel.setPreferredSize(new Dimension(350,100));
+        //bottomButtonPanel.
+        //bottomButtonPanel.setBorder(BorderFactory.createLineBorder(Color.black));
+        bottomButtonPanel.add(runTaggerButton);
+        if (plinkExists){
+            bottomButtonPanel.add(includeResultsButton);
+        }
+        bottomButtonPanel.add(forceIncludeButton);
+        bottomButtonPanel.add(forceExcludeButton);
+        bottomButtonPanel.add(allelesCapturedButton);
+        bottomButtonPanel.add(designScoresButton);
+        bottomButtonPanel.add(resetThresholdsButton);
+
+        add(bottomButtonPanel);
+    }
+
+    public void addActionListener(ActionListener al){
+        listenerList.add(ActionListener.class, al);
+    }
+
+    public void fireTaggerEvent(ActionEvent ae){
+        Object listeners[] = listenerList.getListenerList();
+        for (int i = 0; i <= listeners.length-1; i+=2){
+            if (listeners[i] == ActionListener.class){
+                ((ActionListener)listeners[i+1]).actionPerformed(ae);
+            }
+        }
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if (command.equals("Run Tagger")) {
+            for (int j = 0; j < table.getColumnCount(); j++){
+                sorter.setSortingStatus(j,TableSorter.NOT_SORTED);
+            }
+
+            try{
+                if(!rsqField.getText().equals("")){
+                    double rsqCut = Double.parseDouble(rsqField.getText());
+                    if (rsqCut > 1){
+                        Options.setTaggerRsqCutoff(1.0);
+                        rsqField.setText("1.0");
+                    }else if (rsqCut < 0){
+                        Options.setTaggerRsqCutoff(0.0);
+                        rsqField.setText("0.0");
+                    }else{
+                        Options.setTaggerRsqCutoff(rsqCut);
+                    }
+                }
+
+                if (!(minDesignField.getText().equals(""))){
+                    Options.setTaggerMinDesignScore(Double.parseDouble(minDesignField.getText()));
+                }else{
+                    Options.setTaggerMinDesignScore(Tagger.DEFAULT_MIN_DESIGNSCORE);
+                }
+
+                if (!lodField.getText().equals("")){
+                    double lodCut = Double.parseDouble(lodField.getText());
+                    if (lodCut < 0){
+                        Options.setTaggerLODCutoff(0.0);
+                        lodField.setText("0.0");
+                    }else{
+                        Options.setTaggerLODCutoff(lodCut);
+                    }
+                }
+
+                int minDist;
+                if (minDistField.getText().equals("")){
+                    minDist = 0;
+                }else{
+                    minDist = Integer.parseInt(minDistField.getText());
+                }
+                if (minDist < 0){
+                    Options.setTaggerMinDistance(0);
+                    minDistField.setText("");
+                }else{
+                    Options.setTaggerMinDistance(minDist);
+                }
+
+                int maxNumTags;
+                if (maxNumTagsField.getText().equals("")){
+                    maxNumTags = 0;
+                }else{
+                    maxNumTags = Integer.parseInt(maxNumTagsField.getText());
+                }
+
+                //build include/exclude lists
+                Vector include = new Vector();
+                Vector exclude = new Vector();
+                Vector capture = new Vector();
+                for(int i= 0;i <table.getRowCount(); i++) {
+                    if(((Boolean)table.getValueAt(i,INCLUDE_COL)).booleanValue()) {
+                        include.add((String)table.getValueAt(i,NAME_COL));
+                    }else if(((Boolean)table.getValueAt(i,EXCLUDE_COL)).booleanValue()) {
+                        exclude.add((String)table.getValueAt(i,NAME_COL));
+                    }
+                    if (((Boolean)table.getValueAt(i,CAPTURE_COL)).booleanValue()){
+                        capture.add(snpsByName.get(table.getValueAt(i,NAME_COL)));
+                    }
+                }
+
+                tagControl = new TaggerController(theData,include,exclude,capture,designScores,
+                        Integer.valueOf(aggressiveGroup.getSelection().getActionCommand()).intValue(),maxNumTags,true);
+
+                runTaggerButton.setEnabled(false);
+
+                taggerProgress.setIndeterminate(true);
+                taggerProgress.setForeground(new Color(40,40,255));
+                taggerProgress.setMaximumSize(new Dimension(250,20));
+                taggerProgressPanel.setLayout(new BoxLayout(taggerProgressPanel,BoxLayout.Y_AXIS));
+                taggerProgressPanel.add(taggerProgressLabel);
+                taggerProgressLabel.setAlignmentX(CENTER_ALIGNMENT);
+                taggerProgressPanel.add(new JLabel("         "));
+                taggerProgressPanel.add(taggerProgress);
+                remove(bottomButtonPanel);
+                add(taggerProgressPanel);
+                revalidate();
+
+                tagControl.runTagger();
+
+                final TaggerConfigPanel tcp = this;
+                timer = new Timer(100, new ActionListener(){
+                    public void actionPerformed(ActionEvent e) {
+                        if(tagControl.isTaggingCompleted()) {
+                            remove(taggerProgressPanel);
+                            add(bottomButtonPanel);
+                            runTaggerButton.setEnabled(true);
+                            //the parent of this is the jtabbedPane in the tagger tab of HV
+                            ((JTabbedPane)(tcp.getParent())).setSelectedIndex(1);
+                            fireTaggerEvent(new ActionEvent(tcp,ActionEvent.ACTION_PERFORMED,"taggingdone"));
+                            timer.stop();
+                        }
+                    }
+                });
+
+                timer.start();
+            }catch (TaggerException t){
+                JOptionPane.showMessageDialog(this,
+                        t.getMessage(),
+                        "Tagger",
+                        JOptionPane.ERROR_MESSAGE);
+            }
+
+        }else if (command.equals("Reset Table")){
+            for (int j = 0; j < table.getColumnCount(); j++){
+                sorter.setSortingStatus(j,TableSorter.NOT_SORTED);
+            }
+
+            for (int i = 0; i < table.getRowCount(); i++){
+                table.setValueAt(new Boolean(false), i, EXCLUDE_COL);
+                table.setValueAt(new Boolean(false), i, INCLUDE_COL);
+                table.setValueAt(new Boolean(true), i, CAPTURE_COL);
+            }
+            rsqField.setText(String.valueOf(Tagger.DEFAULT_RSQ_CUTOFF));
+        }else if (command.equals("Load Includes")){
+            Hashtable forceIncludes = new Hashtable(1,1);
+            fc.setSelectedFile(new File(""));
+            int returnVal = fc.showOpenDialog(this);
+            if (returnVal == JFileChooser.APPROVE_OPTION){
+                try{
+                    BufferedReader br = new BufferedReader(new FileReader(fc.getSelectedFile()));
+                    String line;
+                    while((line = br.readLine()) != null) {
+                        if(line.length() > 0 && line.charAt(0) != '#'){
+                            line = line.trim();
+                            forceIncludes.put(line,"I");
+                        }
+                    }
+                }catch(IOException ioe){
+                    //throw new IOException("An error occured while reading the force includes file.");
+                }
+            }
+
+            for (int i = 0; i < table.getRowCount(); i++){
+                if (forceIncludes.containsKey(table.getValueAt(i,NAME_COL))){
+                    table.setValueAt(new Boolean(true),i,INCLUDE_COL);
+                    table.setValueAt(new Boolean(true),i,CAPTURE_COL);
+                }
+            }
+        }else if (command.equals("Include All")){
+            for (int i = 0; i < table.getRowCount(); i++){
+                table.setValueAt(new Boolean(true),i,INCLUDE_COL);
+                table.setValueAt(new Boolean(true),i,CAPTURE_COL);
+            }
+        }else if (command.equals("Load Excludes")){
+            Hashtable forceExcludes = new Hashtable(1,1);
+            fc.setSelectedFile(new File(""));
+            int returnVal = fc.showOpenDialog(this);
+            if (returnVal == JFileChooser.APPROVE_OPTION){
+                try{
+                    BufferedReader br = new BufferedReader(new FileReader(fc.getSelectedFile()));
+                    String line;
+                    while((line = br.readLine()) != null) {
+                        if(line.length() > 0 && line.charAt(0) != '#'){
+                            line = line.trim();
+                            forceExcludes.put(line,"E");
+                        }
+                    }
+                }catch(IOException ioe){
+                    //throw new IOException("An error occured while reading the force excludes file.");
+                }
+            }
+
+            for (int j = 0; j < table.getRowCount(); j++){
+                if (forceExcludes.containsKey(table.getValueAt(j,NAME_COL))){
+                    table.setValueAt(new Boolean(true),j,EXCLUDE_COL);
+                    table.setValueAt(new Boolean(true),j,CAPTURE_COL);
+                }
+            }
+        }else if (command.equals("Exclude All")){
+            for (int i = 0; i < table.getRowCount(); i++){
+                table.setValueAt(new Boolean(true),i,EXCLUDE_COL);
+                table.setValueAt(new Boolean(true),i,CAPTURE_COL);
+            }
+        }else if (command.equals("Uncapture All")){
+            for (int i = 0; i < table.getRowCount(); i++){
+                table.setValueAt(new Boolean(false),i,CAPTURE_COL);
+            }
+        }else if (command.equals("Exclude A/T & C/G SNPs")){
+            for (int i = 0; i < table.getRowCount(); i++){
+                if (((SNP)snpsByName.get(table.getValueAt(i,NAME_COL))).getStrandIssue()){
+                    table.setValueAt(new Boolean(true),i,EXCLUDE_COL);
+                    table.setValueAt(new Boolean(true),i,CAPTURE_COL);
+                }
+            }
+        }else if (command.equals("Design Scores")){
+            designScores = new Hashtable(1,1);
+            fc.setSelectedFile(new File(""));
+            int returnVal = fc.showOpenDialog(this);
+            if (returnVal == JFileChooser.APPROVE_OPTION){
+                try{
+                    BufferedReader br = new BufferedReader(new FileReader(fc.getSelectedFile()));
+                    String line;
+                    while((line = br.readLine()) != null) {
+                        if(line.length() > 0 && line.charAt(0) != '#'){
+                            StringTokenizer st = new StringTokenizer(line);
+                            String marker = st.nextToken();
+                            Double score = new Double(st.nextToken());
+                            designScores.put(marker,score);
+                        }
+                    }
+                }catch(IOException ioe){
+                    JOptionPane.showMessageDialog(this,
+                            "Error reading the design scores file",
+                            "File Error",
+                            JOptionPane.ERROR_MESSAGE);
+                    return;
+                }catch(NumberFormatException nfe){
+                    JOptionPane.showMessageDialog(this,
+                            "Invalid file formatting",
+                            "File Error",
+                            JOptionPane.ERROR_MESSAGE);
+                    return;
+                }
+                for (int i = 0; i < table.getRowCount();i++){
+                    if (designScores.containsKey(table.getValueAt(i,NAME_COL))){
+                        table.setValueAt(designScores.get(table.getValueAt(i,NAME_COL)),i,DESIGN_COL);
+                    }
+                }
+            }
+        }else if (command.equals("Alleles to Capture")){
+            Hashtable allelesCaptured = new Hashtable(1,1);
+            fc.setSelectedFile(new File(""));
+            int returnVal = fc.showOpenDialog(this);
+            if (returnVal == JFileChooser.APPROVE_OPTION){
+                try{
+                    BufferedReader br = new BufferedReader(new FileReader(fc.getSelectedFile()));
+                    String line;
+                    while((line = br.readLine()) != null) {
+                        if(line.length() > 0 && line.charAt(0) != '#'){
+                            line = line.trim();
+                            allelesCaptured.put(line,"C");
+                        }
+                    }
+                }catch(IOException ioe){
+                    JOptionPane.showMessageDialog(this,
+                            "Error reading the alleles file",
+                            "File Error",
+                            JOptionPane.ERROR_MESSAGE);
+                    return;
+                }
+
+                for (int j = 0; j < table.getRowCount(); j++){
+                    if (allelesCaptured.containsKey(table.getValueAt(j,NAME_COL))){
+                        table.setValueAt(new Boolean(true),j,CAPTURE_COL);
+                    }else{
+                        table.setValueAt(new Boolean(false),j,CAPTURE_COL);
+                        table.setValueAt(new Boolean(false),j,INCLUDE_COL);
+                        table.setValueAt(new Boolean(false),j,EXCLUDE_COL);
+                    }
+                }
+            }
+        }else if (command.equals("Reset Thresholds")){
+            rsqField.setText(Double.toString(Tagger.DEFAULT_RSQ_CUTOFF));
+            lodField.setText(Double.toString(Tagger.DEFAULT_LOD_CUTOFF));
+            minDistField.setText(Short.toString(Tagger.DEFAULT_MIN_DISTANCE));
+            maxNumTagsField.setText("");
+            minDesignField.setText("");
+        }else if (command.equals("Force in PLINK SNPs")){
+            Hashtable forceIncludes = new Hashtable(1,1);
+            for (int i = 0; i < Chromosome.getSize(); i++){
+                if (Chromosome.getMarker(i).getExtra() != null){
+                    forceIncludes.put(Chromosome.getMarker(i).getDisplayName(),"");
+                }
+            }
+
+            for (int i = 0; i < table.getRowCount(); i++){
+                if (forceIncludes.containsKey(table.getValueAt(i,NAME_COL))){
+                    table.setValueAt(new Boolean(true),i,INCLUDE_COL);
+                    table.setValueAt(new Boolean(true),i,CAPTURE_COL);
+                }
+            }
+        }
+    }
+
+    public TaggerController getTaggerController() {
+        return tagControl;
+    }
+
+    public void export(File outfile) throws IOException, HaploViewException{
+        if (tagControl != null){
+            tagControl.saveResultsToFile(outfile);
+        }else{
+            throw new HaploViewException("Tagger has not yet generated any results");
+        }
+    }
+
+    class TagConfigTableModel extends AbstractTableModel {
+        static final long serialVersionUID = 5952046961517951909L;
+        Vector columnNames; Vector data;
+
+        public TagConfigTableModel(Vector c, Vector d){
+            columnNames=c;
+            data=d;
+        }
+
+        public int getColumnCount(){
+            return columnNames.size();
+        }
+
+        public int getRowCount(){
+            return data.size();
+        }
+
+        public Object getValueAt(int row, int column){
+            return ((Vector)data.elementAt(row)).elementAt(column);
+        }
+
+        public Class getColumnClass(int c){
+            return getValueAt(0, c).getClass();
+        }
+
+        public String getColumnName(int n){
+            return (String)columnNames.elementAt(n);
+        }
+
+        public boolean isCellEditable(int row, int col){
+            if (col == CAPTURE_COL) {
+                return true;
+            }else if(col == INCLUDE_COL || col == EXCLUDE_COL){
+                if(((Boolean)((Vector)data.get(row)).get(CAPTURE_COL)).booleanValue()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public void setValueAt(Object value, int row, int col){
+            ((Vector)data.elementAt(row)).set(col, value);
+            fireTableCellUpdated(row, col);
+        }
+    }
+}
diff --git a/edu/mit/wi/haploview/tagger/TaggerController.java b/edu/mit/wi/haploview/tagger/TaggerController.java
new file mode 100755
index 0000000..d6e8521
--- /dev/null
+++ b/edu/mit/wi/haploview/tagger/TaggerController.java
@@ -0,0 +1,177 @@
+package edu.mit.wi.haploview.tagger;
+
+import edu.mit.wi.tagger.*;
+import edu.mit.wi.haploview.*;
+import edu.mit.wi.haploview.SNP;
+
+import java.util.Vector;
+import java.util.Hashtable;
+import java.io.File;
+import java.io.IOException;
+
+
+public class TaggerController {
+    private Tagger tagger;
+    private Vector results;
+    private int numToCapture;
+    private boolean taggingCompleted;
+    private Hashtable snpHash;
+
+    public TaggerController(HaploData hd, Vector included, Vector excluded,
+                            Vector sitesToCapture, Hashtable designScores, int aggressionLevel, int maxNumTags,boolean findTags)
+            throws TaggerException{
+        Vector taggerSNPs = new Vector();
+
+        snpHash = new Hashtable();
+
+        numToCapture = sitesToCapture.size();
+
+        for(int i=0;i<sitesToCapture.size();i++) {
+            SNP tempSNP = (SNP) sitesToCapture.get(i);
+            edu.mit.wi.tagger.SNP s = new edu.mit.wi.tagger.SNP(tempSNP.getDisplayName(),tempSNP.getPosition(),tempSNP.getMAF());
+            taggerSNPs.add(s);
+            snpHash.put(tempSNP.getDisplayName(),s);
+        }
+
+        Vector includedSNPs = new Vector();
+        for(int i=0;i<included.size();i++) {
+            if(snpHash.containsKey(included.get(i))){
+                includedSNPs.add(snpHash.get(included.get(i)));
+            }
+        }
+
+        Vector excludedSNPs = new Vector();
+        for(int i=0;i<excluded.size();i++) {
+            if(snpHash.containsKey(excluded.get(i))){
+                excludedSNPs.add(snpHash.get(excluded.get(i)));
+            }
+        }
+
+        Hashtable indicesByVarSeq = new Hashtable();
+        for(int i=0;i<Chromosome.getSize();i++) {
+            if(snpHash.containsKey(Chromosome.getMarker(i).getDisplayName())){
+                indicesByVarSeq.put(snpHash.get(Chromosome.getMarker(i).getDisplayName()),new Integer(i));
+            }
+        }
+
+        for (int i = 0; i < sitesToCapture.size(); i++){
+            SNP tempSNP = (SNP) sitesToCapture.get(i);
+            edu.mit.wi.tagger.SNP taggerSNP = (edu.mit.wi.tagger.SNP) snpHash.get(tempSNP.getDisplayName());
+            int p = ((Integer)indicesByVarSeq.get(taggerSNP)).intValue();
+            for (int j = 1; j < hd.dpTable.getLength(p); j++){
+                PairwiseLinkage pl = hd.dpTable.getLDStats(p,j+p);
+                if (pl != null && pl.getLOD() >= Options.getTaggerLODCutoff()){
+                    if (indicesByVarSeq.containsValue(new Integer(j+p))){
+                        edu.mit.wi.tagger.SNP ldsnp =
+                                (edu.mit.wi.tagger.SNP) snpHash.get(Chromosome.getMarker(j+p).getDisplayName());
+                        taggerSNP.addToLDList(ldsnp);
+                        ldsnp.addToLDList(taggerSNP);
+                    }
+                }
+            }
+        }
+
+        HaploviewAlleleCorrelator hac = new HaploviewAlleleCorrelator(indicesByVarSeq, hd);
+        tagger = new Tagger(taggerSNPs,includedSNPs,excludedSNPs, designScores, hac, Options.getTaggerRsqCutoff(),
+                aggressionLevel, Options.getMaxDistance(), maxNumTags,findTags,Options.isPrintAllTags());
+    }
+
+    public void runTagger() {
+        TagThread tagThread = new TagThread(tagger);
+        taggingCompleted = false;
+        tagThread.start();
+    }
+
+    public int getTaggedSoFar() {
+        return tagger.taggedSoFar;
+    }
+
+    public int getUntaggableCount() {
+        return tagger.getUntaggableCount();
+    }
+
+    public Vector getForceIncludeds(){
+        return tagger.getForceInclude();
+    }
+
+    public Vector getMarkerTagDetails(int i){
+        //returns a vector with the details of how this marker was tagged:
+        //name, tag_name, r^2 with its tag
+        Vector res = new Vector();
+        String name = Chromosome.getMarker(i).getDisplayName();
+        res.add(name);
+        if (snpHash.containsKey(name)){
+            edu.mit.wi.tagger.SNP ts = (edu.mit.wi.tagger.SNP)snpHash.get(name);
+            TagSequence bestTag = ts.getBestTag();
+            if(bestTag != null) {
+                res.add(bestTag.getName());
+                res.add(String.valueOf(tagger.getPairwiseCompRsq(ts,bestTag.getSequence())));
+            } else {
+                res.add("Untaggable");
+                res.add(new String());
+            }
+        }else{
+            res.add(new String());
+            res.add(new String());
+        }
+
+        return res;
+    }
+
+    private void taggingFinished() {
+        taggingCompleted = true;
+    }
+
+    public boolean isTaggingCompleted() {
+        return taggingCompleted;
+    }
+
+    public Vector getResults() {
+        return results;
+    }
+
+    public int getNumTagSNPs(){
+        return tagger.getTagSNPs().size();
+    }
+
+    public int getNumToCapture(){
+        return numToCapture;
+    }
+
+    public void saveResultsToFile(File outFile) throws IOException {
+        if(taggingCompleted) {
+            tagger.saveResultToFile(outFile);
+        }
+    }
+
+    public void dumpTests(File outFile) throws IOException {
+        tagger.dumpTests(outFile);
+    }
+
+    public void dumpTags(File outFile) throws IOException {
+        tagger.dumpTags(outFile);
+    }
+
+    public void dumpConditionalHaps(File outFile) throws IOException {
+        tagger.dumpConditionalHaps(outFile);
+    }
+
+    private class TagThread extends Thread{
+        Tagger tagger;
+        public TagThread(Tagger t) {
+            tagger =t;
+        }
+        public void run() {
+            results = tagger.findTags();
+            taggingFinished();
+        }
+    }
+
+    public double getMeanRSq(){
+        return tagger.getMeanRSq();
+    }
+
+    public int getPercentCaptured(){
+        return tagger.getPercentCaptured();
+    }
+}
diff --git a/edu/mit/wi/haploview/tagger/TaggerResultsPanel.java b/edu/mit/wi/haploview/tagger/TaggerResultsPanel.java
new file mode 100755
index 0000000..f9ef153
--- /dev/null
+++ b/edu/mit/wi/haploview/tagger/TaggerResultsPanel.java
@@ -0,0 +1,239 @@
+package edu.mit.wi.haploview.tagger;
+
+import javax.swing.*;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.ListSelectionEvent;
+import java.util.Vector;
+import java.awt.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.IOException;
+
+import edu.mit.wi.tagger.*;
+import edu.mit.wi.haploview.*;
+
+public class TaggerResultsPanel extends HaploviewTab
+        implements ListSelectionListener, ActionListener {
+    static final long serialVersionUID = -3878872990341697973L;
+    private JList tagList;
+    private JList taggedList;
+    private TaggerController tc;
+
+    private Vector tags;
+    private Vector forceIncluded;
+
+    public void setTags(TaggerController t) {
+        tc = t;
+
+        removeAll();
+        setLayout(new BoxLayout(this,BoxLayout.X_AXIS));
+
+        Vector colNames = new Vector();
+        Vector tableData = new Vector();
+
+        colNames.add("Allele");
+        colNames.add("Test");
+        colNames.add("r\u00b2");
+
+
+        for (int i = 0; i < Chromosome.getSize(); i++){
+            Vector v = t.getMarkerTagDetails(i);
+            tableData.add(v);
+        }
+
+        BasicTableModel btm = new BasicTableModel(colNames, tableData);
+        JTable markerTable = new JTable(btm);
+        GreyedOutRenderer gor = new GreyedOutRenderer();
+        markerTable.setDefaultRenderer(String.class,gor);
+
+        JScrollPane tableScroller = new JScrollPane(markerTable);
+
+        tags = t.getResults();
+        forceIncluded = new Vector();
+        Vector fi = t.getForceIncludeds();
+        for (int i = 0; i < fi.size(); i++){
+            forceIncluded.add(((edu.mit.wi.tagger.SNP)fi.get(i)).getName());
+        }
+
+        DefaultListModel tagListModel = new DefaultListModel();
+        for(int i=0;i<tags.size();i++){
+            TagSequence ts = (TagSequence)tags.get(i);
+            tagListModel.addElement(ts.getName());
+        }
+
+        tagList = new JList(tagListModel);
+        tagList.setCellRenderer(new TagListRenderer());
+        tagList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+        tagList.setSelectedIndex(0);
+        tagList.addListSelectionListener(this);
+        tagList.setPreferredSize(new Dimension(tagList.getPreferredSize().width + 10,
+                tagList.getPreferredSize().height));
+        JScrollPane listScrollPane = new JScrollPane(tagList);
+        JPanel topListPanel = new JPanel();
+        topListPanel.setLayout(new BoxLayout(topListPanel,BoxLayout.Y_AXIS));
+        JLabel tagLabel = new JLabel("Tests");
+        Font defaultFont = tagLabel.getFont();
+        //make the word 'tests' nice and big.
+        tagLabel.setFont(new Font(defaultFont.getName(),Font.BOLD,(int)(defaultFont.getSize()*1.5)));
+        tagLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
+        topListPanel.add(tagLabel);
+
+        topListPanel.add(Box.createRigidArea(new Dimension(0,10)));
+
+        topListPanel.add(listScrollPane);
+        if (forceIncluded.size() > 0){
+            //let them know why some are in bold
+            JLabel forceLabel = new JLabel("(forced-in markers shown in bold)");
+            forceLabel.setAlignmentX(Component.CENTER_ALIGNMENT);
+            topListPanel.add(forceLabel);
+        }
+
+        DefaultListModel taggedListModel = new DefaultListModel();
+        taggedList = new JList(taggedListModel);
+        taggedList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        tagList.setSelectedIndex(0);
+        tagList.addListSelectionListener(this);
+        JScrollPane taggedListScrollPane = new JScrollPane(taggedList);
+        JPanel bottomListPanel = new JPanel();
+        bottomListPanel.setLayout(new BoxLayout(bottomListPanel, BoxLayout.Y_AXIS));
+        //bottomListPanel.add(new JLabel("Alleles captured by Current Selection"));
+        JLabel capturesLabel = new JLabel("Alleles captured by Current Selection");
+        capturesLabel.setFont(new Font(defaultFont.getName(),Font.BOLD,(int)(defaultFont.getSize()*1.5)));
+        bottomListPanel.add(capturesLabel);
+        bottomListPanel.add(taggedListScrollPane);
+
+        JPanel listsPanel = new JPanel();
+        listsPanel.setLayout(new BoxLayout(listsPanel,BoxLayout.Y_AXIS));
+        listsPanel.add(topListPanel);
+        listsPanel.add(Box.createRigidArea(new Dimension(0,10)));
+        listsPanel.add(bottomListPanel);
+
+        listsPanel.add(Box.createRigidArea(new Dimension(0,10)));
+
+
+        listsPanel.add(new JLabel(t.getNumTagSNPs() + " SNPs in " + t.getResults().size() +  " tests captured " + t.getTaggedSoFar() + " of " + t.getNumToCapture() +
+                " (" + t.getPercentCaptured() + "%)" + " alleles at r\u00b2 >= " + Util.roundDouble(Options.getTaggerRsqCutoff(),3)));
+        listsPanel.add(new JLabel("Mean max r\u00b2 is " + Util.roundDouble(t.getMeanRSq(),3)));
+
+        if(t.getUntaggableCount() > 0) {
+            String cantTag = "Unable to capture " + t.getUntaggableCount() + " alleles (shown in red).";
+            JLabel cantTagLabel = new JLabel(cantTag);
+            listsPanel.add(cantTagLabel);
+        }
+
+        listsPanel.add(Box.createRigidArea(new Dimension(0,10)));
+        JButton dumpTestsButton = new JButton("Dump Tests File");
+        dumpTestsButton.setActionCommand("dump");
+        dumpTestsButton.addActionListener(this);
+        JButton dumpTagsButton = new JButton("Dump Tags File");
+        dumpTagsButton.setActionCommand("dump tags");
+        dumpTagsButton.addActionListener(this);
+        JPanel listsButtonPanel = new JPanel();
+        listsButtonPanel.add(dumpTestsButton);
+        listsButtonPanel.add(dumpTagsButton);
+        listsButtonPanel.setMaximumSize(listsButtonPanel.getPreferredSize());
+        listsButtonPanel.setAlignmentX(LEFT_ALIGNMENT);
+        listsPanel.add(listsButtonPanel);
+        listsPanel.add(Box.createRigidArea(new Dimension(0,5)));
+
+        add(listsPanel);
+        add(Box.createRigidArea(new Dimension(5,0)));
+        add(new JSeparator(JSeparator.VERTICAL));
+        add(Box.createRigidArea(new Dimension(5,0)));
+        add(tableScroller);
+        refresh();
+    }
+
+    public void valueChanged(ListSelectionEvent e) {
+        if(!e.getValueIsAdjusting()) {
+            refresh();
+        }
+    }
+
+    private void refresh() {
+        if (tags.size() > 0){
+            ((DefaultListModel)taggedList.getModel()).removeAllElements();
+            int[] selected = tagList.getSelectedIndices();
+            for(int i=0;i<selected.length;i++) {
+                TagSequence ts = (TagSequence) tags.get(selected[i]);
+                Vector tagged = ts.getBestTagged();
+                for(int j=0;j<tagged.size();j++) {
+                    ((DefaultListModel)taggedList.getModel()).addElement(((edu.mit.wi.tagger.SNP)tagged.get(j)).getName());
+                }
+
+            }
+        }
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        if (e.getActionCommand().equals("taggingdone")){
+            TaggerConfigPanel tcp = (TaggerConfigPanel) e.getSource();
+            setTags(tcp.getTaggerController());
+        }else if (e.getActionCommand().equals("dump")){
+            try{
+                HaploView.fc.setSelectedFile(new File(""));
+                if (HaploView.fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION){
+                    File outfile = HaploView.fc.getSelectedFile();
+                    tc.dumpTests(outfile);
+                }
+            }catch (IOException ioe){
+                JOptionPane.showMessageDialog(this,
+                        ioe.getMessage(),
+                        "Error",
+                        JOptionPane.ERROR_MESSAGE);
+            }
+        }else if (e.getActionCommand().equals("dump tags")){
+            try{
+                HaploView.fc.setSelectedFile(new File(""));
+                if (HaploView.fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION){
+                    File outfile = HaploView.fc.getSelectedFile();
+                    tc.dumpTags(outfile);
+                }
+            }catch (IOException ioe){
+                JOptionPane.showMessageDialog(this,
+                        ioe.getMessage(),
+                        "Error",
+                        JOptionPane.ERROR_MESSAGE);
+            }
+        }
+    }
+
+    class GreyedOutRenderer extends DefaultTableCellRenderer {
+        static final long serialVersionUID = -6999190778658568864L;
+        public Component getTableCellRendererComponent
+                (JTable table, Object value, boolean isSelected,
+                 boolean hasFocus, int row, int column)
+        {
+            Component cell = super.getTableCellRendererComponent
+                    (table, value, isSelected, hasFocus, row, column);
+
+            if (column == 0 && table.getValueAt(row,1).equals("")){
+                cell.setForeground(Color.lightGray);
+            }else if ((column == 0 || column == 1) && table.getValueAt(row,1).equals("Untaggable")){
+                cell.setForeground(Color.red);
+            }else if (!isSelected){
+                cell.setForeground(Color.black);
+            }
+
+            return cell;
+        }
+    }
+
+    class TagListRenderer extends DefaultListCellRenderer{
+        static final long serialVersionUID = 7725144055202648676L;
+        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus){
+            Component cell = super.getListCellRendererComponent(list,value,index,isSelected,cellHasFocus);
+
+            Font defaultFont = list.getFont();
+            if (forceIncluded.contains(value)){
+                cell.setFont(new Font(defaultFont.getName(),Font.BOLD,defaultFont.getSize()));
+            }else{
+                cell.setFont(defaultFont);
+            }
+
+            return cell;
+        }
+    }
+}
diff --git a/edu/mit/wi/pedfile/CheckData.java b/edu/mit/wi/pedfile/CheckData.java
new file mode 100755
index 0000000..6995260
--- /dev/null
+++ b/edu/mit/wi/pedfile/CheckData.java
@@ -0,0 +1,617 @@
+
+/*
+* $Id: CheckData.java,v 3.25 2008/03/23 00:49:38 jcwhitworth Exp $
+* WHITEHEAD INSTITUTE
+* SOFTWARE COPYRIGHT NOTICE AGREEMENT
+* This software and its documentation are copyright 2003 by the
+* Whitehead Institute for Biomedical Research.  All rights are reserved.
+*
+* This software is supplied without any warranty or guaranteed support
+* whatsoever.  The Whitehead Institute can not be responsible for its
+* use, misuse, or functionality.
+*/
+
+package edu.mit.wi.pedfile;
+
+import edu.mit.wi.haploview.Chromosome;
+
+import java.util.*;
+
+/**
+ * <p>Title: CheckData.java </p> <p>Description: Used to check
+ * pedigree file and return information about observed heterozyosity,
+ * predicted heterozygosity, Hardy-Weinberg test p-value, genotyped
+ * percent, number of families with a fully genotyped trio and number
+ * of Mendelian inheritance errors.</p>
+ */
+
+public class CheckData {
+    private PedFile pedFile;
+
+    static public double hwCut = 0.001;
+    static public int failedGenoCut = 75;
+    static public int numMendErrCut = 1;
+    static public double mafCut = 0.001;
+    //These are unchanging defaults used for the reset button.
+    static public double defaultHwCut = 0.001;
+    static public int defaultFailedGenoCut = 75;
+    static public int defaultNumMendErrCut = 1;
+    static public double defaultMafCut = 0.001;
+
+
+    public CheckData(PedFile pedFile) {
+        this.pedFile = pedFile;
+    }
+
+    /**
+     * checks the pedigree file
+     * @return Vector includes information about observed heterozyosity,
+     * predicted heterozygosity, Hardy-Weinberg test p-value, genotyped percent,
+     * number of families with a fully genotyped trio and number of Mendelian inheritance errors.
+     */
+    public Vector check() throws PedFileException{
+        int numOfMarkers = pedFile.getNumMarkers();
+        Vector results = new Vector(numOfMarkers);
+        //_size = pedFile.getNumIndividuals();
+
+        for(int i= 0; i < numOfMarkers; i++){
+            results.add(checkMarker(i));
+        }
+        return results;
+    }
+
+    private MarkerResult checkMarker(int loc)throws PedFileException{
+        MarkerResult result = new MarkerResult();
+        Individual currentInd;
+        int missing=0, founderHetCount=0, mendErrNum=0;
+        int allele1 = 0, allele2 = 0, called = 0;
+        Hashtable founderGenoCount = new Hashtable();
+        Hashtable kidgeno = new Hashtable();
+        int[] founderHomCount = new int[5];
+        Vector mendels = new Vector();
+
+        int femaleCount = 0;
+
+        int[] count = new int[6];
+        for(int i=0;i<5;i++) {
+            founderHomCount[i] =0;
+            count[i]=0;
+        }
+        count[5] = 0;
+
+        //loop through each family, check data for marker loc
+        Enumeration famList = pedFile.getFamList();
+        while(famList.hasMoreElements()){
+            Family currentFamily = pedFile.getFamily((String)famList.nextElement());
+            Enumeration indList = currentFamily.getMemberList();
+            //loop through each individual in the current Family
+            while(indList.hasMoreElements()){
+                currentInd = currentFamily.getMember((String)indList.nextElement());
+                allele1 = currentInd.getAllele(loc,0);
+                allele2 = currentInd.getAllele(loc,1);
+
+                //if haploid, check for male hets
+                if(Chromosome.getDataChrom().equalsIgnoreCase("chrx") && currentInd.getGender()==1){
+                    if(allele1 != allele2) {
+                        currentInd.zeroOutMarker(loc);
+                        pedFile.addHaploidHet(currentInd.getFamilyID() + "\t" + currentInd.getIndividualID() + "\t" + loc);
+                    }
+                }
+
+                //no allele data missing
+                if(allele1 > 0 && allele2 >0){
+                    //make sure entry has parents
+                    if (currentFamily.containsMember(currentInd.getMomID()) &&
+                            currentFamily.containsMember(currentInd.getDadID())){
+                        //do mendel check
+                        int momAllele1 = (currentFamily.getMember(currentInd.getMomID())).getAllele(loc,0);
+                        int momAllele2 = (currentFamily.getMember(currentInd.getMomID())).getAllele(loc,1);
+                        int dadAllele1 = (currentFamily.getMember(currentInd.getDadID())).getAllele(loc,0);
+                        int dadAllele2 = (currentFamily.getMember(currentInd.getDadID())).getAllele(loc,1);
+
+
+                        if(Chromosome.getDataChrom().equalsIgnoreCase("chrx")){
+                            if (dadAllele1 != dadAllele2){
+                                dadAllele1 = 0;
+                                dadAllele2 = 0;
+                            }
+                            if (!(momAllele1 == 0 || momAllele2 == 0 || dadAllele1 == 0 || dadAllele2 ==0)){
+                                if(currentInd.getGender() == 1) {
+                                    //this is an x chrom for a male, so the only thing we need to check is if
+                                    //allele1 matches either momallele1 or momallele2
+                                    if(allele1 != momAllele1 && allele1 != momAllele2) {
+                                        mendErrNum ++;
+                                        MendelError mend = new MendelError(currentInd.getFamilyID(),
+                                                currentInd.getIndividualID());
+                                        mendels.add(mend);
+                                        currentInd.zeroOutMarker(loc);
+                                        currentFamily.getMember(currentInd.getMomID()).zeroOutMarker(loc);
+                                        currentFamily.getMember(currentInd.getDadID()).zeroOutMarker(loc);
+                                    }
+                                }else {
+                                    //if gender is anything except 1 we assume female
+                                    if(momAllele1 == momAllele2) {
+                                        //mom hom and dad matches mom
+                                        if(dadAllele1 == momAllele1) {
+                                            //kid must be hom same allele
+                                            if(allele1 != momAllele1 || allele2 != momAllele2){
+                                                mendErrNum ++;
+                                                MendelError mend = new MendelError(currentInd.getFamilyID(),
+                                                        currentInd.getIndividualID());
+                                                mendels.add(mend);
+                                                currentInd.zeroOutMarker(loc);
+                                                currentFamily.getMember(currentInd.getMomID()).zeroOutMarker(loc);
+                                                currentFamily.getMember(currentInd.getDadID()).zeroOutMarker(loc);
+                                            }
+                                        }else {
+                                            //kid must be het
+                                            if(allele1 == allele2 ){
+                                                mendErrNum ++;
+                                                MendelError mend = new MendelError(currentInd.getFamilyID(),
+                                                        currentInd.getIndividualID());
+                                                mendels.add(mend);
+                                                currentInd.zeroOutMarker(loc);
+                                                currentFamily.getMember(currentInd.getMomID()).zeroOutMarker(loc);
+                                                currentFamily.getMember(currentInd.getDadID()).zeroOutMarker(loc);
+                                            }
+                                        }
+                                    }else{
+                                        //mom het,so only need to check that at least one allele matches dad
+                                        if(allele1 != dadAllele1 && allele2 != dadAllele1){
+                                            mendErrNum ++;
+                                            MendelError mend = new MendelError(currentInd.getFamilyID(),
+                                                    currentInd.getIndividualID());
+                                            mendels.add(mend);
+                                            currentInd.zeroOutMarker(loc);
+                                            currentFamily.getMember(currentInd.getMomID()).zeroOutMarker(loc);
+                                            currentFamily.getMember(currentInd.getDadID()).zeroOutMarker(loc);
+                                        }
+                                    }
+                                }
+                            }
+                        }else{
+
+                            //don't check if parents are missing any data
+                            if (!(momAllele1 == 0 || momAllele2 == 0 || dadAllele1 == 0 || dadAllele2 ==0)){
+                                //mom hom
+                                if(momAllele1 == momAllele2){
+                                    //both parents hom
+                                    if (dadAllele1 == dadAllele2){
+                                        //both parents hom same allele
+                                        if (momAllele1 == dadAllele1){
+                                            //kid must be hom same allele
+                                            if (allele1 != momAllele1 || allele2 != momAllele1) {
+                                                mendErrNum ++;
+                                                MendelError mend = new MendelError(currentInd.getFamilyID(),
+                                                        currentInd.getIndividualID());
+                                                mendels.add(mend);
+                                                currentInd.zeroOutMarker(loc);
+                                                currentFamily.getMember(currentInd.getMomID()).zeroOutMarker(loc);
+                                                currentFamily.getMember(currentInd.getDadID()).zeroOutMarker(loc);
+                                            }
+                                            //parents hom diff allele
+                                        }else{
+                                            //kid must be het
+                                            if (allele1 == allele2) {
+                                                mendErrNum++;
+                                                MendelError mend = new MendelError(currentInd.getFamilyID(),
+                                                        currentInd.getIndividualID());
+                                                mendels.add(mend);
+                                                currentInd.zeroOutMarker(loc);
+                                                currentFamily.getMember(currentInd.getMomID()).zeroOutMarker(loc);
+                                                currentFamily.getMember(currentInd.getDadID()).zeroOutMarker(loc);
+                                            }
+                                        }
+                                        //mom hom dad het
+                                    }else{
+                                        //kid can't be hom for non-momallele
+                                        if (allele1 != momAllele1 && allele2 != momAllele1){
+                                            mendErrNum++;
+                                            MendelError mend = new MendelError(currentInd.getFamilyID(),
+                                                    currentInd.getIndividualID());
+                                            mendels.add(mend);
+                                            currentInd.zeroOutMarker(loc);
+                                            currentFamily.getMember(currentInd.getMomID()).zeroOutMarker(loc);
+                                            currentFamily.getMember(currentInd.getDadID()).zeroOutMarker(loc);
+                                        }
+                                    }
+                                    //mom het
+                                }else{
+                                    //dad hom
+                                    if (dadAllele1 == dadAllele2){
+                                        //kid can't be hom for non-dadallele
+                                        if(allele1 != dadAllele1 && allele2 != dadAllele1){
+                                            mendErrNum++;
+                                            MendelError mend = new MendelError(currentInd.getFamilyID(),
+                                                    currentInd.getIndividualID());
+                                            mendels.add(mend);
+                                            currentInd.zeroOutMarker(loc);
+                                            currentFamily.getMember(currentInd.getMomID()).zeroOutMarker(loc);
+                                            currentFamily.getMember(currentInd.getDadID()).zeroOutMarker(loc);
+                                        }
+                                    }
+                                    //both parents het no mend err poss
+                                }
+                            }
+                        }
+                    }
+                    //end mendel check
+                }
+            }
+
+            indList = currentFamily.getMemberList();
+            //loop through each individual in the current Family
+            while(indList.hasMoreElements()){
+                currentInd = currentFamily.getMember((String)indList.nextElement());
+                if (currentInd.getZeroed(loc)){
+                    allele1 = 0;
+                    allele2 = 0;
+                }else{
+                    allele1 = currentInd.getAllele(loc,0);
+                    allele2 = currentInd.getAllele(loc,1);
+                }
+
+                String familyID = currentInd.getFamilyID();
+
+                //no allele data missing
+                if(allele1 > 0 && allele2 >0){
+                    //indiv has no parents -- i.e. is a founder
+                    if(!currentFamily.hasAncestor(currentInd.getIndividualID())){
+                        //set founderGenoCount
+                        if(founderGenoCount.containsKey(familyID)){
+                            int value = ((Integer)founderGenoCount.get(familyID)).intValue() +1;
+                            founderGenoCount.put(familyID, new Integer(value));
+                        }else{
+                            founderGenoCount.put(familyID, new Integer(1));
+                        }
+
+                        if (allele1 != 9){  //value of 9 means an 'h' allele for haps files...
+                            count[allele1]++;
+                        }else{
+                            count[5]++;
+                        }
+                        if (!Chromosome.getDataChrom().equalsIgnoreCase("chrx") || currentInd.getGender() != 1) {
+                            if(allele1 != allele2 || allele1 == 9 || allele2 == 9) {
+                                founderHetCount++;
+                            }else{
+                                founderHomCount[allele1]++;
+                            }
+                            if(allele2 != 9){
+                                count[allele2]++;
+                            }else{
+                                count[5]++;
+                            }
+                            femaleCount++;
+                        }
+                    }else{
+                        if(kidgeno.containsKey(familyID)){
+                            int value = ((Integer)kidgeno.get(familyID)).intValue() +1;
+                            kidgeno.put(familyID, new Integer(value));
+                        }
+                        else{
+                            kidgeno.put(familyID, new Integer(1));
+                        }
+                    }
+
+                    called++;
+                }
+                //missing data
+                else missing++;
+
+            }
+            currentFamily.setMendErrs(mendErrNum);
+        }
+        int founderHomTotal = 0;
+        for (int i = 0; i < founderHomCount.length; i++){
+            founderHomTotal += founderHomCount[i];
+        }
+        double obsHET = getObsHET(founderHetCount, founderHomTotal);
+        double freqStuff[] = null;
+        int numHets = count[5];
+        count[5] = 0;
+        if (numHets > 0){
+            int numAlleles = 0;
+            for (int i = 1; i < count.length-1; i++){
+                if (count[i] > 0){
+                    numAlleles++;
+                }
+            }
+
+            if (numAlleles == 0){
+                count[1] += numHets/2;
+                count[3] += numHets/2;
+            }else if (numAlleles ==  1){
+                for (int i = 1; i < count.length-1; i++){
+                    if (count[i] > 0){
+                        count[i] += numHets/2;
+                        if (i == 4){
+                            count[3] += numHets/2;
+                        }else{
+                            count[i+1] += numHets/2;
+                        }
+                        break;
+                    }
+                }
+            }else if (numAlleles == 2){
+                for (int i = 1; i < count.length -1; i++){
+                    if (count[i] > 0){
+                        count[i] += numHets/2;
+                    }
+                }
+            }
+        }
+        try{
+            freqStuff = getFreqStuff(count);
+        }catch (PedFileException pfe){
+            throw new PedFileException("More than two alleles at marker " + (loc+1));
+        }
+        double preHET = freqStuff[0];
+        double maf = freqStuff[1];
+        String minorAllele, majorAllele;
+        if (freqStuff[2] == 1){
+            minorAllele = "A";
+        }else if (freqStuff[2] == 2){
+            minorAllele = "C";
+        }else if (freqStuff[2] == 3){
+            minorAllele = "G";
+        }else{
+            minorAllele = "T";
+        }
+
+        if (freqStuff[3] == 1){
+            majorAllele = "A";
+        }else if (freqStuff[3] == 2){
+            majorAllele = "C";
+        }else if (freqStuff[3] == 3){
+            majorAllele = "G";
+        }else{
+            majorAllele = "T";
+        }
+
+        //HW p value
+        double pvalue = getPValue(founderHomCount, founderHetCount);
+
+        //This will cause the values to show up as NA since there aren't enough females to calculate
+        if(femaleCount < 10 && Chromosome.getDataChrom().equalsIgnoreCase("chrx")){
+            obsHET = Double.MAX_VALUE;
+            preHET = Double.MAX_VALUE;
+            pvalue = Double.MAX_VALUE;
+        }
+
+        //geno percent
+        double genopct;
+        if (called == 0){
+            genopct = 0;
+        }else{
+            genopct = 100.0*((double)called/(called+missing));
+        }
+        //System.out.println(genopct);
+
+        // num of families with a fully genotyped trio
+        //int famTrio =0;
+        int famTrio = getNumOfFamTrio(pedFile.getFamList(), founderGenoCount, kidgeno);
+
+        //rating
+        int rating = this.getRating(genopct, pvalue, mendErrNum,maf);
+
+        if (mendErrNum > 0 && !pedFile.getMendelsExist()){
+            pedFile.setMendelsExist(true);
+        }
+
+        result.setObsHet(obsHET);
+        result.setPredHet(preHET);
+        result.setMAF(maf);
+        result.setMinorAllele(minorAllele);
+        result.setMajorAllele(majorAllele);
+        result.setHWpvalue(pvalue);
+        result.setGenoPercent(genopct);
+        result.setFamTrioNum(famTrio);
+        result.setMendErrNum(mendErrNum);
+        result.setRating(rating);
+        result.setMendelErrors(mendels);
+        return result;
+    }
+
+    /**
+     * Gets observed heterozygosity
+     */
+    private double getObsHET(int het, int hom){
+        double obsHET;
+        if (het+hom == 0){
+            obsHET = 0;
+        }else{
+            obsHET = het/(het+hom+0.0);
+        }
+        return obsHET;
+    }
+
+    private double[] getFreqStuff(int[] count) throws PedFileException{
+        double[] freqStuff = new double[4];
+        int sumsq=0, sum=0, num=0, mincount = -1;
+        int numberOfAlleles = 0;
+        for(int i=0;i<count.length;i++){
+            if(count[i] != 0){
+                numberOfAlleles++;
+                num = count[i];
+                sumsq += num*num;
+                sum += num;
+
+                if (num > mincount){
+                    freqStuff[3] = i;
+                }
+
+                if (mincount < 0 || mincount > num){
+                    mincount = num;
+                    freqStuff[2] = i;
+                }
+            }
+        }
+
+        if (numberOfAlleles > 2){
+            throw new PedFileException("More than two alleles!");
+        }
+
+        if (sum == 0){
+            freqStuff[0] = 0;
+            freqStuff[1] = 0;
+        }else{
+            freqStuff[0] = 1.0 - (sumsq/((sum*sum)+0.0));
+            if (mincount/(sum+0.0) == 1){
+                freqStuff[1] = 0.0;
+            }else{
+                freqStuff[1] = mincount/(sum+0.0);
+            }
+        }
+        return freqStuff;
+    }
+
+    private double getPValue(int[] parentHom, int parentHet) throws PedFileException{
+        //ie: 11 13 31 33 -> homA =1 homB = 1 parentHet=2
+        int homA=0, homB=0;
+        double pvalue=0;
+        for(int i=0;i<parentHom.length;i++){
+            if(parentHom[i] !=0){
+                if(homA>0) homB = parentHom[i];
+                else homA = parentHom[i];
+            }
+        }
+        //caculate p value from homA, parentHet and homB
+        if (homA + parentHet + homB <= 0){
+            pvalue=0;
+        }else{
+            pvalue = hwCalculate(homA, parentHet, homB);
+        }
+        return pvalue;
+    }
+
+    private double hwCalculate(int obsAA, int obsAB, int obsBB) throws PedFileException{
+        //Calculates exact two-sided hardy-weinberg p-value. Parameters
+        //are number of genotypes, number of rare alleles observed and
+        //number of heterozygotes observed.
+        //
+        // (c) 2003 Jan Wigginton, Goncalo Abecasis
+        int diplotypes =  obsAA + obsAB + obsBB;
+        int rare = (obsAA*2) + obsAB;
+        int hets = obsAB;
+
+
+        //make sure "rare" allele is really the rare allele
+        if (rare > diplotypes){
+            rare = 2*diplotypes-rare;
+        }
+
+        //make sure numbers aren't screwy
+        if (hets > rare){
+            throw new PedFileException("HW test: " + hets + "heterozygotes but only " + rare + "rare alleles.");
+        }
+        double[] tailProbs = new double[rare+1];
+        for (int z = 0; z < tailProbs.length; z++){
+            tailProbs[z] = 0;
+        }
+
+        //start at midpoint
+        //all the casting is to make sure we don't overflow ints if there are 10's of 1000's of inds
+        int mid = (int)((double)rare * (double)(2 * diplotypes - rare) / (double)(2 * diplotypes));
+
+        //check to ensure that midpoint and rare alleles have same parity
+        if (((rare & 1) ^ (mid & 1)) != 0){
+            mid++;
+        }
+        int het = mid;
+        int hom_r = (rare - mid) / 2;
+        int hom_c = diplotypes - het - hom_r;
+
+        //Calculate probability for each possible observed heterozygote
+        //count up to a scaling constant, to avoid underflow and overflow
+        tailProbs[mid] = 1.0;
+        double sum = tailProbs[mid];
+        for (het = mid; het > 1; het -=2){
+            tailProbs[het-2] = (tailProbs[het] * het * (het-1.0))/(4.0*(hom_r + 1.0) * (hom_c + 1.0));
+            sum += tailProbs[het-2];
+            //2 fewer hets for next iteration -> add one rare and one common homozygote
+            hom_r++;
+            hom_c++;
+        }
+
+        het = mid;
+        hom_r = (rare - mid) / 2;
+        hom_c = diplotypes - het - hom_r;
+        for (het = mid; het <= rare - 2; het += 2){
+            tailProbs[het+2] = (tailProbs[het] * 4.0 * hom_r * hom_c) / ((het+2.0)*(het+1.0));
+            sum += tailProbs[het+2];
+            //2 more hets for next iteration -> subtract one rare and one common homozygote
+            hom_r--;
+            hom_c--;
+        }
+
+        for (int z = 0; z < tailProbs.length; z++){
+            tailProbs[z] /= sum;
+        }
+
+        double top = tailProbs[hets];
+        for (int i = hets+1; i <= rare; i++){
+            top += tailProbs[i];
+        }
+        double otherSide = tailProbs[hets];
+        for (int i = hets-1; i >= 0; i--){
+            otherSide += tailProbs[i];
+        }
+
+        if (top > 0.5 && otherSide > 0.5){
+            return 1.0;
+        }else{
+            if (top < otherSide){
+                return top * 2;
+            }else{
+                return otherSide * 2;
+            }
+        }
+    }
+
+
+
+    private int getNumOfFamTrio(Enumeration famList, Hashtable parentgeno, Hashtable kidgeno){
+        //this is buggy. it doesn't do what we want with larger families.
+        //it's hard to even define what we want this to represent. oh well.
+        int tdtfams =0;
+        while(famList.hasMoreElements()){
+            int parentGeno=0, kidsGeno =0;
+            String key = (String)famList.nextElement();
+            Integer pGeno = (Integer)parentgeno.get(key);
+            Integer kGeno = (Integer)kidgeno.get(key);
+            if(pGeno != null) parentGeno = pGeno.intValue();
+            if(kGeno != null) kidsGeno = kGeno.intValue();
+            //basically we want the smaller of either (a) half the number of genotyped parents
+            //or (b) the number of genotyped offspring.
+            if(parentGeno>=2 && kidsGeno>=1){
+                if (parentGeno/2 > kidsGeno){
+                    tdtfams += kidsGeno;
+                }else{
+                    tdtfams += parentGeno/2;
+                }
+            }
+        }
+        return tdtfams;
+    }
+
+    private int getRating(double genopct, double pval, int menderr, double maf){
+        int rating = 0;
+        if (genopct < failedGenoCut){
+            rating -= 2;
+        }
+        if (pval < hwCut){
+            rating -= 4;
+        }
+        if (menderr > numMendErrCut){
+            rating -= 8;
+        }
+        if (maf < mafCut){
+            rating -= 16;
+        }
+        if (rating == 0){
+            rating = 1;
+        }
+        return rating;
+    }
+}
+
diff --git a/edu/mit/wi/pedfile/CheckDataException.java b/edu/mit/wi/pedfile/CheckDataException.java
new file mode 100755
index 0000000..c9c4387
--- /dev/null
+++ b/edu/mit/wi/pedfile/CheckDataException.java
@@ -0,0 +1,33 @@
+/*
+ * $Id: CheckDataException.java,v 3.1 2008/02/11 19:21:35 htl10 Exp $
+ * WHITEHEAD INSTITUTE
+ * SOFTWARE COPYRIGHT NOTICE AGREEMENT
+ * This software and its documentation are copyright 2002 by the
+ * Whitehead Institute for Biomedical Research.  All rights are reserved.
+ *
+ * This software is supplied without any warranty or guaranteed support
+ * whatsoever.  The Whitehead Institute can not be responsible for its
+ * use, misuse, or functionality.
+ */
+
+package edu.mit.wi.pedfile;
+
+/**
+ * <p>Title: CheckDataException.java </p>
+ * <p>Description: </p>
+ * @author Hui Gong
+ * @version $Revision 1.1 $
+ */
+
+public class CheckDataException extends Exception{
+    static final long serialVersionUID = 5362642498353473882L;
+
+	public CheckDataException() {
+		super();
+	}
+
+	public CheckDataException(String s){
+		super(s);
+	}
+
+}
diff --git a/edu/mit/wi/pedfile/Family.java b/edu/mit/wi/pedfile/Family.java
new file mode 100755
index 0000000..b7242d1
--- /dev/null
+++ b/edu/mit/wi/pedfile/Family.java
@@ -0,0 +1,146 @@
+/*
+* $Id: Family.java,v 3.1 2006/04/10 18:29:51 djbender Exp $
+* WHITEHEAD INSTITUTE
+* SOFTWARE COPYRIGHT NOTICE AGREEMENT
+* This software and its documentation are copyright 2002 by the
+* Whitehead Institute for Biomedical Research.  All rights are reserved.
+*
+* This software is supplied without any warranty or guaranteed support
+* whatsoever.  The Whitehead Institute can not be responsible for its
+* use, misuse, or functionality.
+*/
+package edu.mit.wi.pedfile;
+
+import java.util.Hashtable;
+import java.util.Enumeration;
+
+/**
+ * stores the familyName and the members of a family from a pedigree file
+ *
+ * this class is not thread safe (untested)
+ *
+ * @author Julian Maller
+ */
+
+public class Family {
+    private Hashtable members;
+    private String familyName;
+    private int mendErrors;
+
+    public Family(){
+        this.members = new Hashtable();
+    }
+
+    public Family(String familyName){
+        this.members = new Hashtable();
+        this.familyName = familyName;
+    }
+
+	/**
+	 * returns the name of this family (familyName)
+	 * @return family name
+	 */
+    public String getFamilyName() {
+        return familyName;
+    }
+
+    public boolean hasAncestor(String id){
+        Individual ind = (Individual) members.get(id);
+        if (ind != null){
+            if (ind.getDadID().equals("0") && ind.getMomID().equals("0")){
+                return false;
+            }else{
+                if (members.containsKey(ind.getDadID()) || members.containsKey(ind.getMomID())){
+                    return true;
+                }else{
+                    return (hasAncestor(ind.getDadID()) || hasAncestor(ind.getMomID()));
+                }
+            }
+        }else{
+            return false;
+        }
+    }
+
+	/**
+	 * sets the family name (familyName)
+	 * @param familyName
+	 */
+    public void setFamilyName(String familyName) {
+        this.familyName = familyName;
+    }
+
+	/**
+	 * returns the members Hashtable (containing individuals)
+	 * @return members Hashtable
+	 */
+    public Hashtable getMembers() {
+        return members;
+    }
+   /**
+    * sets the members hashtable
+    * @param members
+    */
+    public void setMembers(Hashtable members) {
+        this.members = members;
+    }
+
+    public int getMendErrs() {
+        return mendErrors;
+    }
+
+    public void setMendErrs(int mendErrors) {
+        this.mendErrors = mendErrors;
+    }
+
+    /**
+     * returns the number of members of this family
+     * @return number of members in this family
+     */
+
+    public int getNumMembers(){
+        return this.members.size();
+    }
+
+	/**
+	 * returns a list of individualIDs that are members of this family in the form of an enumeration
+	 * @return enumeration memberlist
+	 */
+    public Enumeration getMemberList(){
+        return this.members.keys();
+    }
+
+    /**
+     * adds a member to this family (adds to members Vector)
+     * @param ind Individual to add to members Vector
+     */
+    public void addMember(Individual ind){
+        this.members.put(ind.getIndividualID(),ind);
+    }
+
+    public void removeMember(String id){
+        members.remove(id);
+    }
+
+    public boolean containsMember(String id){
+        if (members.containsKey(id)){
+            return true;
+        }else{
+            return false;
+        }
+    }
+
+    /**
+     * get the Individual with individualID
+     * @param individualID the individualID of the individual we want
+     * @return the individual with matching individualID
+     */
+    public Individual getMember(String individualID) throws PedFileException{
+        if (!(this.members.containsKey(individualID))){
+            throw new PedFileException("Individual " + individualID +
+                    " in family " + familyName + " is referenced, but appears to be missing.");
+        }
+        return (Individual)this.members.get(individualID);
+    }
+
+
+}
diff --git a/edu/mit/wi/pedfile/Individual.java b/edu/mit/wi/pedfile/Individual.java
new file mode 100755
index 0000000..4c175d7
--- /dev/null
+++ b/edu/mit/wi/pedfile/Individual.java
@@ -0,0 +1,233 @@
+/*
+* $Id: Individual.java,v 3.5 2008/02/11 19:21:35 htl10 Exp $
+* WHITEHEAD INSTITUTE
+* SOFTWARE COPYRIGHT NOTICE AGREEMENT
+* This software and its documentation are copyright 2002 by the
+* Whitehead Institute for Biomedical Research.  All rights are reserved.
+*
+* This software is supplied without any warranty or guaranteed support
+* whatsoever.  The Whitehead Institute can not be responsible for its
+* use, misuse, or functionality.
+*/
+
+package edu.mit.wi.pedfile;
+
+import java.util.Vector;
+
+/**
+ * stores information about an individual from a pedigree file
+ *
+ * this class is not thread safe (untested)
+ *
+ * @author Julian Maller
+ */
+public class Individual {
+    private String familyID;
+    private String individualID;
+    private String momID;
+    private String dadID;
+    private int gender;
+    private int affectedStatus;
+
+    private String reasonImAxed;
+    private int liability; //optional
+    //private Vector markers;
+    //private byte[] alleles1;
+    //private byte[] alleles2;
+    private byte[][] alleles;
+    private double numGoodMarkers;
+    private boolean[] zeroed;
+    //private boolean phased = false;
+    //this is used to keep track of the index of the last marker added
+    private int currMarker;
+
+    public final static int FEMALE = 2;
+    public final static int MALE = 1;
+    public final static int AFFACTED = 2;
+    public final static int UNAFFACTED = 1;
+    public final static String DATA_MISSING ="0";
+
+
+
+    public Individual(int numMarkers, boolean isPhased) {
+        alleles = new byte[2][numMarkers];
+        this.zeroed = new boolean[numMarkers];
+        this.currMarker = 0;
+        //this.phased = isPhased;
+    }
+
+    /**
+     * gets the family ID
+     * @return The familyID for this individual
+     */
+    public String getFamilyID() {
+        return familyID;
+    }
+    /**
+     * sets the family ID
+     * @param familyID
+     */
+    public void setFamilyID(String familyID) {
+        this.familyID = familyID;
+    }
+    /**
+     * gets the Individual ID
+     * @return The individualID for this individual
+     */
+    public String getIndividualID() {
+        return individualID;
+    }
+    /**
+     * sets the individual ID
+     * @param individualID
+     */
+    public void setIndividualID(String individualID) {
+        this.individualID = individualID;
+    }
+    /**
+     * gets the momID for this individual
+     * @return momID
+     */
+    public String getMomID() {
+        return momID;
+    }
+    /**
+     * sets the momid
+     * @param momID
+     */
+    public void setMomID(String momID) {
+        this.momID = momID;
+    }
+    /**
+     * gets the dad ID for this individual
+     * @return dadID
+     */
+    public String getDadID() {
+        return dadID;
+    }
+    /**
+     * sets the dadID
+     * @param dadID
+     */
+    public void setDadID(String dadID) {
+        this.dadID = dadID;
+    }
+    /**
+     * gets the gender for this individual
+     * @return gender
+     */
+    public int getGender() {
+        return gender;
+    }
+    /**
+     * sets the gender
+     * @param gender
+     */
+    public void setGender(int gender) {
+        this.gender = gender;
+    }
+    /**
+     * gets the affected status for this individual
+     * @return affectedStatus
+     */
+    public int getAffectedStatus() {
+        return affectedStatus;
+    }
+    /**
+     * sets the affected status
+     * @param affectedStatus
+     */
+    public void setAffectedStatus(int affectedStatus) {
+        this.affectedStatus = affectedStatus;
+    }
+    /**
+     * gets the liability class for this individual
+     * @return liability
+     */
+    public int getLiability() {
+        return liability;
+    }
+    /**
+     * sets the liability class
+     * @param liability
+     */
+    public void setLiability(int liability) {
+        this.liability = liability;
+    }
+
+    /**
+     * gets the markers vector for this individual
+     * @return Vector markers
+     */
+    public Vector getMarkers() {
+        return null;
+    }
+
+    public void setMarkers(byte[] ma, byte[] mb){
+        alleles[0] = ma;
+        alleles[1] = mb;
+    }
+
+    /**
+     * returns the number of markers for this individual
+     * @return integer count of markers
+     */
+    public int getNumMarkers(){
+        return this.alleles[0].length;
+    }
+
+    public byte getAllele(int location, int index){
+            return alleles[index][location];
+    }
+
+
+    public void addMarker(byte markera, byte markerb) {
+        alleles[0][currMarker] = markera;
+        alleles[1][currMarker] = markerb;
+        zeroed[currMarker] = false;
+        currMarker++;
+         if (!(markera == 0 || markerb == 0)){
+            numGoodMarkers++;
+        }
+    }
+
+    /**
+     * checks to see if a marker has been zeroed out
+     * @param location - which marker to check
+     * @return true if marker is zeroed, false otherwise
+     */
+    public boolean getZeroed(int location){
+        //return ((Boolean)zeroed.get(location)).booleanValue();
+        return zeroed[location];
+    }
+
+    /**
+     * sets the bit that this marker has been zeroed out for this indiv (e.g. because it has a mendel error)
+     * @param i - marker to be zeroed
+     */
+    public void zeroOutMarker(int i){
+        //this.zeroed.set(i, new Boolean(true));
+        this.zeroed[i] = true;
+    }
+
+    public String getReasonImAxed() {
+        return reasonImAxed;
+    }
+
+    public void setReasonImAxed(String reasonImAxed) {
+        this.reasonImAxed = reasonImAxed;
+    }
+
+    public double getGenoPC(){
+        return numGoodMarkers/alleles[0].length;
+    }
+
+    public boolean[] getZeroedArray() {
+        return zeroed;
+    }
+
+    public void setZeroedArray(boolean[] z) {
+        zeroed = z;
+    }
+}
+
diff --git a/edu/mit/wi/pedfile/MarkerResult.java b/edu/mit/wi/pedfile/MarkerResult.java
new file mode 100755
index 0000000..f1fb198
--- /dev/null
+++ b/edu/mit/wi/pedfile/MarkerResult.java
@@ -0,0 +1,229 @@
+/*
+ * $Id: MarkerResult.java,v 3.10 2007/03/12 19:20:15 djbender Exp $
+ * WHITEHEAD INSTITUTE
+ * SOFTWARE COPYRIGHT NOTICE AGREEMENT
+ * This software and its documentation are copyright 2003 by the
+ * Whitehead Institute for Biomedical Research.  All rights are reserved.
+ *
+ * This software is supplied without any warranty or guaranteed support
+ * whatsoever.  The Whitehead Institute can not be responsible for its
+ * use, misuse, or functionality.
+ */
+
+package edu.mit.wi.pedfile;
+
+import edu.mit.wi.haploview.Util;
+import java.text.*;
+import java.util.Locale;
+import java.util.Vector;
+
+/**
+ * <p>Title: MarkerResult.java </p>
+ * <p>Description: Gets the result for a marker
+ * Result includes observed heterozyosity, predicted heterozygosity,
+ * Hardy-Weinberg test p-value, genotyped percent, number of families with
+ * a fully genotyped trio, number of Mendelian inheritance errors and rating.</p>
+ * @author Hui Gong
+ * @version $Revision 1.2 $
+ */
+
+public class MarkerResult {
+
+	private double _obsHET;
+	private double _predHET;
+    private double _maf;
+    private String _minorAllele;
+    private String _majorAllele;
+    private double _HWpval;
+	private double _genoPercent;
+	private int _famTrioNum;
+	private int _mendErrNum;
+	private int _rating;
+    private Vector mendelErrors;
+
+    private static NumberFormat nf = NumberFormat.getInstance(Locale.US);
+    private static NumberFormat pctNF = NumberFormat.getInstance(Locale.US);
+
+    static {
+        nf.setMinimumFractionDigits(3);
+        nf.setMaximumFractionDigits(3);
+        nf.setGroupingUsed(false);
+
+        pctNF.setMinimumFractionDigits(0);
+        pctNF.setMaximumFractionDigits(1);
+    }
+
+	/**
+	 * Sets observed heterozygosity
+	 */
+	public void setObsHet(double obsHet){
+		this._obsHET = obsHet;
+	}
+
+	/**
+	 * Sets predicted heterozygosity
+	 */
+	public void setPredHet(double predHet){
+		this._predHET = predHet;
+	}
+
+	/**
+	 * Sets Hardy-Weinberg test p-value
+	 */
+	public void setHWpvalue(double pvalue){
+		this._HWpval = pvalue;
+	}
+
+	/**
+	 * Sets percent of individuals genotyped
+	 */
+	public void setGenoPercent(double genoPct){
+		this._genoPercent = genoPct;
+	}
+
+	/**
+	 * Sets # of families with a fully genotyped trio
+	 */
+	public void setFamTrioNum(int num){
+		this._famTrioNum = num;
+	}
+
+	/**
+	 * Sets # of Mendelian inheritance errors
+	 */
+	public void setMendErrNum(int num){
+		this._mendErrNum = num;
+	}
+
+    /**
+     * Sets minor allele frequency
+     * @param maf - minor allele frequency
+     */
+    public void setMAF(double maf) {
+        this._maf = maf;
+    }
+
+    /**
+     * Sets minor allele
+     */
+    public void setMinorAllele(String minallele) {
+        this._minorAllele = minallele;
+    }
+
+    /**
+     * Sets major allele
+     */
+    public void setMajorAllele(String majallele){
+        this._majorAllele = majallele;
+    }
+
+    /**
+     * Sets the data rating
+     */
+    public void setRating(int rating){
+		this._rating = rating;
+	}
+
+    public void setMendelErrors(Vector mends){
+        mendelErrors = mends;
+    }
+
+    /**
+	 * Gets observed heterozygosity
+	 */
+	public double getObsHet(){
+		return Double.parseDouble(nf.format(this._obsHET));
+	}
+
+    /**
+     * returns minor allele frequency
+     * @return  minor allele frequency
+     */
+    public double getMAF(){
+        return Double.parseDouble(nf.format(this._maf));
+    }
+
+    /**
+     * returns minor allele
+     */
+    public String getMinorAllele(){
+        return this._minorAllele;
+    }
+
+    /**
+     * returns major allele
+     */
+    public String getMajorAllele(){
+        return this._majorAllele;
+    }
+
+    /**
+     * Gets predicted heterozygosity
+	 */
+	public double getPredHet(){
+		return Double.parseDouble(nf.format(this._predHET));
+	}
+
+	/**
+	 * Gets Hardy-Weinberg test p-value
+	 */
+	public String getHWpvalue(){
+        //the old formatting was cutting off anything less than 0.001
+        return  Util.formatPValue(this._HWpval);
+	}
+
+	/**
+	 * Gets percent of individuals genotyped
+	 */
+	public double getGenoPercent(){
+        return Double.parseDouble(pctNF.format(this._genoPercent));
+	}
+
+	/**
+	 * Gets # of families with a fully genotyped trio
+	 */
+	public int getFamTrioNum(){
+		return this._famTrioNum;
+	}
+
+	/**
+	 * Gets # of Mendelian inheritance errors
+	 */
+	public int getMendErrNum(){
+		return this._mendErrNum;
+	}
+
+
+	/**
+	 * Gets the data rating
+	 */
+	public int getRating(){
+		return this._rating;
+	}
+
+    public Vector getMendelErrors(){
+        return mendelErrors;
+    }
+
+    public String toString(){
+		StringBuffer buffer = new StringBuffer();
+		NumberFormat format=NumberFormat.getInstance();
+		format.setMaximumFractionDigits(3);
+		format.setMinimumFractionDigits(3);
+
+        buffer.append(format.format(this._obsHET)).append("\t").append(format.format(this._predHET)).append("\t");
+
+		format.setMaximumFractionDigits(2);
+		format.setMinimumFractionDigits(0);
+        buffer.append(format.format(this._HWpval)).append("\t");
+
+		format.setMaximumFractionDigits(1);
+		format.setMinimumFractionDigits(1);
+        buffer.append(format.format(this._genoPercent)).append("\t");
+        buffer.append(this._famTrioNum).append("\t");
+		if(this._mendErrNum < 0) buffer.append("   \t");
+		else buffer.append(this._mendErrNum).append("\t");
+		buffer.append(this._rating);
+		return buffer.toString();
+	}
+}
diff --git a/edu/mit/wi/pedfile/MathUtil.java b/edu/mit/wi/pedfile/MathUtil.java
new file mode 100755
index 0000000..15db6e0
--- /dev/null
+++ b/edu/mit/wi/pedfile/MathUtil.java
@@ -0,0 +1,114 @@
+/*
+ * $Id: MathUtil.java,v 3.0 2005/01/27 18:19:03 jcbarret Exp $
+ * WHITEHEAD INSTITUTE
+ * SOFTWARE COPYRIGHT NOTICE AGREEMENT
+ * This software and its documentation are copyright 2003 by the
+ * Whitehead Institute for Biomedical Research.  All rights are reserved.
+ *
+ * This software is supplied without any warranty or guaranteed support
+ * whatsoever.  The Whitehead Institute can not be responsible for its
+ * use, misuse, or functionality.
+ */
+
+package edu.mit.wi.pedfile;
+
+/**
+ * <p>Title: MathUtil.java </p>
+ * <p>Description: Includes a list of math functions translated from numerical recipes in C</p>
+ * @author Hui Gong
+ * @version $Revision 1.1 $
+ */
+
+import java.lang.Math;
+
+public class MathUtil {
+	private static final int ITMAX = 100;
+	private static double EPS = 3.0e-7;
+	private static double FPMIN = 1.0e-30;
+
+	public static double gammq (double a, double x) {
+		double gamser, gammcf; //, gln;
+		//if ((x < 0.0 )|| (a <= 0.0 ))
+		//	throw new CheckDataException("Invalid arguments in routine gammq" );
+        try{
+            if (x < (a + 1.0) ) {
+                gamser = gser(a , x);
+                return (1.0 - gamser) ;
+            }
+            else {
+                gammcf = gcf (a , x);
+                return gammcf ;
+            }
+        }catch (CheckDataException e){
+            //TODO: fix this
+            return 0;
+        }
+    }
+
+    public static double gcf (double a, double x) throws CheckDataException {
+        int i;
+        double an, gammcf, b, c, d, del, h, gln;
+        gln = gammln (a );
+        b = x + 1.0 - a ;
+        c = 1.0 / FPMIN ;
+        d = 1.0 / b ;
+        h = d ;
+        for (i = 1 ; i <= ITMAX ; i ++ ) {
+            an = -i * (i - a);
+            b += 2.0 ;
+            d = an * d + b ;
+            if (Math.abs(d )< FPMIN ) d = FPMIN ;
+            c = b + an / c ;
+            if (Math.abs (c )< FPMIN ) c = FPMIN ;
+            d = 1.0 / d ;
+            del = d * c ;
+            h *= del ;
+            if (Math.abs (del - 1.0 )< EPS ) break ;
+        }
+
+        if (i > ITMAX ){
+            throw new CheckDataException("a too large, ITMAX too small in gcf");
+        }
+        gammcf = Math.exp (-x + a * Math.log (x )- gln)* h ;
+        return gammcf;
+    }
+
+    public static double gammln (double xx){
+        double x, y, tmp, ser;
+        double cof[] ={76.18009172947146, -86.50532032941677, 24.01409824083091, -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5};
+        int j;
+        y = xx;
+        x = xx;
+        tmp = x + 5.5 ;
+        tmp -= (x + 0.5) * Math.log (tmp );
+        ser = 1.000000000190015 ;
+        for (j = 0 ; j <= 5 ; j ++ ) ser += cof[j]/++y;
+        return -tmp + Math.log (2.5066282746310005 * ser / x );
+    }
+
+    public static double gser (double a, double x) throws CheckDataException {
+        int n;
+        double sum, gln, del, ap, gamser = 0;
+        gln = gammln(a);
+        if (x <= 0.0 ) {
+            if (x < 0.0 ) throw new CheckDataException("x less than 0 in routine gser" );
+            gamser  = 0.0 ;
+            return gamser;
+        }
+        else {
+            ap = a ;
+            del = sum = 1.0 / a ;
+            for (n = 1 ; n <= ITMAX ; n ++ ) {
+                ++ap;
+                del *= x / ap ;
+                sum += del ;
+                if (Math.abs (del )< (Math.abs (sum )* EPS) ) {
+                    gamser = sum * Math.exp (-x + a * Math.log (x )- gln);
+                    return gamser;
+                }
+            }
+            throw new CheckDataException("a too large, ITMAX too small in routine gser" );
+        }
+    }
+
+}
diff --git a/edu/mit/wi/pedfile/MendelError.java b/edu/mit/wi/pedfile/MendelError.java
new file mode 100755
index 0000000..5b1765a
--- /dev/null
+++ b/edu/mit/wi/pedfile/MendelError.java
@@ -0,0 +1,21 @@
+package edu.mit.wi.pedfile;
+
+
+
+public class MendelError {
+
+    private String family, child;
+
+    public MendelError (String fam, String ind){
+        family = fam;
+        child = ind;
+    }
+
+    public String getFamilyID(){
+        return family;
+    }
+
+    public String getChildID(){
+        return child;
+    }
+}
diff --git a/edu/mit/wi/pedfile/PedFile.java b/edu/mit/wi/pedfile/PedFile.java
new file mode 100755
index 0000000..71e1a2e
--- /dev/null
+++ b/edu/mit/wi/pedfile/PedFile.java
@@ -0,0 +1,1789 @@
+/*
+* $Id: PedFile.java,v 3.48 2008/02/24 19:54:12 djbender Exp $
+* WHITEHEAD INSTITUTE
+* SOFTWARE COPYRIGHT NOTICE AGREEMENT
+* This software and its documentation are copyright 2002 by the
+* Whitehead Institute for Biomedical Research.  All rights are reserved.
+*
+* This software is supplied without any warranty or guaranteed support
+* whatsoever.  The Whitehead Institute can not be responsible for its
+* use, misuse, or functionality.
+*/
+package edu.mit.wi.pedfile;
+
+
+import edu.mit.wi.haploview.Chromosome;
+import edu.mit.wi.haploview.Options;
+import edu.mit.wi.haploview.SNP;
+import edu.mit.wi.haploview.Constants;
+import edu.mit.wi.pedparser.PedParser;
+import edu.mit.wi.pedparser.PedigreeException;
+
+import java.util.*;
+import java.util.zip.GZIPInputStream;
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.io.*;
+
+import org._3pq.jgrapht.graph.SimpleGraph;
+
+/**
+ * Handles input and storage of Pedigree files
+ *
+ * this class is not thread safe (untested).
+ * modified from original Pedfile and checkdata classes by Hui Gong
+ * @author Julian Maller
+ */
+public class PedFile {
+    private Hashtable families;
+
+    private Vector axedPeople = new Vector();
+
+    //stores the individuals found by parse() in allIndividuals. this is useful for outputting Pedigree information to a file of another type.
+    private Vector allIndividuals;
+
+    //stores the individuals chosen by pedparser
+    private Vector unrelatedIndividuals;
+
+    private Vector results = null;
+    private String[][] hminfo;
+    //bogusParents is true if someone in the file referenced a parent not in the file
+    private boolean bogusParents = false;
+    private Vector haploidHets;
+    private boolean mendels = false;
+
+    private static Hashtable hapMapTranslate;
+    private int[] markerRatings;
+    private int[] dups;
+    private HashSet whitelist;
+
+
+    public PedFile(){
+
+        //hardcoded hapmap info
+        this.families = new Hashtable();
+
+        hapMapTranslate = new Hashtable(90,1);
+        hapMapTranslate.put("NA10846", "1334 NA10846 NA12144 NA12145 1 0" );
+        hapMapTranslate.put("NA12144", "1334 NA12144 0 0 1 0");
+        hapMapTranslate.put("NA12145", "1334 NA12145 0 0 2 0");
+        hapMapTranslate.put("NA10847", "1334a NA10847 NA12146 NA12239 2 0" );
+        hapMapTranslate.put("NA12146", "1334a NA12146 0 0 1 0");
+        hapMapTranslate.put("NA12239", "1334a NA12239 0 0 2 0");
+        hapMapTranslate.put("NA07029", "1340 NA07029 NA06994 NA07000 1 0" );
+        hapMapTranslate.put("NA06994", "1340 NA06994 0 0 1 0");
+        hapMapTranslate.put("NA07000", "1340 NA07000 0 0 2 0");
+        hapMapTranslate.put("NA07019", "1340a NA07019 NA07022 NA07056 2 0" );
+        hapMapTranslate.put("NA07022", "1340a NA07022 0 0 1 0");
+        hapMapTranslate.put("NA07056", "1340a NA07056 0 0 2 0");
+        hapMapTranslate.put("NA07048", "1341 NA07048 NA07034 NA07055 1 0" );
+        hapMapTranslate.put("NA07034", "1341 NA07034 0 0 1 0");
+        hapMapTranslate.put("NA07055", "1341 NA07055 0 0 2 0");
+        hapMapTranslate.put("NA06991", "1341a NA06991 NA06993 NA06985 2 0" );
+        hapMapTranslate.put("NA06993", "1341a NA06993 0 0 1 0");
+        //hapMapTranslate.put("NA06993.dup", "dup NA06993.dup 0 0 1 0");
+        hapMapTranslate.put("NA06985", "1341a NA06985 0 0 2 0");
+        hapMapTranslate.put("NA10851", "1344 NA10851 NA12056 NA12057 1 0" );
+        hapMapTranslate.put("NA12056", "1344 NA12056 0 0 1 0");
+        hapMapTranslate.put("NA12057", "1344 NA12057 0 0 2 0");
+        hapMapTranslate.put("NA07348", "1345 NA07348 NA07357 NA07345 2 0" );
+        hapMapTranslate.put("NA07357", "1345 NA07357 0 0 1 0");
+        hapMapTranslate.put("NA07345", "1345 NA07345 0 0 2 0");
+        hapMapTranslate.put("NA10857", "1346 NA10857 NA12043 NA12044 1 0" );
+        hapMapTranslate.put("NA12043", "1346 NA12043 0 0 1 0");
+        hapMapTranslate.put("NA12044", "1346 NA12044 0 0 2 0");
+        hapMapTranslate.put("NA10859", "1347 NA10859 NA11881 NA11882 2 0" );
+        hapMapTranslate.put("NA11881", "1347 NA11881 0 0 1 0");
+        hapMapTranslate.put("NA11882", "1347 NA11882 0 0 2 0");
+        hapMapTranslate.put("NA10854", "1349 NA10854 NA11839 NA11840 2 0" );
+        hapMapTranslate.put("NA11839", "1349 NA11839 0 0 1 0");
+        hapMapTranslate.put("NA11840", "1349 NA11840 0 0 2 0");
+        hapMapTranslate.put("NA10856", "1350 NA10856 NA11829 NA11830 1 0" );
+        hapMapTranslate.put("NA11829", "1350 NA11829 0 0 1 0");
+        hapMapTranslate.put("NA11830", "1350 NA11830 0 0 2 0");
+        hapMapTranslate.put("NA10855", "1350a NA10855 NA11831 NA11832 2 0" );
+        hapMapTranslate.put("NA11831", "1350a NA11831 0 0 1 0");
+        hapMapTranslate.put("NA11832", "1350a NA11832 0 0 2 0");
+        hapMapTranslate.put("NA12707", "1358 NA12707 NA12716 NA12717 1 0" );
+        hapMapTranslate.put("NA12716", "1358 NA12716 0 0 1 0");
+        hapMapTranslate.put("NA12717", "1358 NA12717 0 0 2 0");
+        hapMapTranslate.put("NA10860", "1362 NA10860 NA11992 NA11993 1 0" );
+        hapMapTranslate.put("NA11992", "1362 NA11992 0 0 1 0");
+        hapMapTranslate.put("NA11993", "1362 NA11993 0 0 2 0");
+        // hapMapTranslate.put("NA11993.dup", "dup NA11993.dup 0 0 2 0");
+        hapMapTranslate.put("NA10861", "1362a NA10861 NA11994 NA11995 2 0" );
+        hapMapTranslate.put("NA11994", "1362a NA11994 0 0 1 0");
+        hapMapTranslate.put("NA11995", "1362a NA11995 0 0 2 0");
+        hapMapTranslate.put("NA10863", "1375 NA10863 NA12264 NA12234 2 0" );
+        hapMapTranslate.put("NA12264", "1375 NA12264 0 0 1 0");
+        hapMapTranslate.put("NA12234", "1375 NA12234 0 0 2 0");
+        hapMapTranslate.put("NA10830", "1408 NA10830 NA12154 NA12236 1 0" );
+        hapMapTranslate.put("NA12154", "1408 NA12154 0 0 1 0");
+        hapMapTranslate.put("NA12236", "1408 NA12236 0 0 2 0");
+        hapMapTranslate.put("NA10831", "1408a NA10831 NA12155 NA12156 2 0" );
+        hapMapTranslate.put("NA12155", "1408a NA12155 0 0 1 0");
+        hapMapTranslate.put("NA12156", "1408a NA12156 0 0 2 0");
+        //hapMapTranslate.put("NA12156.dup", "dup NA12156.dup 0 0 2 0");
+        hapMapTranslate.put("NA10835", "1416 NA10835 NA12248 NA12249 1 0" );
+        hapMapTranslate.put("NA12248", "1416 NA12248 0 0 1 0");
+        // hapMapTranslate.put("NA12248.dup", "dup NA1248.dup 0 0 1 0");
+        hapMapTranslate.put("NA12249", "1416 NA12249 0 0 2 0");
+        hapMapTranslate.put("NA10838", "1420 NA10838 NA12003 NA12004 1 0" );
+        hapMapTranslate.put("NA12003", "1420 NA12003 0 0 1 0");
+        //hapMapTranslate.put("NA12003.dup", "dup NA12003.dup 0 0 1 0");
+        hapMapTranslate.put("NA12004", "1420 NA12004 0 0 2 0");
+        hapMapTranslate.put("NA10839", "1420a NA10839 NA12005 NA12006 2 0" );
+        hapMapTranslate.put("NA12005", "1420a NA12005 0 0 1 0");
+        hapMapTranslate.put("NA12006", "1420a NA12006 0 0 2 0");
+        hapMapTranslate.put("NA12740", "1444 NA12740 NA12750 NA12751 2 0" );
+        hapMapTranslate.put("NA12750", "1444 NA12750 0 0 1 0");
+        hapMapTranslate.put("NA12751", "1444 NA12751 0 0 2 0");
+        hapMapTranslate.put("NA12752", "1447 NA12752 NA12760 NA12761 1 0" );
+        hapMapTranslate.put("NA12760", "1447 NA12760 0 0 1 0");
+        hapMapTranslate.put("NA12761", "1447 NA12761 0 0 2 0");
+        hapMapTranslate.put("NA12753", "1447a NA12753 NA12762 NA12763 2 0" );
+        hapMapTranslate.put("NA12762", "1447a NA12762 0 0 1 0");
+        hapMapTranslate.put("NA12763", "1447a NA12763 0 0 2 0");
+        hapMapTranslate.put("NA12801", "1454 NA12801 NA12812 NA12813 1 0" );
+        hapMapTranslate.put("NA12812", "1454 NA12812 0 0 1 0");
+        hapMapTranslate.put("NA12813", "1454 NA12813 0 0 2 0");
+        hapMapTranslate.put("NA12802", "1454a NA12802 NA12814 NA12815 2 0" );
+        hapMapTranslate.put("NA12814", "1454a NA12814 0 0 1 0");
+        hapMapTranslate.put("NA12815", "1454a NA12815 0 0 2 0");
+        hapMapTranslate.put("NA12864", "1459 NA12864 NA12872 NA12873 1 0" );
+        hapMapTranslate.put("NA12872", "1459 NA12872 0 0 1 0");
+        hapMapTranslate.put("NA12873", "1459 NA12873 0 0 2 0");
+        hapMapTranslate.put("NA12865", "1459a NA12865 NA12874 NA12875 2 0" );
+        hapMapTranslate.put("NA12874", "1459a NA12874 0 0 1 0");
+        hapMapTranslate.put("NA12875", "1459a NA12875 0 0 2 0");
+        hapMapTranslate.put("NA12878", "1463 NA12878 NA12891 NA12892 2 0" );
+        hapMapTranslate.put("NA12891", "1463 NA12891 0 0 1 0");
+        hapMapTranslate.put("NA12892", "1463 NA12892 0 0 2 0");
+        hapMapTranslate.put("NA18526", "chi1 NA18526 0 0 2 0");
+        hapMapTranslate.put("NA18524", "chi2 NA18524 0 0 1 0");
+        hapMapTranslate.put("NA18529", "chi3 NA18529 0 0 2 0");
+        hapMapTranslate.put("NA18558", "chi4 NA18558 0 0 1 0");
+        hapMapTranslate.put("NA18532", "chi5 NA18532 0 0 2 0");
+        hapMapTranslate.put("NA18561", "chi6 NA18561 0 0 1 0");
+        hapMapTranslate.put("NA18942", "jap1 NA18942 0 0 2 0");
+        hapMapTranslate.put("NA18940", "jap2 NA18940 0 0 1 0");
+        hapMapTranslate.put("NA18951", "jap3 NA18951 0 0 2 0");
+        hapMapTranslate.put("NA18943", "jap4 NA18943 0 0 1 0");
+        hapMapTranslate.put("NA18947", "jap5 NA18947 0 0 2 0");
+        hapMapTranslate.put("NA18944", "jap6 NA18944 0 0 1 0");
+        hapMapTranslate.put("NA18562", "chi7 NA18562 0 0 1 0");
+        hapMapTranslate.put("NA18537", "chi8 NA18537 0 0 2 0");
+        hapMapTranslate.put("NA18603", "chi9 NA18603 0 0 1 0");
+        hapMapTranslate.put("NA18540", "chi10 NA18540 0 0 2 0");
+        hapMapTranslate.put("NA18605", "chi11 NA18605 0 0 1 0");
+        hapMapTranslate.put("NA18542", "chi12 NA18542 0 0 2 0");
+        hapMapTranslate.put("NA18945", "jap7 NA18945 0 0 1 0");
+        hapMapTranslate.put("NA18949", "jap8 NA18949 0 0 2 0");
+        hapMapTranslate.put("NA18948", "jap9 NA18948 0 0 1 0");
+        hapMapTranslate.put("NA18952", "jap10 NA18952 0 0 1 0");
+        hapMapTranslate.put("NA18956", "jap11 NA18956 0 0 2 0");
+        hapMapTranslate.put("NA18545", "chi13 NA18545 0 0 2 0");
+        hapMapTranslate.put("NA18572", "chi46 NA18572 0 0 1 0");
+        hapMapTranslate.put("NA18547", "chi15 NA18547 0 0 2 0");
+        hapMapTranslate.put("NA18609", "chi16 NA18609 0 0 1 0");
+        hapMapTranslate.put("NA18550", "chi17 NA18550 0 0 2 0");
+        hapMapTranslate.put("NA18608", "chi18 NA18608 0 0 1 0");
+        hapMapTranslate.put("NA18964", "jap12 NA18964 0 0 2 0");
+        hapMapTranslate.put("NA18953", "jap13 NA18953 0 0 1 0");
+        hapMapTranslate.put("NA18968", "jap14 NA18968 0 0 2 0");
+        hapMapTranslate.put("NA18959", "jap15 NA18959 0 0 1 0");
+        hapMapTranslate.put("NA18969", "jap16 NA18969 0 0 2 0");
+        hapMapTranslate.put("NA18960", "jap17 NA18960 0 0 1 0");
+        hapMapTranslate.put("NA18552", "chi19 NA18552 0 0 2 0");
+        hapMapTranslate.put("NA18611", "chi20 NA18611 0 0 1 0");
+        hapMapTranslate.put("NA18555", "chi21 NA18555 0 0 2 0");
+        hapMapTranslate.put("NA18564", "chi22 NA18564 0 0 2 0");
+        hapMapTranslate.put("NA18961", "jap18 NA18961 0 0 1 0");
+        hapMapTranslate.put("NA18972", "jap19 NA18972 0 0 2 0");
+        hapMapTranslate.put("NA18965", "jap20 NA18965 0 0 1 0");
+        hapMapTranslate.put("NA18973", "jap21 NA18973 0 0 2 0");
+        hapMapTranslate.put("NA18966", "jap22 NA18966 0 0 1 0");
+        hapMapTranslate.put("NA18975", "jap23 NA18975 0 0 2 0");
+        hapMapTranslate.put("NA18566", "chi23 NA18566 0 0 2 0");
+        hapMapTranslate.put("NA18563", "chi24 NA18563 0 0 1 0");
+        hapMapTranslate.put("NA18570", "chi25 NA18570 0 0 2 0");
+        hapMapTranslate.put("NA18612", "chi26 NA18612 0 0 1 0");
+        hapMapTranslate.put("NA18571", "chi27 NA18571 0 0 2 0");
+        hapMapTranslate.put("NA18620", "chi28 NA18620 0 0 1 0");
+        hapMapTranslate.put("NA18976", "jap24 NA18976 0 0 2 0");
+        hapMapTranslate.put("NA18967", "jap25 NA18967 0 0 1 0");
+        hapMapTranslate.put("NA18978", "jap26 NA18978 0 0 2 0");
+        hapMapTranslate.put("NA18970", "jap27 NA18970 0 0 1 0");
+        hapMapTranslate.put("NA18980", "jap28 NA18980 0 0 2 0");
+        hapMapTranslate.put("NA18995", "jap29 NA18995 0 0 1 0");
+        hapMapTranslate.put("NA18621", "chi29 NA18621 0 0 1 0");
+        hapMapTranslate.put("NA18594", "chi30 NA18594 0 0 2 0");
+        //  hapMapTranslate.put("NA18594.dup", "dup 0 0 0 0 0");
+        //  hapMapTranslate.put("NA18603.dup", "dup 0 0 0 0 0");
+        //  hapMapTranslate.put("NA18609.dup", "dup 0 0 0 0 0");
+        //  hapMapTranslate.put("NA18951.dup", "dup 0 0 0 0 0");
+        //  hapMapTranslate.put("NA18995.dup", "dup 0 0 0 0 0");
+        hapMapTranslate.put("NA18622", "chi31 NA18622 0 0 1 0");
+        hapMapTranslate.put("NA18573", "chi32 NA18573 0 0 2 0");
+        hapMapTranslate.put("NA18623", "chi33 NA18623 0 0 1 0");
+        hapMapTranslate.put("NA18576", "chi34 NA18576 0 0 2 0");
+        hapMapTranslate.put("NA18971", "jap30 NA18971 0 0 1 0");
+        hapMapTranslate.put("NA18981", "jap31 NA18981 0 0 2 0");
+        hapMapTranslate.put("NA18974", "jap32 NA18974 0 0 1 0");
+        hapMapTranslate.put("NA18987", "jap33 NA18987 0 0 2 0");
+        hapMapTranslate.put("NA18990", "jap34 NA18990 0 0 1 0");
+        hapMapTranslate.put("NA18991", "jap35 NA18991 0 0 2 0");
+        hapMapTranslate.put("NA18577", "chi35 NA18577 0 0 2 0");
+        hapMapTranslate.put("NA18624", "chi36 NA18624 0 0 1 0");
+        hapMapTranslate.put("NA18579", "chi37 NA18579 0 0 2 0");
+        hapMapTranslate.put("NA18632", "chi38 NA18632 0 0 1 0");
+        hapMapTranslate.put("NA18582", "chi39 NA18582 0 0 2 0");
+        hapMapTranslate.put("NA18633", "chi40 NA18633 0 0 1 0");
+        hapMapTranslate.put("NA18994", "jap36 NA18994 0 0 1 0");
+        hapMapTranslate.put("NA18992", "jap37 NA18992 0 0 2 0");
+        hapMapTranslate.put("NA18997", "jap38 NA18997 0 0 2 0");
+        hapMapTranslate.put("NA18996", "jap39 NA18996 0 0 1 0");
+        hapMapTranslate.put("NA18635", "chi41 NA18635 0 0 1 0");
+        hapMapTranslate.put("NA18592", "chi42 NA18592 0 0 2 0");
+        hapMapTranslate.put("NA18636", "chi43 NA18636 0 0 1 0");
+        hapMapTranslate.put("NA18593", "chi44 NA18593 0 0 2 0");
+        hapMapTranslate.put("NA18637", "chi45 NA18637 0 0 1 0");
+        hapMapTranslate.put("NA19000", "jap40 NA19000 0 0 1 0");
+        hapMapTranslate.put("NA18998", "jap41 NA18998 0 0 2 0");
+        hapMapTranslate.put("NA19005", "jap42 NA19005 0 0 1 0");
+        hapMapTranslate.put("NA18999", "jap43 NA18999 0 0 2 0");
+        hapMapTranslate.put("NA19007", "jap44 NA19007 0 0 1 0");
+        hapMapTranslate.put("NA19003", "jap45 NA19003 0 0 2 0");
+        hapMapTranslate.put("NA19012", "jap46 NA19012 0 0 1 0");
+        hapMapTranslate.put("NA18500", "Yoruba004 NA18500 NA18501 NA18502 1 0");
+        hapMapTranslate.put("NA18501", "Yoruba004 NA18501 0 0 1 0");
+        hapMapTranslate.put("NA18502", "Yoruba004 NA18502 0 0 2 0");
+        hapMapTranslate.put("NA18503", "Yoruba005 NA18503 NA18504 NA18505 1 0");
+        hapMapTranslate.put("NA18504", "Yoruba005 NA18504 0 0 1 0");
+        hapMapTranslate.put("NA18505", "Yoruba005 NA18505 0 0 2 0");
+        hapMapTranslate.put("NA18506", "Yoruba009 NA18506 NA18507 NA18508 1 0");
+        hapMapTranslate.put("NA18507", "Yoruba009 NA18507 0 0 1 0");
+        hapMapTranslate.put("NA18508", "Yoruba009 NA18508 0 0 2 0");
+        hapMapTranslate.put("NA18860", "Yoruba012 NA18860 NA18859 NA18858 1 0");
+        hapMapTranslate.put("NA18859", "Yoruba012 NA18859 0 0 1 0");
+        hapMapTranslate.put("NA18858", "Yoruba012 NA18858 0 0 2 0");
+        hapMapTranslate.put("NA18515", "Yoruba013 NA18515 NA18516 NA18517 1 0");
+        hapMapTranslate.put("NA18516", "Yoruba013 NA18516 0 0 1 0");
+        hapMapTranslate.put("NA18517", "Yoruba013 NA18517 0 0 2 0");
+        hapMapTranslate.put("NA18521", "Yoruba016 NA18521 NA18522 NA18523 1 0");
+        hapMapTranslate.put("NA18522", "Yoruba016 NA18522 0 0 1 0");
+        hapMapTranslate.put("NA18523", "Yoruba016 NA18523 0 0 2 0");
+        hapMapTranslate.put("NA18872", "Yoruba017 NA18872 NA18871 NA18870 1 0");
+        hapMapTranslate.put("NA18871", "Yoruba017 NA18871 0 0 1 0");
+        hapMapTranslate.put("NA18870", "Yoruba017 NA18870 0 0 2 0");
+        hapMapTranslate.put("NA18854", "Yoruba018 NA18854 NA18853 NA18852 1 0");
+        hapMapTranslate.put("NA18853", "Yoruba018 NA18853 0 0 1 0");
+        hapMapTranslate.put("NA18852", "Yoruba018 NA18852 0 0 2 0");
+        hapMapTranslate.put("NA18857", "Yoruba023 NA18857 NA18856 NA18855 1 0");
+        hapMapTranslate.put("NA18856", "Yoruba023 NA18856 0 0 1 0");
+        hapMapTranslate.put("NA18855", "Yoruba023 NA18855 0 0 2 0");
+        hapMapTranslate.put("NA18863", "Yoruba024 NA18863 NA18862 NA18861 1 0");
+        hapMapTranslate.put("NA18862", "Yoruba024 NA18862 0 0 1 0");
+        hapMapTranslate.put("NA18861", "Yoruba024 NA18861 0 0 2 0");
+        hapMapTranslate.put("NA18914", "Yoruba028 NA18914 NA18913 NA18912 1 0");
+        hapMapTranslate.put("NA18913", "Yoruba028 NA18913 0 0 1 0");
+        hapMapTranslate.put("NA18912", "Yoruba028 NA18912 0 0 2 0");
+        hapMapTranslate.put("NA19094", "Yoruba040 NA19094 NA19092 NA19093 2 0");
+        hapMapTranslate.put("NA19092", "Yoruba040 NA19092 0 0 1 0");
+        hapMapTranslate.put("NA19093", "Yoruba040 NA19093 0 0 2 0");
+        hapMapTranslate.put("NA19103", "Yoruba042 NA19103 NA19101 NA19102 1 0");
+        hapMapTranslate.put("NA19101", "Yoruba042 NA19101 0 0 1 0");
+        hapMapTranslate.put("NA19102", "Yoruba042 NA19102 0 0 2 0");
+        hapMapTranslate.put("NA19139", "Yoruba043 NA19139 NA19138 NA19137 1 0");
+        hapMapTranslate.put("NA19138", "Yoruba043 NA19138 0 0 1 0");
+        hapMapTranslate.put("NA19137", "Yoruba043 NA19137 0 0 2 0");
+        hapMapTranslate.put("NA19202", "Yoruba045 NA19202 NA19200 NA19201 2 0");
+        hapMapTranslate.put("NA19200", "Yoruba045 NA19200 0 0 1 0");
+        hapMapTranslate.put("NA19201", "Yoruba045 NA19201 0 0 2 0");
+        hapMapTranslate.put("NA19173", "Yoruba047 NA19173 NA19171 NA19172 1 0");
+        hapMapTranslate.put("NA19171", "Yoruba047 NA19171 0 0 1 0");
+        hapMapTranslate.put("NA19172", "Yoruba047 NA19172 0 0 2 0");
+        hapMapTranslate.put("NA19205", "Yoruba048 NA19205 NA19203 NA19204 1 0");
+        hapMapTranslate.put("NA19203", "Yoruba048 NA19203 0 0 1 0");
+        hapMapTranslate.put("NA19204", "Yoruba048 NA19204 0 0 2 0");
+        hapMapTranslate.put("NA19211", "Yoruba050 NA19211 NA19210 NA19209 1 0");
+        hapMapTranslate.put("NA19210", "Yoruba050 NA19210 0 0 1 0");
+        hapMapTranslate.put("NA19209", "Yoruba050 NA19209 0 0 2 0");
+        hapMapTranslate.put("NA19208", "Yoruba051 NA19208 NA19207 NA19206 1 0");
+        hapMapTranslate.put("NA19207", "Yoruba051 NA19207 0 0 1 0");
+        hapMapTranslate.put("NA19206", "Yoruba051 NA19206 0 0 2 0");
+        hapMapTranslate.put("NA19161", "Yoruba056 NA19161 NA19160 NA19159 1 0");
+        hapMapTranslate.put("NA19160", "Yoruba056 NA19160 0 0 1 0");
+        hapMapTranslate.put("NA19159", "Yoruba056 NA19159 0 0 2 0");
+        hapMapTranslate.put("NA19221", "Yoruba058 NA19221 NA19223 NA19222 2 0");
+        hapMapTranslate.put("NA19223", "Yoruba058 NA19223 0 0 1 0");
+        hapMapTranslate.put("NA19222", "Yoruba058 NA19222 0 0 2 0");
+        hapMapTranslate.put("NA19120", "Yoruba060 NA19120 NA19119 NA19116 1 0");
+        hapMapTranslate.put("NA19119", "Yoruba060 NA19119 0 0 1 0");
+        hapMapTranslate.put("NA19116", "Yoruba060 NA19116 0 0 2 0");
+        hapMapTranslate.put("NA19142", "Yoruba071 NA19142 NA19141 NA19140 1 0");
+        hapMapTranslate.put("NA19141", "Yoruba071 NA19141 0 0 1 0");
+        hapMapTranslate.put("NA19140", "Yoruba071 NA19140 0 0 2 0");
+        hapMapTranslate.put("NA19154", "Yoruba072 NA19154 NA19153 NA19152 1 0");
+        hapMapTranslate.put("NA19153", "Yoruba072 NA19153 0 0 1 0");
+        hapMapTranslate.put("NA19152", "Yoruba072 NA19152 0 0 2 0");
+        hapMapTranslate.put("NA19145", "Yoruba074 NA19145 NA19144 NA19143 1 0");
+        hapMapTranslate.put("NA19144", "Yoruba074 NA19144 0 0 1 0");
+        hapMapTranslate.put("NA19143", "Yoruba074 NA19143 0 0 2 0");
+        hapMapTranslate.put("NA19129", "Yoruba077 NA19129 NA19128 NA19127 2 0");
+        hapMapTranslate.put("NA19128", "Yoruba077 NA19128 0 0 1 0");
+        hapMapTranslate.put("NA19127", "Yoruba077 NA19127 0 0 2 0");
+        hapMapTranslate.put("NA19132", "Yoruba101 NA19132 NA19130 NA19131 2 0");
+        hapMapTranslate.put("NA19130", "Yoruba101 NA19130 0 0 1 0");
+        hapMapTranslate.put("NA19131", "Yoruba101 NA19131 0 0 2 0");
+        hapMapTranslate.put("NA19100", "Yoruba105 NA19100 NA19098 NA19099 2 0");
+        hapMapTranslate.put("NA19098", "Yoruba105 NA19098 0 0 1 0");
+        hapMapTranslate.put("NA19099", "Yoruba105 NA19099 0 0 2 0");
+        hapMapTranslate.put("NA19194", "Yoruba112 NA19194 NA19192 NA19193 1 0");
+        hapMapTranslate.put("NA19192", "Yoruba112 NA19192 0 0 1 0");
+        hapMapTranslate.put("NA19193", "Yoruba112 NA19193 0 0 2 0");
+        hapMapTranslate.put("NA19240", "Yoruba117 NA19240 NA19239 NA19238 2 0");
+        hapMapTranslate.put("NA19239", "Yoruba117 NA19239 0 0 1 0");
+        hapMapTranslate.put("NA19238", "Yoruba117 NA19238 0 0 2 0");
+
+        // data entered for the additional perlegen samples
+        hapMapTranslate.put("NA10844", "NA10844 NA10844 0 0 2 0");
+        hapMapTranslate.put("NA17134", "NA17134 NA17134 0 0 2 0");
+        hapMapTranslate.put("NA17115", "NA17115 NA17115 0 0 1 0");
+        hapMapTranslate.put("NA12560", "NA12560 NA12560 0 0 1 0");
+        hapMapTranslate.put("NA17137", "NA17137 NA17137 0 0 2 0");
+        hapMapTranslate.put("NA17747", "NA17747 NA17747 0 0 2 0");
+        hapMapTranslate.put("NA12547", "NA12547 NA12547 0 0 1 0");
+        hapMapTranslate.put("NA17138", "NA17138 NA17138 0 0 2 0");
+        hapMapTranslate.put("NA17116", "NA17116 NA17116 0 0 2 0");
+        hapMapTranslate.put("NA17753", "NA17753 NA17753 0 0 1 0");
+        hapMapTranslate.put("NA17102", "NA17102 NA17102 0 0 1 0");
+        hapMapTranslate.put("NA10858", "NA10858 NA10858 0 0 1 0");
+        hapMapTranslate.put("NA17106", "NA17106 NA17106 0 0 1 0");
+        hapMapTranslate.put("NA17114", "NA17114 NA17114 0 0 1 0");
+        hapMapTranslate.put("NA12548", "NA12548 NA12548 0 0 2 0");
+        hapMapTranslate.put("NA17109", "NA17109 NA17109 0 0 1 0");
+        hapMapTranslate.put("NA17740", "NA17740 NA17740 0 0 2 0");
+        hapMapTranslate.put("NA17139", "NA17139 NA17139 0 0 2 0");
+        hapMapTranslate.put("NA07349", "NA07349 NA07349 0 0 1 0");
+        hapMapTranslate.put("NA17761", "NA17761 NA17761 0 0 2 0");
+        hapMapTranslate.put("NA10853", "NA10853 NA10853 0 0 2 0");
+        hapMapTranslate.put("NA17737", "NA17737 NA17737 0 0 1 0");
+        hapMapTranslate.put("NA17744", "NA17744 NA17744 0 0 2 0");
+        hapMapTranslate.put("NA10845", "NA10845 NA10845 0 0 1 0");
+        hapMapTranslate.put("NA17140", "NA17140 NA17140 0 0 2 0");
+        hapMapTranslate.put("NA17105", "NA17105 NA17105 0 0 1 0");
+        hapMapTranslate.put("NA17752", "NA17752 NA17752 0 0 2 0");
+        hapMapTranslate.put("NA17746", "NA17746 NA17746 0 0 2 0");
+        hapMapTranslate.put("NA17135", "NA17135 NA17135 0 0 2 0");
+        hapMapTranslate.put("NA17742", "NA17742 NA17742 0 0 1 0");
+        hapMapTranslate.put("NA17104", "NA17104 NA17104 0 0 1 0");
+        hapMapTranslate.put("NA17741", "NA17741 NA17741 0 0 2 0");
+        hapMapTranslate.put("NA17738", "NA17738 NA17738 0 0 2 0");
+        hapMapTranslate.put("NA17201", "NA17201 NA17201 0 0 1 0");
+        hapMapTranslate.put("NA17745", "NA17745 NA17745 0 0 2 0");
+        hapMapTranslate.put("NA17749", "NA17749 NA17749 0 0 1 0");
+        hapMapTranslate.put("NA17133", "NA17133 NA17133 0 0 2 0");
+        hapMapTranslate.put("NA10842", "NA10842 NA10842 0 0 1 0");
+        hapMapTranslate.put("NA17736", "NA17736 NA17736 0 0 1 0");
+        hapMapTranslate.put("NA17107", "NA17107 NA17107 0 0 1 0");
+        hapMapTranslate.put("NA10852", "NA10852 NA10852 0 0 2 0");
+        hapMapTranslate.put("NA17756", "NA17756 NA17756 0 0 2 0");
+        hapMapTranslate.put("NA17735", "NA17735 NA17735 0 0 2 0");
+        hapMapTranslate.put("NA10848", "NA10848 NA10848 0 0 1 0");
+        hapMapTranslate.put("NA10850", "NA10850 NA10850 0 0 2 0");
+        hapMapTranslate.put("NA17110", "NA17110 NA17110 0 0 2 0");
+        hapMapTranslate.put("NA17111", "NA17111 NA17111 0 0 1 0");
+        hapMapTranslate.put("NA17136", "NA17136 NA17136 0 0 2 0");
+        hapMapTranslate.put("NA17755", "NA17755 NA17755 0 0 1 0");
+        hapMapTranslate.put("NA06990", "NA06990 NA06990 0 0 2 0");
+        hapMapTranslate.put("NA17733", "NA17733 NA17733 0 0 2 0");
+        hapMapTranslate.put("NA17103", "NA17103 NA17103 0 0 1 0");
+        hapMapTranslate.put("NA17739", "NA17739 NA17739 0 0 2 0");
+        hapMapTranslate.put("NA17108", "NA17108 NA17108 0 0 1 0");
+        hapMapTranslate.put("NA17759", "NA17759 NA17759 0 0 1 0");
+        hapMapTranslate.put("NA17112", "NA17112 NA17112 0 0 2 0");
+        hapMapTranslate.put("NA17113", "NA17113 NA17113 0 0 2 0");
+        hapMapTranslate.put("NA10843", "NA10843 NA10843 0 0 2 0");
+        hapMapTranslate.put("NA17743", "NA17743 NA17743 0 0 1 0");
+        hapMapTranslate.put("NA17757", "NA17757 NA17757 0 0 2 0");
+        hapMapTranslate.put("NA17754", "NA17754 NA17754 0 0 2 0");
+        hapMapTranslate.put("NA17734", "NA17734 NA17734 0 0 2 0");
+    }
+
+    /**
+     * gets the allIndividuals Vector
+     */
+    public Vector getAllIndividuals() {
+        return allIndividuals;
+    }
+
+    public Vector getUnusedIndividuals(){
+        HashSet used = new HashSet(getUnrelatedIndividuals());
+        HashSet all = new HashSet(getAllIndividuals());
+        all.removeAll(used);
+        return new Vector(all);
+    }
+
+    public Vector getUnrelatedIndividuals() {
+        return unrelatedIndividuals;
+    }
+
+    /**
+     *
+     * @return enumeration containing a list of familyID's in the families hashtable
+     */
+    public Enumeration getFamList(){
+        return this.families.keys();
+    }
+    /**
+     *
+     * @param familyID id of desired family
+     * @return Family identified by familyID in families hashtable
+     */
+    public Family getFamily(String familyID){
+        return (Family)this.families.get(familyID);
+    }
+
+    /**
+     *
+     * @return the number of Family objects in the families hashtable
+     */
+    public int getNumFamilies(){
+        return this.families.size();
+    }
+
+    /**
+     * this method iterates through each family in Hashtable families and adds up
+     * the number of individuals in total across all families
+     * @return the total number of individuals in all the family objects in the families hashtable
+     */
+    public int getNumIndividuals(){
+        Enumeration famEnum = this.families.elements();
+        int total =0;
+        while (famEnum.hasMoreElements()) {
+            Family fam = (Family) famEnum.nextElement();
+            total += fam.getNumMembers();
+        }
+        return total;
+    }
+
+    /**
+     * finds the first individual in the first family and returns the number of markers for that individual
+     * @return the number of markers
+     */
+    public int getNumMarkers(){
+        Enumeration famList = this.families.elements();
+        int numMarkers = 0;
+        while (famList.hasMoreElements()) {
+            Family fam = (Family) famList.nextElement();
+            Enumeration indList = fam.getMemberList();
+            Individual ind = null;
+            while(indList.hasMoreElements()){
+                try{
+                    ind = fam.getMember((String)indList.nextElement());
+                }catch(PedFileException pfe){
+                }
+                numMarkers = ind.getNumMarkers();
+                if(numMarkers > 0){
+                    return numMarkers;
+                }
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * takes in a pedigree file in the form of a vector of strings and parses it.
+     * data is stored in families in the member hashtable families
+     */
+    public void parseLinkage(Vector pedigrees) throws PedFileException {
+        int colNum = -1;
+        boolean withOptionalColumn = false;
+        int numMarkers = 0;
+        boolean genoError = false;
+        int numLines = pedigrees.size();
+        if (numLines == 0){
+            throw new PedFileException("Data format error: empty file");
+        }
+        Individual ind;
+        this.allIndividuals = new Vector();
+
+        for(int k=0; k<numLines; k++){
+            StringTokenizer tokenizer = new StringTokenizer((String)pedigrees.get(k), "\n\t\" \"");
+            int numTokens = tokenizer.countTokens();
+
+            //reading the first line
+            if(colNum < 1){
+                //only check column number count for the first nonblank line
+                colNum = numTokens;
+                if(colNum%2==1) {
+                    withOptionalColumn = true;
+                    numMarkers= (numTokens - 7)/2;
+                }else {
+                    numMarkers = (numTokens -6)/2;
+                }
+            }
+            if(colNum != numTokens) {
+                //this line has a different number of columns
+                //should send some sort of error message
+                throw new PedFileException("Column number mismatch in pedfile. line " + (k+1));
+            }
+
+            try{
+                ind = new Individual(numMarkers, false);
+            }catch(NegativeArraySizeException neg) {
+                throw new PedFileException("File formatting error.");
+            }
+            if(numTokens < 6) {
+                throw new PedFileException("Incorrect number of fields on line " + (k+1));
+            }
+
+            if(tokenizer.hasMoreTokens()){
+
+                ind.setFamilyID(new String(tokenizer.nextToken().trim()));
+                ind.setIndividualID(new String(tokenizer.nextToken().trim()));
+                ind.setDadID(new String(tokenizer.nextToken().trim()));
+                ind.setMomID(new String(tokenizer.nextToken().trim()));
+                try {
+                    ind.setGender(Integer.parseInt(tokenizer.nextToken().trim()));
+                    ind.setAffectedStatus(Integer.parseInt(tokenizer.nextToken().trim()));
+                    if(withOptionalColumn) {
+                        ind.setLiability(Integer.parseInt(tokenizer.nextToken().trim()));
+                    }
+                }catch(NumberFormatException nfe) {
+                    throw new PedFileException("Pedfile error: invalid gender or affected status on line " + (k+1));
+                }
+
+                byte genotype1;
+                byte genotype2;
+                if (!tokenizer.hasMoreTokens()){
+                    throw new PedFileException("Pedfile error: no marker genotypes specified.");
+                }
+                while(tokenizer.hasMoreTokens()){
+                    try {
+                        String alleleA = tokenizer.nextToken();
+                        String alleleB = tokenizer.nextToken();
+                        int[] checker1, checker2;
+                        checker1 = checkGenotype(alleleA);
+                        checker2 = checkGenotype(alleleB);
+                        if (checker1[1] != checker2[1]){
+                            genoError = !genoError;
+                        }
+
+                        if (genoError){
+                            throw new PedFileException("File input error on line " + (k+1) + ", marker " + (ind.getNumMarkers()+1)  +
+                                    ".\nFor any marker, an individual's genotype must be only letters or only numbers.");
+                        }
+
+                        if(checker1[0] < 0 || checker1[0] > 4 || checker2[0] < 0 || checker2[0] > 4) {
+                            throw new PedFileException("Pedigree file input error: invalid genotype on line " + (k+1)
+                                    + ".\n all genotypes must be 0-4 or A/C/G/T.");
+                        }
+                        genotype1 = (byte)checker1[0];
+                        genotype2 = (byte)checker2[0];
+                        ind.addMarker(genotype1,genotype2);
+                    }catch(NumberFormatException nfe) {
+                        throw new PedFileException("Pedigree file input error: invalid genotype on line " + (k+1) );
+                    }
+                }
+
+                //check if the family exists already in the Hashtable
+                Family fam = (Family)this.families.get(ind.getFamilyID());
+                if(fam == null){
+                    //it doesnt exist, so create a new Family object
+                    fam = new Family(ind.getFamilyID());
+                }
+
+                if (fam.getMembers().containsKey(ind.getIndividualID())){
+                    throw new PedFileException("Individual "+ind.getIndividualID()+" in family "+ ind.getFamilyID()+" appears more than once.");
+                }
+
+                fam.addMember(ind);
+                this.families.put(ind.getFamilyID(),fam);
+                this.allIndividuals.add(ind);
+
+            }
+        }
+
+        //now we check if anyone has a reference to a parent who isnt in the file, and if so, we remove the reference
+        for(int i=0;i<allIndividuals.size();i++) {
+            Individual currentInd = (Individual) allIndividuals.get(i);
+            Hashtable curFam = ((Family)(families.get(currentInd.getFamilyID())) ).getMembers();
+            if( !currentInd.getDadID().equals("0") && ! (curFam.containsKey(currentInd.getDadID()))) {
+                currentInd.setDadID("0");
+                bogusParents = true;
+            }
+            if(!currentInd.getMomID().equals("0") && ! (curFam.containsKey(currentInd.getMomID()))) {
+                currentInd.setMomID("0");
+                bogusParents = true;
+            }
+        }
+
+
+    }
+
+    public void parseHapMap(Vector lines, Vector hapsData) throws PedFileException {
+        int colNum = -1;
+        int numLines = lines.size();
+        if (numLines < 2){
+            throw new PedFileException("Hapmap data format error: empty file");
+        }
+        if (hapsData != null){
+            String indName;
+            for (int i=0; i < hapsData.size(); i++){
+                StringTokenizer hd = new StringTokenizer((String)hapsData.get(i));
+                if (hd.countTokens() < 6){
+                    throw new PedFileException("Hapmap data format error: pedigree data on line " + (i+1) + ".");
+                }
+                if (hd.countTokens() > 7){
+                    throw new PedFileException("Hapmap data format error: pedigree data on line " + (i+1) + ".");
+                }
+                hd.nextToken();
+                indName = hd.nextToken();
+                hapMapTranslate.put(indName, (String)hapsData.get(i));
+            }
+        }
+        Individual ind;
+
+        this.allIndividuals = new Vector();
+
+        //enumerate indivs
+        StringTokenizer st = new StringTokenizer((String)lines.get(0), "\n\t\" \"");
+        int numMetaColumns = 0;
+        boolean doneMeta = false;
+        boolean genoErrorB = false;
+        while(!doneMeta && st.hasMoreTokens()){
+            String thisfield = st.nextToken();
+            numMetaColumns++;
+            //first indiv ID will be a string beginning with "NA"
+            if (thisfield.startsWith("NA")){
+                doneMeta = true;
+            }
+        }
+        numMetaColumns--;
+
+        st = new StringTokenizer((String)lines.get(0), "\n\t\" \"");
+        for (int i = 0; i < numMetaColumns; i++){
+            st.nextToken();
+        }
+        Vector namesIncludingDups = new Vector();
+        StringTokenizer dt;
+        while (st.hasMoreTokens()){
+            //todo: sort out how this used to work. now it's counting the header line so we subtract 1
+            ind = new Individual(numLines-1, false);
+
+            String name = st.nextToken();
+            namesIncludingDups.add(name);
+            if (name.endsWith("dup")){
+                //skip dups (i.e. don't add 'em to ind array)
+                continue;
+            }
+            String details = (String)hapMapTranslate.get(name);
+            if (details == null){
+                throw new PedFileException("Hapmap data format error: " + name);
+            }
+            dt = new StringTokenizer(details, "\n\t\" \"");
+            ind.setFamilyID(dt.nextToken().trim());
+            ind.setIndividualID(dt.nextToken().trim());
+            ind.setDadID(dt.nextToken().trim());
+            ind.setMomID(dt.nextToken().trim());
+            try {
+                ind.setGender(Integer.parseInt(dt.nextToken().trim()));
+                ind.setAffectedStatus(Integer.parseInt(dt.nextToken().trim()));
+            }catch(NumberFormatException nfe) {
+                throw new PedFileException("File error: invalid gender or affected status for indiv " + name);
+            }
+
+            //check if the family exists already in the Hashtable
+            Family fam = (Family)this.families.get(ind.getFamilyID());
+            if(fam == null){
+                //it doesnt exist, so create a new Family object
+                fam = new Family(ind.getFamilyID());
+            }
+            fam.addMember(ind);
+            this.families.put(ind.getFamilyID(),fam);
+            this.allIndividuals.add(ind);
+        }
+
+        //start at k=1 to skip header which we just processed above.
+        hminfo = new String[numLines-1][];
+        for(int k=1;k<numLines;k++){
+            StringTokenizer tokenizer = new StringTokenizer((String)lines.get(k));
+            //reading the first line
+            if(colNum < 0){
+                //only check column number count for the first line
+                colNum = tokenizer.countTokens();
+            }
+            if(colNum != tokenizer.countTokens()) {
+                //this line has a different number of columns
+                //should send some sort of error message
+                //TODO: add something which stores number of markers for all lines and checks that they're consistent
+                throw new PedFileException("Line number mismatch in input file. line " + (k+1));
+            }
+
+            if(tokenizer.hasMoreTokens()){
+                hminfo[k-1] = new String[2];
+                for (int skip = 0; skip < numMetaColumns; skip++){
+                    //meta-data crap
+                    String s;
+                    try{
+                        s = tokenizer.nextToken().trim();
+                    }catch(NoSuchElementException nse){
+                        throw new PedFileException("Data format error on line " + (k+1) + ": " + (String)lines.get(k));
+                    }
+
+                    //get marker name, chrom and pos
+                    if (skip == 0){
+                        hminfo[k-1][0] = s;
+                    }
+                    if (skip == 2){
+                        String dc = Chromosome.getDataChrom();
+                        if (dc != null && !dc.equals("none")){
+                            if (!dc.equalsIgnoreCase(s)){
+                                throw new PedFileException("Hapmap file format error on line " + (k+1) +
+                                        ":\n The file appears to contain multiple chromosomes:" +
+                                        "\n" + dc + ", " + s);
+                            }
+                        }else{
+                            Chromosome.setDataChrom(s);
+                        }
+                    }
+                    if (skip == 3){
+                        hminfo[k-1][1] = s;
+                    }
+                    if (skip == 5){
+                        Chromosome.setDataBuild(s);
+                    }
+                }
+                int index = 0;
+                int indexIncludingDups = -1;
+                while(tokenizer.hasMoreTokens()){
+                    String alleles = tokenizer.nextToken();
+
+                    indexIncludingDups++;
+                    //we've skipped the dups in the ind array, so we skip their genotypes
+                    if (((String)namesIncludingDups.elementAt(indexIncludingDups)).endsWith("dup")){
+                        continue;
+                    }
+
+                    ind = (Individual)allIndividuals.elementAt(index);
+                    int[] checker1, checker2;
+                    try{
+                        checker1 = checkGenotype(alleles.substring(0,1));
+                        checker2 = checkGenotype(alleles.substring(1,2));
+                    }catch(NumberFormatException nfe){
+                        throw new PedFileException("Invalid genotype on individual " + ind.getIndividualID() + ".");
+                    }
+                    if (checker1[1] != checker2[1]){
+                        genoErrorB = !genoErrorB;
+                    }
+                    byte allele1 = (byte)checker1[0];
+                    byte allele2 = (byte)checker2[0];
+                    ind.addMarker(allele1, allele2);
+                    if (genoErrorB){
+                        throw new PedFileException("File input error: individual " + ind.getIndividualID() + ", marker "
+                                + this.hminfo[ind.getNumMarkers()-1][0] + ".\nFor any marker, an individual's genotype must be only letters or only numbers.");
+                    }
+                    index++;
+                }
+            }
+        }
+    }
+
+    public void parseHapMapPhase(String[] info) throws IOException, PedFileException{
+        if (info[3].equals("")){
+            Chromosome.setDataChrom("none");
+        }else{
+            Chromosome.setDataChrom("chr" + info[3]);
+        }
+        Chromosome.setDataBuild("ncbi_b35");
+        Vector sampleData = new Vector();
+        Vector legendData = new Vector();
+        Vector legendMarkers = new Vector();
+        Vector legendPositions = new Vector();
+        Individual ind = null;
+        byte[] byteDataT = new byte[0];
+        byte[] byteDataU = new byte[0];
+        this.allIndividuals = new Vector();
+
+        InputStream phaseStream, sampleStream, legendStream;
+        String phaseName, sampleName, legendName;
+
+        try {
+            URL sampleURL = new URL(info[1]);
+            sampleName = sampleURL.getFile();
+            sampleStream = sampleURL.openStream();
+        }catch (MalformedURLException mfe){
+            File sampleFile = new File(info[1]);
+            if (sampleFile.length() < 1){
+                throw new PedFileException("Sample file is empty or non-existent: " + sampleFile.getName());
+            }
+            sampleName = sampleFile.getName();
+            sampleStream = new FileInputStream(sampleFile);
+        }catch (IOException ioe){
+            throw new PedFileException("Could not connect to " + info[1]);
+        }
+
+        //read in the individual ids data.
+        try{
+            BufferedReader sampleBuffReader;
+            if (Options.getGzip()){
+                GZIPInputStream sampleInputStream = new GZIPInputStream(sampleStream);
+                sampleBuffReader = new BufferedReader(new InputStreamReader(sampleInputStream));
+            }else{
+                sampleBuffReader = new BufferedReader(new InputStreamReader(sampleStream));
+            }
+            String sampleLine;
+            while((sampleLine = sampleBuffReader.readLine())!=null){
+                StringTokenizer sampleTokenizer = new StringTokenizer(sampleLine);
+                sampleData.add(sampleTokenizer.nextToken());
+            }
+        }catch(NoSuchElementException nse){
+            throw new PedFileException("File format error in " + sampleName);
+        }
+
+         try {
+             URL legendURL = new URL(info[2]);
+             legendName = legendURL.getFile();
+             legendStream = legendURL.openStream();
+         }catch (MalformedURLException mfe){
+             File legendFile = new File(info[2]);
+             if (legendFile.length() < 1){
+                 throw new PedFileException("Legend file is empty or non-existent: " + legendFile.getName());
+             }
+             legendName = legendFile.getName();
+             legendStream = new FileInputStream(legendFile);
+         }catch (IOException ioe){
+             throw new PedFileException("Could not connect to " + info[2]);
+         }
+
+        //read in the legend data
+        try{
+            BufferedReader legendBuffReader;
+            if (Options.getGzip()){
+                GZIPInputStream legendInputStream = new GZIPInputStream(legendStream);
+                legendBuffReader = new BufferedReader(new InputStreamReader(legendInputStream));
+            }else{
+                legendBuffReader = new BufferedReader(new InputStreamReader(legendStream));
+            }
+            String legendLine;
+            String zero, one;
+            while((legendLine = legendBuffReader.readLine())!=null){
+                StringTokenizer legendSt = new StringTokenizer(legendLine);
+                String markerid = legendSt.nextToken();
+                if (markerid.equalsIgnoreCase("rs") || markerid.equalsIgnoreCase("marker")){ //skip header
+                    continue;
+                }
+                legendMarkers.add(markerid);
+                legendPositions.add(legendSt.nextToken());
+                byte[] legendBytes = new byte[2];
+                zero = legendSt.nextToken();
+                one = legendSt.nextToken();
+
+                if (zero.equalsIgnoreCase("A")){
+                    legendBytes[0] = 1;
+                }else if (zero.equalsIgnoreCase("C")){
+                    legendBytes[0] = 2;
+                }else if (zero.equalsIgnoreCase("G")){
+                    legendBytes[0] = 3;
+                }else if (zero.equalsIgnoreCase("T")){
+                    legendBytes[0] = 4;
+                }else{
+                    throw new PedFileException("Invalid allele: " + zero);
+                }
+
+                if (one.equalsIgnoreCase("A")){
+                    legendBytes[1] = 1;
+                }else if (one.equalsIgnoreCase("C")){
+                    legendBytes[1] = 2;
+                }else if (one.equalsIgnoreCase("G")){
+                    legendBytes[1] = 3;
+                }else if (one.equalsIgnoreCase("T")){
+                    legendBytes[1] = 4;
+                }else{
+                    throw new PedFileException("Invalid allele: " + one);
+                }
+
+                legendData.add(legendBytes);
+            }
+
+            hminfo = new String[legendPositions.size()][2];
+
+            for (int i = 0; i < legendPositions.size(); i++){
+                //marker name.
+                hminfo[i][0] = (String)legendMarkers.get(i);
+                //marker position.
+                hminfo[i][1] = (String)legendPositions.get(i);
+            }
+        }catch(NoSuchElementException nse){
+            throw new PedFileException("File format error in " + legendName);
+        }
+
+        try {
+            URL phaseURL = new URL(info[0]);
+            phaseName = phaseURL.getFile();
+            phaseStream = phaseURL.openStream();
+        }catch (MalformedURLException mfe){
+            File phaseFile = new File(info[0]);
+            if (phaseFile.length() < 1){
+                throw new PedFileException("Genotypes file is empty or non-existent: " + phaseFile.getName());
+            }
+            phaseName = phaseFile.getName();
+            phaseStream = new FileInputStream(phaseFile);
+        }catch (IOException ioe){
+            throw new PedFileException("Could not connect to " + info[0]);
+        }
+
+        //read in the phased data.
+        try{
+            BufferedReader phasedBuffReader;
+            if (Options.getGzip()){
+                GZIPInputStream phasedInputStream = new GZIPInputStream(phaseStream);
+                phasedBuffReader = new BufferedReader(new InputStreamReader(phasedInputStream));
+            }else{
+                phasedBuffReader = new BufferedReader(new InputStreamReader(phaseStream));
+            }
+            String phasedLine;
+            int columns = 0;
+            String token;
+            boolean even = false;
+            int iterator = 0;
+            while((phasedLine = phasedBuffReader.readLine()) != null){
+                StringTokenizer phasedSt = new StringTokenizer(phasedLine);
+                columns = phasedSt.countTokens();
+                if(even){
+                    iterator++;
+                }else{   //Only set up a new individual every 2 lines.
+                    ind = new Individual(columns, true);
+                    try{
+                        ind.setIndividualID((String)sampleData.get(iterator));
+                    }catch (ArrayIndexOutOfBoundsException e){
+                        throw new PedFileException("File error: Sample file is missing individual IDs");
+                    }
+                    if (columns != legendData.size()){
+                        throw new PedFileException("File error: invalid number of markers on Individual " + ind.getIndividualID());
+                    }
+                    String details = (String)hapMapTranslate.get(ind.getIndividualID());
+                    //exception in case of wierd compression combos in input files
+                    if (details == null){
+                        throw new PedFileException("File format error in " + sampleName);
+                    }
+                    StringTokenizer dt = new StringTokenizer(details, "\n\t\" \"");
+                    ind.setFamilyID(dt.nextToken().trim());
+                    //skip individualID since we already have it.
+                    dt.nextToken();
+                    ind.setDadID(dt.nextToken());
+                    ind.setMomID(dt.nextToken());
+                    try {
+                        ind.setGender(Integer.parseInt(dt.nextToken().trim()));
+                        ind.setAffectedStatus(Integer.parseInt(dt.nextToken().trim()));
+                    }catch(NumberFormatException nfe) {
+                        throw new PedFileException("File error: invalid gender or affected status for indiv " + ind.getIndividualID());
+                    }
+
+                    //check if the family exists already in the Hashtable
+                    Family fam = (Family)this.families.get(ind.getFamilyID());
+                    if(fam == null){
+                        //it doesnt exist, so create a new Family object
+                        fam = new Family(ind.getFamilyID());
+                    }
+                    fam.addMember(ind);
+                    this.families.put(ind.getFamilyID(),fam);
+                    this.allIndividuals.add(ind);
+                }
+
+                int index = 0;
+                if (!even){
+                    byteDataT = new byte[columns];
+                }else{
+                    byteDataU = new byte[columns];
+                }
+                while(phasedSt.hasMoreTokens()){
+                    token = phasedSt.nextToken();
+                    if (!even){
+                        if (token.equalsIgnoreCase("0")){
+                            byteDataT[index] = ((byte[])legendData.get(index))[0];
+                        }else if (token.equalsIgnoreCase("1")){
+                            byteDataT[index] = ((byte[])legendData.get(index))[1];
+                        }else {
+                            throw new PedFileException("File format error in " + phaseName);
+                        }
+                    }else{
+                        if (token.equalsIgnoreCase("0")){
+                            byteDataU[index] = ((byte[])legendData.get(index))[0];
+                        }else if (token.equalsIgnoreCase("1")){
+                            byteDataU[index] = ((byte[])legendData.get(index))[1];
+                        }else if (Chromosome.getDataChrom().equalsIgnoreCase("chrx") && ind.getGender() == Individual.MALE && token.equalsIgnoreCase("-")){
+                            //X male
+                        }else {
+                            throw new PedFileException("File format error in " + phaseName);
+                        }
+                    }
+                    index++;
+                }
+                if (even){
+                    if (ind.getGender() == Individual.MALE && Chromosome.getDataChrom().equalsIgnoreCase("chrx")){
+                        for(int i=0; i < columns; i++){
+                            ind.addMarker(byteDataT[i], byteDataT[i]);
+                        }
+                    }else{
+                        for(int i=0; i < columns; i++){
+                            ind.addMarker(byteDataT[i], byteDataU[i]);
+                        }
+                    }
+                }
+                even = !even;
+            }
+        }catch(NoSuchElementException nse){
+            throw new PedFileException("File format error in " + phaseName);
+        }
+    }
+
+    public void parsePhasedDownload(String[] info) throws IOException, PedFileException{
+        String targetChrom = "chr" + info[4];
+        Chromosome.setDataChrom(targetChrom);
+        Vector legendMarkers = new Vector();
+        Vector legendPositions = new Vector();
+        Vector hmpVector = new Vector();
+        Individual ind = null;
+        byte[] byteDataT = new byte[0];
+        byte[] byteDataU = new byte[0];
+        this.allIndividuals = new Vector();
+        String panelChoice;
+        if (info[1].equals("CHB+JPT")){
+            panelChoice = "JC";
+        }else{
+            panelChoice = info[1];
+        }
+        boolean pseudoChecked = false;
+        long startPos;
+        if (info[2].equals("0")){
+            startPos = 1;
+        }else{
+            startPos = (Integer.parseInt(info[2]))*1000;
+        }
+        long stopPos = (Integer.parseInt(info[3]))*1000;
+        String phaseChoice;
+        if (info[5].startsWith("16")){
+            Chromosome.setDataBuild("ncbi_b34");
+            phaseChoice = "I";
+        }else if (info[5].equals("21")){
+            Chromosome.setDataBuild("ncbi_b35");
+            phaseChoice = "II";
+        }else{
+            Chromosome.setDataBuild("ncbi_b36");
+            phaseChoice = "III";
+        }
+        String output = info[6];
+        boolean infoDone = false;
+        boolean hminfoDone = false;
+        String urlHmp = "http://www.hapmap.org/cgi-perl/phased?chr=" + targetChrom + "&pop=" + panelChoice +
+                "&start=" + startPos + "&stop=" + stopPos + "&ds=p" + phaseChoice + "&out=" + output + "&filter=cons+"
+                + panelChoice.toLowerCase();
+
+        try{
+            URL hmpUrl = new URL(urlHmp);
+            HttpURLConnection hmpCon = (HttpURLConnection)hmpUrl.openConnection();
+            hmpCon.setRequestProperty("User-agent", Constants.USER_AGENT);
+            hmpCon.setRequestProperty("Accept-Encoding","gzip");
+            hmpCon.connect();
+
+            int response = hmpCon.getResponseCode();
+
+            if ((response != HttpURLConnection.HTTP_ACCEPTED) && (response != HttpURLConnection.HTTP_OK)) {
+                throw new IOException("Could not connect to HapMap database.");
+            }else {
+                GZIPInputStream g = new GZIPInputStream(hmpCon.getInputStream());
+                BufferedReader hmpBuffReader = new BufferedReader(new InputStreamReader(g));
+                String hmpLine;
+                char token;
+                int columns;
+                while((hmpLine = hmpBuffReader.readLine())!=null){
+                    if (hmpLine.startsWith("---")){
+                        //continue;
+                    }else if (hmpLine.startsWith("pop:")){
+                        //continue;
+                    }else if (hmpLine.startsWith("build:")){
+                        StringTokenizer buildSt = new StringTokenizer(hmpLine);
+                        buildSt.nextToken();
+                        Chromosome.setDataBuild(new String(buildSt.nextToken()));
+                    }else if (hmpLine.startsWith("hapmap_release:")){
+                        //continue;
+                    }else if (hmpLine.startsWith("filters:")){
+                        //continue;
+                    }else if (hmpLine.startsWith("start:")){
+                        //continue;
+                    }else if (hmpLine.startsWith("stop:")){
+                        //continue;
+                    }else if (hmpLine.startsWith("snps:")){
+                        //continue;
+                    }else if (hmpLine.startsWith("phased_haplotypes:")){
+                        infoDone = true;
+                    }else if (hmpLine.startsWith("No")){
+                        throw new PedFileException(hmpLine);
+                    }else if (hmpLine.startsWith("Too many")){
+                        throw new PedFileException(hmpLine);
+                    }else if (!infoDone){
+                        StringTokenizer posSt = new StringTokenizer(hmpLine," \t:-");
+                        //posSt.nextToken(); //skip the -
+                        legendMarkers.add(posSt.nextToken());
+                        legendPositions.add(posSt.nextToken());
+                    }else if (infoDone){
+                        if (!hminfoDone){
+                            hminfo = new String[legendPositions.size()][2];
+                            for (int i = 0; i < legendPositions.size(); i++){
+                                //marker name.
+                                hminfo[i][0] = (String)legendMarkers.get(i);
+                                //marker position.
+                                hminfo[i][1] = (String)legendPositions.get(i);
+                            }
+                            hminfoDone = true;
+                        }
+                        hmpVector.add(hmpLine);
+                    }
+                }
+
+                for (int i = 0; i < hmpVector.size(); i++){
+                    StringTokenizer dataSt = new StringTokenizer((String)hmpVector.get(i));
+                    dataSt.nextToken(); //skip the -
+                    String newid = dataSt.nextToken();  //individual ID with _c1/_c2
+                    String data = dataSt.nextToken(); //alleles
+                    columns = data.length();
+                    StringTokenizer filter = new StringTokenizer(newid,"_:");
+                    String id = filter.nextToken();
+                    String strand = filter.nextToken();
+                    if (strand.equals("c1")){   //Only set up a new individual on c1.
+                        ind = new Individual(columns, true);
+                        ind.setIndividualID(new String(id));
+                        if (columns != legendMarkers.size()){
+                            throw new PedFileException("File error: invalid number of markers on Individual " + ind.getIndividualID());
+                        }
+                        String details = (String)hapMapTranslate.get(ind.getIndividualID());
+                        StringTokenizer dt = new StringTokenizer(details, "\n\t\" \"");
+                        ind.setFamilyID(dt.nextToken().trim());
+                        //skip individualID since we already have it.
+                        dt.nextToken();
+                        ind.setDadID(dt.nextToken());
+                        ind.setMomID(dt.nextToken());
+                        try {
+                            ind.setGender(Integer.parseInt(dt.nextToken().trim()));
+                            ind.setAffectedStatus(Integer.parseInt(dt.nextToken().trim()));
+                        }catch(NumberFormatException nfe) {
+                            throw new PedFileException("File error: invalid gender or affected status for indiv " + ind.getIndividualID());
+                        }
+                        if (!pseudoChecked){
+                            if (ind.getGender() == Individual.MALE){
+                                pseudoChecked = true;
+                                if (Chromosome.getDataChrom().equalsIgnoreCase("chrx")){
+                                    StringTokenizer checkSt = new StringTokenizer((String)hmpVector.get(i+1),":- \t");
+                                    String checkNewid = checkSt.nextToken();
+                                    checkSt.nextToken(); //alleles
+                                    StringTokenizer checkFilter = new StringTokenizer(checkNewid,"_");
+                                    checkFilter.nextToken();
+                                    String checkStrand = checkFilter.nextToken();
+                                    if (checkStrand.equals("c2")){
+                                        Chromosome.setDataChrom("chrp");
+                                    }
+                                }
+                            }
+                        }
+
+                        //check if the family exists already in the Hashtable
+                        Family fam = (Family)this.families.get(ind.getFamilyID());
+                        if(fam == null){
+                            //it doesnt exist, so create a new Family object
+                            fam = new Family(ind.getFamilyID());
+                        }
+                        fam.addMember(ind);
+                        this.families.put(ind.getFamilyID(),fam);
+                        this.allIndividuals.add(ind);
+                    }
+
+                    int index = 0;
+                    if (strand.equals("c1")){
+                        byteDataT = new byte[columns];
+                    }else{
+                        byteDataU = new byte[columns];
+                    }
+                    for(int k = 0; k < columns; k++){
+                        token = data.charAt(k);
+                        if (strand.equals("c1")){
+                            if (token == 'A'){
+                                byteDataT[index] = 1;
+                            }else if (token == 'C'){
+                                byteDataT[index] = 2;
+                            }else if (token == 'G'){
+                                byteDataT[index] = 3;
+                            }else if (token == 'T'){
+                                byteDataT[index] = 4;
+                            }else {
+                                throw new PedFileException("Invalid Allele: " + token);
+                            }
+                        }else{
+                            if (token == 'A'){
+                                byteDataU[index] = 1;
+                            }else if (token == 'C'){
+                                byteDataU[index] = 2;
+                            }else if (token == 'G'){
+                                byteDataU[index] = 3;
+                            }else if (token == 'T'){
+                                byteDataU[index] = 4;
+                            }else if (token == '-'){
+                                /*if (!(Chromosome.getDataChrom().equalsIgnoreCase("chrx"))){
+                                                       throw new PedFileException("Missing allele on non X-chromosome data");
+                                                   }else{
+                                                       byteDataU[index] = byteDataT[index];
+                                                   }*/
+                                throw new PedFileException("Haploview does not currently support regions encompassing both\n"
+                                        + "pseudoautosomal and non-pseudoautosomal markers.");
+                            }else {
+                                throw new PedFileException("File format error.");
+                            }
+                        }
+                        index++;
+                    }
+                    if (strand.equals("c2")){
+                        for(int j=0; j < columns; j++){
+                            ind.addMarker(byteDataT[j], byteDataU[j]);
+                        }
+                    }else if (strand.equals("c1") && (ind.getGender() == Individual.MALE) &&
+                            (Chromosome.getDataChrom().equalsIgnoreCase("chrx"))){
+                        for(int j=0; j < columns; j++){
+                            ind.addMarker(byteDataT[j], byteDataT[j]);
+                        }
+                    }
+                }
+            }
+            hmpCon.disconnect();
+        }catch(IOException io){
+            throw new IOException("Could not connect to HapMap database.");
+        }
+    }
+
+/*    public void parseFastPhase(String[] info) throws IOException, PedFileException{
+        if (info[3].equals("")){
+            Chromosome.setDataChrom("none");
+        }else{
+            Chromosome.setDataChrom("chr" + info[3]);
+        }
+        Chromosome.setDataBuild("ncbi_b35");
+        BufferedReader reader;
+        InputStream inStream;
+
+        try {
+            URL inURL = new URL(info[0]);
+            inStream = inURL.openStream();
+        }catch (MalformedURLException mfe){
+            File inFile = new File(info[0]);
+            if (inFile.length() < 1){
+                throw new PedFileException("Genotype file is empty or non-existent: " + inFile.getName());
+            }
+            inStream = new FileInputStream(inFile);
+        }catch (IOException ioe){
+            throw new PedFileException("Could not connect to " + info[0]);
+        }
+
+        if (Options.getGzip()){
+            GZIPInputStream sampleInputStream = new GZIPInputStream(inStream);
+            reader = new BufferedReader(new InputStreamReader(sampleInputStream));
+        }else{
+            reader = new BufferedReader(new InputStreamReader(inStream));
+        }
+        this.allIndividuals = new Vector();
+        //TODO: put fastPHASE parsing code here
+*//*        byte[] byteDataT = new byte[0];
+        byte[] byteDataU = new byte[0];
+        char token;
+        int numMarkers;
+        int lineNumber = 0;
+        String line;
+        Individual ind = null;
+        while((line = reader.readLine())!=null){
+            StringTokenizer st = new StringTokenizer(line);
+            if (st.countTokens() != 5){
+                throw new PedFileException("Invalid file formatting on line " + lineNumber+1);
+            }
+            String markers = new String(st.nextToken());
+            st.nextToken(); //marker numbering
+            int gender = Integer.parseInt(st.nextToken());
+            String id = st.nextToken();
+            char strand = st.nextToken().charAt(0); // T or U
+            numMarkers = markers.length();
+            if (strand == 'T'){
+                ind = new Individual(numMarkers, true);
+                ind.setGender(gender);
+                ind.setIndividualID(id);
+                ind.setFamilyID("Bender");
+                ind.setDadID("0");
+                ind.setMomID("0");
+                byteDataT = new byte[numMarkers];
+
+                //check if the family exists already in the Hashtable
+                Family fam = (Family)this.families.get(ind.getFamilyID());
+                if(fam == null){
+                    //it doesnt exist, so create a new Family object
+                    fam = new Family(ind.getFamilyID());
+                }
+                fam.addMember(ind);
+                this.families.put(ind.getFamilyID(),fam);
+                this.allIndividuals.add(ind);
+            }else{
+                byteDataU = new byte[numMarkers];
+            }
+
+            int index = 0;
+            for (int i = 0; i < numMarkers; i++){
+                token = markers.charAt(i);
+                if (strand == 'T'){
+                    if (token == '1'){
+                        byteDataT[index] = 1;
+                    }else if (token == '2'){
+                        byteDataT[index] = 2;
+                    }else if (token == '3'){
+                        byteDataT[index] = 3;
+                    }else if (token == '4'){
+                        byteDataT[index] = 4;
+                    }else {
+                        throw new PedFileException("Invalid Allele: " + token);
+                    }
+                }else{
+                    if (token == '1'){
+                        byteDataU[index] = 1;
+                    }else if (token == '2'){
+                        byteDataU[index] = 2;
+                    }else if (token == '3'){
+                        byteDataU[index] = 3;
+                    }else if (token == '4'){
+                        byteDataU[index] = 4;
+                    }else {
+                        throw new PedFileException("Invalid Allele: " + token);
+                    }
+                }
+                index++;
+            }
+
+            if (strand == 'U'){
+                for(int j=0; j < numMarkers; j++){
+                    ind.addMarker(byteDataT[j], byteDataU[j]);
+                }
+            }
+            lineNumber++;
+        }*//*
+    }*/
+
+    public void parseHapsFile(Vector individs) throws PedFileException{
+        //This method is used to parse haps files which now go through similar processing to ped files.
+        String currentLine;
+        byte[] genos = new byte[0];
+        String ped, indiv;
+        int numLines = individs.size();
+        if (numLines == 0){
+            throw new PedFileException("Data format error: empty file");
+        }
+
+        Individual ind = null;
+        this.allIndividuals = new Vector();
+        int lineCount = 0;
+        int numTokens = 0;
+        Vector chromA = new Vector();
+        Vector chromB = new Vector();
+        boolean hapsEven = false;
+        boolean hapsError = false;
+
+        for (int i=0; i<numLines; i++){
+            lineCount++;
+            currentLine = (individs.get(i)).toString();
+            if (currentLine.length() == 0){
+                continue;
+            }
+            StringTokenizer st = new StringTokenizer(currentLine);
+            //first two tokens are expected to be ped, indiv
+            if (st.countTokens() >2){
+                ped = st.nextToken();
+                indiv = st.nextToken();
+            }else{
+                throw new PedFileException("Genotype file error:\nLine " + lineCount +
+                        " appears to have fewer than 3 columns.");
+            }
+            if(hapsEven){
+                ind = new Individual(st.countTokens(), false);
+                ind.setFamilyID(ped);
+                ind.setIndividualID(indiv);
+                ind.setDadID("");
+                ind.setMomID("");
+                ind.setGender(0);
+                ind.setAffectedStatus(0);
+            }
+
+            //all other tokens are loaded into a vector (they should all be genotypes)
+            genos = new byte[st.countTokens()];
+
+            int q = 0;
+
+            if (numTokens == 0){
+                numTokens = st.countTokens();
+            }
+            if (numTokens != st.countTokens()){
+                throw new PedFileException("Genotype file error:\nLine " + lineCount +
+                        " appears to have an incorrect number of entries");
+            }
+            //Allowed for A/C/G/T input in Haps files.
+            while (st.hasMoreTokens()){
+                String thisGenotype = (String)st.nextElement();
+                if (!hapsEven){
+                    chromA.add(thisGenotype);
+                }
+                else {
+                    chromB.add(thisGenotype);
+                }
+                if (thisGenotype.equalsIgnoreCase("h")) {
+                    genos[q] = 9;
+                }else if (thisGenotype.equalsIgnoreCase("A")){
+                    genos[q] = 1;
+                }else if (thisGenotype.equalsIgnoreCase("C")){
+                    genos[q] = 2;
+                }else if (thisGenotype.equalsIgnoreCase("G")){
+                    genos[q] = 3;
+                }else if (thisGenotype.equalsIgnoreCase("T")){
+                    genos[q] = 4;
+                }
+                else{
+                    try{
+                        genos[q] = Byte.parseByte(thisGenotype);
+                    }catch (NumberFormatException nfe){
+                        throw new PedFileException("Genotype file input error:\ngenotype value \""
+                                + thisGenotype + "\" on line " + lineCount + " not allowed.");
+                    }
+                }
+                //Allele values other then 0-4 or 9 generate exceptions.
+                if ((genos[q] < 0 || genos[q] > 4) && (genos[q] != 9)){
+                    throw new PedFileException("Genotype file input error:\ngenotype value \"" + genos[q] +
+                            "\" on line " + lineCount + " not allowed.");
+                }
+                q++;
+            }
+
+            if (hapsEven) {
+                for (int m=0; m<chromA.size(); m++){
+                    if (((String)chromA.get(m)).equalsIgnoreCase("h")){
+                        chromA.set(m, "9");
+                    }else if (((String)chromA.get(m)).equalsIgnoreCase("A")){
+                        chromA.set(m, "1");
+                        hapsError = !hapsError;
+                    }else if (((String)chromA.get(m)).equalsIgnoreCase("C")){
+                        chromA.set(m, "2");
+                        hapsError = !hapsError;
+                    }else if (((String)chromA.get(m)).equalsIgnoreCase("G")){
+                        chromA.set(m, "3");
+                        hapsError = !hapsError;
+                    }else if (((String)chromA.get(m)).equalsIgnoreCase("T")){
+                        chromA.set(m, "4");
+                        hapsError = !hapsError;
+                    }
+                    if (((String)chromB.get(m)).equalsIgnoreCase("h")){
+                        chromB.set(m, "9");
+                    }else if (((String)chromB.get(m)).equalsIgnoreCase("A")){
+                        chromB.set(m, "1");
+                        hapsError = !hapsError;
+                    }else if (((String)chromB.get(m)).equalsIgnoreCase("C")){
+                        chromB.set(m, "2");
+                        hapsError = !hapsError;
+                    }else if (((String)chromB.get(m)).equalsIgnoreCase("G")){
+                        chromB.set(m, "3");
+                        hapsError = !hapsError;
+                    }else if (((String)chromB.get(m)).equalsIgnoreCase("T")){
+                        chromB.set(m, "4");
+                        hapsError = !hapsError;
+                    }
+                    if (hapsError){
+                        throw new PedFileException("File input error: Individual " + ind.getFamilyID() + " strand " + ind.getIndividualID()  + ", marker " + (m+1)  +
+                                ".\nFor any marker, an individual's genotype must be only letters or only numbers.");
+                    }
+                    byte allele1 = Byte.parseByte(chromA.get(m).toString());
+                    byte allele2 = Byte.parseByte(chromB.get(m).toString());
+                    ind.addMarker(allele1, allele2);
+                }
+                //check if the family exists already in the Hashtable
+                Family fam = (Family)this.families.get(ind.getFamilyID());
+                if(fam == null){
+                    //it doesnt exist, so create a new Family object
+                    fam = new Family(ind.getFamilyID());
+                }
+
+                if (fam.getMembers().containsKey(ind.getIndividualID())){
+                    throw new PedFileException("Individual "+ind.getIndividualID()+" in family "+ ind.getFamilyID()+" appears more than once.");
+                }
+
+                fam.addMember(ind);
+                this.families.put(ind.getFamilyID(),fam);
+                this.allIndividuals.add(ind);
+                chromA = new Vector();
+                chromB = new Vector();
+            }
+            hapsEven = !hapsEven;
+        }
+        if (hapsEven){
+            //we're missing a line here
+            throw new PedFileException("Genotype file appears to have an odd number of lines.\n"+
+                    "Each individual is required to have two chromosomes");
+        }
+
+
+    }
+
+    public int[] checkGenotype(String allele) throws PedFileException{
+        //This method cleans up the genotype checking process for hap map and ped files & allows for both numerical and alphabetical input.
+        int[] genotype = new int[2];
+
+        if (allele.equalsIgnoreCase("N")){
+            genotype[0] = 0;
+        }else if (allele.equalsIgnoreCase("A")){
+            genotype[0] = 1;
+        }else if (allele.equalsIgnoreCase("C")){
+            genotype[0] = 2;
+        }else if (allele.equalsIgnoreCase("G")){
+            genotype[0] = 3;
+        }else if (allele.equalsIgnoreCase("T")){
+            genotype[0] = 4;
+        }else{
+            genotype[0] = Integer.parseInt(allele.trim());
+            genotype[1] = 1;
+        }
+
+        return genotype;
+    }
+
+    public Vector check() throws PedFileException{
+        //before we perform the check we want to prune out individuals with too much missing data
+        //or trios which contain individuals with too much missing data
+
+        Iterator fitr = families.values().iterator();
+        Vector useable = new Vector();
+        while (fitr.hasNext()){
+            Family curFam = (Family) fitr.next();
+            Enumeration indIDEnum = curFam.getMemberList();
+            Vector victor = new Vector();
+            while (indIDEnum.hasMoreElements()){
+                victor.add(curFam.getMember((String) indIDEnum.nextElement()));
+            }
+
+            PedParser pp = new PedParser();
+            try {
+                SimpleGraph sg = pp.buildGraph(victor, Options.getMissingThreshold());
+                Vector indStrings = pp.parsePed(sg);
+                if (indStrings != null){
+                    Iterator sitr = indStrings.iterator();
+                    while (sitr.hasNext()){
+                        useable.add(curFam.getMember((String)sitr.next()));
+                    }
+                }
+            }catch (PedigreeException pe){
+                String pem = pe.getMessage();
+                if (pem.indexOf("one parent") != -1){
+                    indIDEnum = curFam.getMemberList();
+                    while (indIDEnum.hasMoreElements()){
+                        curFam.getMember((String) indIDEnum.nextElement()).setReasonImAxed(pem);
+                    }
+                }else{
+                    throw new PedFileException(pem + "\nin family " + curFam.getFamilyName());
+                }
+            }
+        }
+
+        unrelatedIndividuals = useable;
+
+        Vector indList = (Vector)allIndividuals.clone();
+        Individual currentInd;
+        Family currentFamily;
+
+        //deal with individuals who are missing too much data
+        for(int x=0; x < indList.size(); x++){
+            currentInd = (Individual)indList.elementAt(x);
+            currentFamily = getFamily(currentInd.getFamilyID());
+
+            if (currentInd.getGenoPC() < 1 - Options.getMissingThreshold()){
+                allIndividuals.removeElement(currentInd);
+                axedPeople.add(currentInd);
+                currentInd.setReasonImAxed("% Genotypes: " + new Double(currentInd.getGenoPC()*100).intValue());
+                currentFamily.removeMember(currentInd.getIndividualID());
+                if (currentFamily.getNumMembers() == 0){
+                    //if everyone in a family is gone, we remove it from the list
+                    families.remove(currentInd.getFamilyID());
+                }
+            }else if (!useable.contains(currentInd)){
+                axedPeople.add(currentInd);
+                if (currentInd.getReasonImAxed() == null){
+                    currentInd.setReasonImAxed("Not a member of maximum unrelated subset.");
+                }
+            }
+        }
+        if (useable.size() == 0){
+            //todo: this should be more specific about the problems.
+            throw new PedFileException("File contains zero valid individuals.");
+        }
+
+        setMendelsExist(false);
+        CheckData cd = new CheckData(this);
+        Vector results = cd.check();
+        this.results = results;
+        return results;
+    }
+
+    public String[][] getHMInfo() {
+        return hminfo;
+    }
+
+    public Vector getResults() {
+        return results;
+    }
+
+    public void setResults(Vector res){
+        results = res;
+    }
+
+    public Vector getAxedPeople() {
+        return axedPeople;
+    }
+
+    public boolean isBogusParents() {
+        return bogusParents;
+    }
+
+    public Vector getTableData(){
+        Vector tableData = new Vector();
+        int numResults = results.size();
+        markerRatings = new int[numResults];
+        dups = new int[numResults];
+        for (int i = 0; i < numResults; i++){
+            Vector tempVect = new Vector();
+            MarkerResult currentResult = (MarkerResult)results.get(i);
+            tempVect.add(new Integer(i+1));
+            if (Chromosome.getUnfilteredMarker(0).getName() != null){
+                tempVect.add(Chromosome.getUnfilteredMarker(i).getDisplayName());
+                tempVect.add(new Long(Chromosome.getUnfilteredMarker(i).getPosition()));
+            }
+            tempVect.add(new Double(currentResult.getObsHet()));
+            tempVect.add(new Double(currentResult.getPredHet()));
+            tempVect.add(new Double(currentResult.getHWpvalue()));
+            tempVect.add(new Double(currentResult.getGenoPercent()));
+            tempVect.add(new Integer(currentResult.getFamTrioNum()));
+            tempVect.add(new Integer(currentResult.getMendErrNum()));
+            tempVect.add(new Double(currentResult.getMAF()));
+            tempVect.add(currentResult.getMajorAllele() + ":" + currentResult.getMinorAllele());
+
+            int dupStatus = Chromosome.getUnfilteredMarker(i).getDupStatus();
+            if ((currentResult.getRating() > 0 && dupStatus != 2) ||
+                    isWhiteListed(Chromosome.getUnfilteredMarker(i))){
+                tempVect.add(new Boolean(true));
+            }else{
+                tempVect.add(new Boolean(false));
+            }
+
+            //these values are never displayed, just kept for bookkeeping
+            markerRatings[i] = currentResult.getRating();
+            dups[i] = dupStatus;
+
+            tableData.add(tempVect.clone());
+        }
+
+        return tableData;
+    }
+
+    public int[] getMarkerRatings(){
+        return markerRatings;
+    }
+
+    public int[] getDups(){
+        return dups;
+    }
+
+    public Vector getColumnNames() {
+        Vector c = new Vector();
+        c = new Vector();
+        c.add("#");
+        if (Chromosome.getUnfilteredMarker(0).getName() != null){
+            c.add("Name");
+            c.add("Position");
+        }
+        c.add("ObsHET");
+        c.add("PredHET");
+        c.add("HWpval");
+        c.add("%Geno");
+        c.add("FamTrio");
+        c.add("MendErr");
+        c.add("MAF");
+        c.add("Alleles");
+        c.add("Rating");
+        return c;
+    }
+
+    public void saveCheckDataToText(File outfile) throws IOException {
+        FileWriter checkWriter = null;
+        if (outfile != null){
+            checkWriter = new FileWriter(outfile);
+        }else{
+            throw new IOException("Error saving checkdata to file.");
+        }
+
+        Vector names = getColumnNames();
+        int numCols = names.size();
+        StringBuffer header = new StringBuffer();
+        for (int i = 0; i < numCols; i++){
+            header.append(names.get(i)).append("\t");
+        }
+        header.append("\n");
+        checkWriter.write(header.toString());
+
+        Vector tableData = getTableData();
+        for (int i = 0; i < tableData.size(); i++){
+            StringBuffer sb = new StringBuffer();
+            Vector row = (Vector)tableData.get(i);
+            //don't print the true/false vals in last column
+            for (int j = 0; j < numCols-1; j++){
+                sb.append(row.get(j)).append("\t");
+            }
+            //print BAD if last column is false
+            if (((Boolean)row.get(numCols-1)).booleanValue()){
+                sb.append("\n");
+            }else{
+                sb.append("BAD\n");
+            }
+            checkWriter.write(sb.toString());
+        }
+
+        checkWriter.close();
+    }
+
+    public void setWhiteList(HashSet whiteListedCustomMarkers) {
+        whitelist = whiteListedCustomMarkers;
+    }
+
+    public boolean isWhiteListed(SNP snp){
+        return whitelist.contains(snp);
+    }
+
+    public Vector getHaploidHets() {
+        return haploidHets;
+    }
+
+    public void addHaploidHet(String haploid) {
+        if(haploidHets != null){
+            haploidHets.add(haploid);
+        }else{
+            haploidHets = new Vector();
+            haploidHets.add(haploid);
+        }
+    }
+
+    public boolean getMendelsExist(){
+        return mendels;
+    }
+
+    public void setMendelsExist(boolean mendel){
+        mendels = mendel;
+    }
+}
+
+
diff --git a/edu/mit/wi/pedfile/PedFileException.java b/edu/mit/wi/pedfile/PedFileException.java
new file mode 100755
index 0000000..4df3481
--- /dev/null
+++ b/edu/mit/wi/pedfile/PedFileException.java
@@ -0,0 +1,8 @@
+package edu.mit.wi.pedfile;
+
+public class PedFileException extends Exception{
+    static final long serialVersionUID = -6857933303437532504L;
+    PedFileException(String s){
+        super(s);
+    }
+}
diff --git a/edu/mit/wi/pedparser/Individual.java b/edu/mit/wi/pedparser/Individual.java
new file mode 100755
index 0000000..f8aeb33
--- /dev/null
+++ b/edu/mit/wi/pedparser/Individual.java
@@ -0,0 +1,49 @@
+package edu.mit.wi.pedparser;
+
+import java.util.HashSet;
+
+public class Individual {
+
+    HashSet kids, spouses;
+    Individual mom, dad;
+    String id;
+    boolean missing;
+    double genotypePercent;
+    int gender;
+
+    Individual(String i, boolean m, int g, double gpct){
+        id = i;
+        missing = m;
+        gender = g;
+        genotypePercent = gpct;
+
+        kids = new HashSet();
+        spouses = new HashSet();
+    }
+
+    void addMom(Individual i) throws PedigreeException{
+        if (mom != null){
+            throw new PedigreeException("Individual " + id + " has more than one mother.");
+        }
+        mom = i;
+    }
+
+    void addDad(Individual i) throws PedigreeException{
+        if (dad != null){
+            throw new PedigreeException("Individual " + id + " has more than one mother.");
+        }
+        dad = i;
+    }
+
+    void addKid(Individual i){
+        kids.add(i);
+    }
+
+    void addSpouse(Individual i){
+        spouses.add(i);
+    }
+
+    public String toString(){
+        return id;
+    }
+}
diff --git a/edu/mit/wi/pedparser/PedEdge.java b/edu/mit/wi/pedparser/PedEdge.java
new file mode 100755
index 0000000..f015995
--- /dev/null
+++ b/edu/mit/wi/pedparser/PedEdge.java
@@ -0,0 +1,26 @@
+package edu.mit.wi.pedparser;
+
+import org._3pq.jgrapht.edge.UndirectedEdge;
+
+public class PedEdge extends UndirectedEdge{
+
+    private Individual ind;
+
+    public PedEdge(Object o, Object o1, Individual i){
+        super (o, o1);
+        ind = i;
+    }
+
+    public PedEdge(Object o, Object o1) {
+        super(o, o1);
+    }
+
+    public Individual getInd() {
+        return ind;
+    }
+
+    public void setInd(Individual ind) {
+        this.ind = ind;
+    }
+
+}
diff --git a/edu/mit/wi/pedparser/PedParser.java b/edu/mit/wi/pedparser/PedParser.java
new file mode 100755
index 0000000..c6aa340
--- /dev/null
+++ b/edu/mit/wi/pedparser/PedParser.java
@@ -0,0 +1,864 @@
+package edu.mit.wi.pedparser;
+
+import org._3pq.jgrapht.graph.SimpleGraph;
+import org._3pq.jgrapht.Graph;
+import org._3pq.jgrapht.UndirectedGraph;
+import org._3pq.jgrapht.alg.ConnectivityInspector;
+
+import java.util.*;
+
+public class
+        PedParser {
+
+    double TRIO_SCORE = 2.001;
+    double TRIO_DIFFERENTIAL = TRIO_SCORE - 1;
+
+    public SimpleGraph buildGraph(Vector pedFileInds, double missingThresh) throws PedigreeException{
+        SimpleGraph theGraph = new SimpleGraph();
+
+        Hashtable indsByID = new Hashtable();
+        Hashtable momsByID = new Hashtable();
+        Hashtable dadsByID = new Hashtable();
+
+        for (int i = 0; i < pedFileInds.size(); i++) {
+            edu.mit.wi.pedfile.Individual ind = (edu.mit.wi.pedfile.Individual) pedFileInds.elementAt(i);
+            String id = ind.getIndividualID();
+            String dad = ind.getDadID();
+            String mom = ind.getMomID();
+            if (id.equals(dad) || id.equals(mom)){
+                throw new PedigreeException(id + " is his own parent");
+            }
+            int gender = ind.getGender();
+            double genopc = ind.getGenoPC();
+            boolean missing = false;
+            if (genopc < 1-missingThresh){
+                missing = true;
+            }
+            Individual newInd = new Individual(id,missing,gender,genopc);
+            indsByID.put(id,newInd);
+            momsByID.put(id,mom);
+            dadsByID.put(id,dad);
+        }
+
+
+        Iterator itr = indsByID.values().iterator();
+        while (itr.hasNext()){
+            Individual ind = (Individual) itr.next();
+            Individual mom = (Individual) indsByID.get(momsByID.get(ind.id));
+            Individual dad = (Individual) indsByID.get(dadsByID.get(ind.id));
+            if (mom != null ^ dad != null){
+                throw new PedigreeException("Individual " + ind +" has only one parent.");
+            }
+            if (dad != null && mom != null){
+                ind.addDad(dad);
+                dad.addKid(ind);
+
+                ind.addMom(mom);
+                mom.addKid(ind);
+
+                mom.addSpouse(dad);
+                dad.addSpouse(mom);
+            }
+        }
+
+        Hashtable nodesByKids = new Hashtable();
+        Hashtable nodesByParents = new Hashtable();
+        HashSet usedPeople = new HashSet();
+        itr = indsByID.values().iterator();
+        while (itr.hasNext()){
+            Individual ind = (Individual) itr.next();
+            Vector parentsToAdd = new Vector();
+            Vector kidsToAdd = new Vector();
+            if(!nodesByKids.containsKey(ind) && (ind.dad != null || ind.mom != null)) {
+                PedTreeNode ptn = new PedTreeNode();
+                kidsToAdd.add(ind);
+                if(ind.mom != null) {
+                    parentsToAdd.add(ind.mom);
+                }
+                if(ind.dad != null) {
+                    parentsToAdd.add(ind.dad);
+                }
+              
+                while(parentsToAdd.size() > 0) {
+                    Individual next = (Individual) parentsToAdd.remove(parentsToAdd.size()-1);
+                    Iterator sitr = next.spouses.iterator();
+                    while(sitr.hasNext()) {
+                        Individual s = (Individual) sitr.next();
+                        if(!(ptn.parents.contains(s))) {
+                            parentsToAdd.add(s);
+                        }
+                    }
+                    ptn.addParent(next);
+                    usedPeople.add(next);
+                    nodesByParents.put(next,ptn);
+                    kidsToAdd.addAll(next.kids);
+                }
+
+                ptn.addChildren(kidsToAdd);
+
+                for(int i=0;i<kidsToAdd.size();i++) {
+                    nodesByKids.put(kidsToAdd.get(i),ptn);
+                }
+
+                usedPeople.addAll(kidsToAdd);
+                theGraph.addVertex(ptn);
+            }
+        }
+
+        itr = indsByID.values().iterator();
+
+        while(itr.hasNext()) {
+            Individual ind = (Individual) itr.next();
+            if(nodesByKids.containsKey(ind) && nodesByParents.containsKey(ind)) {
+                PedEdge theEdge = new PedEdge(nodesByKids.get(ind), nodesByParents.get(ind),ind);
+                theGraph.addEdge(theEdge);
+            }
+        }
+
+        //now add any floating singletons
+        itr = indsByID.values().iterator();
+        while (itr.hasNext()){
+            Individual ind = (Individual) itr.next();
+            if (!usedPeople.contains(ind)){
+                theGraph.addVertex(ind);
+            }
+        }
+
+
+
+        return theGraph;
+    }
+
+    public Vector parsePed(UndirectedGraph g) throws PedigreeException{
+        //takes any graph and submits all connected subgraphs to parseConnectedGraph
+        //returns the sum of those results
+        Vector retVec = new Vector();
+        ConnectivityInspector inspectorGadget = new ConnectivityInspector(g);
+        Iterator itr = inspectorGadget.connectedSets().iterator();
+        while (itr.hasNext()){
+            SimpleGraph subGraph = new SimpleGraph();
+            Iterator vitr = ((Set)itr.next()).iterator();
+            HashSet edgeSet = new HashSet();
+            while (vitr.hasNext()){
+                Object o = vitr.next();
+                subGraph.addVertex(o);
+                edgeSet.addAll(g.edgesOf(o));
+            }
+            subGraph.addAllEdges(edgeSet);
+            retVec.addAll(parseConnectedGraph(subGraph));
+        }
+
+        return retVec;
+    }
+
+    private Vector parseConnectedGraph(UndirectedGraph g) throws PedigreeException{
+        Iterator itr;
+
+        ConnectivityInspector inspectorGadget = new ConnectivityInspector(g);
+        if (!inspectorGadget.isGraphConnected()){
+            throw new PedigreeException("Graph not connected.");
+        }
+
+        //this deals with floating singletons
+        if (g.vertexSet().size() == 1){
+            itr = g.vertexSet().iterator();
+            Object o = itr.next();
+            if (o instanceof Individual){
+                Vector v = new Vector();
+                if (!((Individual)o).missing){
+                    v.add(((Individual)o).id);
+                }
+                return v;
+            }
+        }
+
+        itr = g.vertexSet().iterator();
+        while (itr.hasNext()){
+            PedTreeNode starter = (PedTreeNode) itr.next();
+            List edges = g.edgesOf(starter);
+            if (starter.parents.size() + starter.kids.size() == edges.size()){
+                //we can't use this node because all its individuals are edges.
+                continue;
+            }
+            PedTreeNode fakeVertex = new PedTreeNode();
+            Iterator litr = edges.iterator();
+            HashSet edgeIndividuals = new HashSet();
+            while (litr.hasNext()){
+                edgeIndividuals.add(((PedEdge)litr.next()).getInd());
+            }
+            HashSet allIndividuals = new HashSet();
+            allIndividuals.addAll(starter.parents);
+            allIndividuals.addAll(starter.kids);
+            allIndividuals.removeAll(edgeIndividuals);
+            Iterator fitr = allIndividuals.iterator();
+            if (fitr.hasNext()){
+                Individual i = (Individual) fitr.next();
+                //if we select this person to be our fake edge he must not be an edge.
+                PedEdge e = new PedEdge(fakeVertex, starter, i);
+                ScoreData sd = scoreEdge(e,fakeVertex,g);
+                Vector retVec = new Vector();
+                Iterator sitr = sd.lineageSingletons.iterator();
+                while (sitr.hasNext()){
+                    retVec.add(((Individual)sitr.next()).id);
+                }
+                Iterator titr = sd.lineageTrios.iterator();
+                while (titr.hasNext()){
+                    Trio t = (Trio) titr.next();
+                    retVec.add(t.mom.id);
+                    retVec.add(t.dad.id);
+                    retVec.add(t.kid.id);
+                }
+                return retVec;
+            }
+        }
+        return null;
+    }
+
+    ScoreData scoreEdge(PedEdge theEdge, PedTreeNode source, Graph g) throws PedigreeException{
+        Hashtable scoresByEdges = new Hashtable();
+        Hashtable edgesByInds = new Hashtable();
+
+        PedTreeNode thisVertex = (PedTreeNode) theEdge.oppositeVertex(source);
+        if (thisVertex.visited){
+            throw new PedigreeException("There appears to be a marriage loop");
+        }else{
+            thisVertex.visited = true;
+        }
+        Individual edgeInd = theEdge.getInd();
+        List edges = g.edgesOf(thisVertex);
+        Iterator eitr = edges.iterator();
+        while (eitr.hasNext()){
+            PedEdge ne = (PedEdge) eitr.next();
+            if (ne != theEdge){
+                scoresByEdges.put(ne, scoreEdge(ne, thisVertex, g));
+                edgesByInds.put(ne.getInd(),ne);
+            }
+        }
+
+        //this HashSet contains the Individuals who aren't available to use
+        //in this node, since we choose to use the 2-score of their edge
+        //HashSet lineageEdges = resolveEdges(thisVertex,scoresByEdges,edgesByInds,new Vector());
+
+        Vector outgoingLineagePeople = new Vector();
+        if (thisVertex.parents.contains(edgeInd)){
+            outgoingLineagePeople.addAll(edgeInd.kids);
+        }else{
+            outgoingLineagePeople.add(edgeInd);
+            if(edgeInd.dad != null) {
+                outgoingLineagePeople.add(edgeInd.dad);
+                outgoingLineagePeople.addAll(edgeInd.dad.kids);
+            }
+            if(edgeInd.mom != null) {
+                outgoingLineagePeople.addAll(edgeInd.mom.kids);
+                outgoingLineagePeople.add(edgeInd.mom);
+            }
+
+        }
+
+        //start scoring
+        //first, build the zeroscore by parsing the node with only people unrelated to edgeInd
+
+        ScoreData sd0 = new ScoreData();
+        if (thisVertex.parents.contains(edgeInd)){
+            HashSet kids = (HashSet)thisVertex.kids.clone();
+            HashSet parents = (HashSet)thisVertex.parents.clone();
+            parents.remove(edgeInd);
+            kids.removeAll(edgeInd.kids);
+            Iterator itr = edgeInd.spouses.iterator();
+            while (itr.hasNext()){
+                Individual s = (Individual) itr.next();
+                sd0.merge(scoreNodeRemainder(s,kids,parents,scoresByEdges,edgesByInds,edgeInd),true,false);
+            }
+        } else{
+            HashSet kids = (HashSet)thisVertex.kids.clone();
+            HashSet parents = (HashSet)thisVertex.parents.clone();
+            kids.removeAll(edgeInd.mom.kids);
+            kids.removeAll(edgeInd.dad.kids);
+            parents.remove(edgeInd.mom);
+            parents.remove(edgeInd.dad);
+            Iterator itr = edgeInd.mom.spouses.iterator();
+            while (itr.hasNext()){
+                Individual s = (Individual) itr.next();
+                if (s != edgeInd.dad){
+                    sd0.merge(scoreNodeRemainder(s,kids,parents,scoresByEdges,edgesByInds,edgeInd),true,false);
+                }
+            }
+            itr = edgeInd.dad.spouses.iterator();
+            while (itr.hasNext()){
+                Individual s = (Individual) itr.next();
+                if (s != edgeInd.mom){
+                    sd0.merge(scoreNodeRemainder(s,kids,parents,scoresByEdges,edgesByInds,edgeInd),true,false);
+                }
+            }
+        }
+
+        //now, parse the node allowing all members
+        Individual starter = null;
+
+
+        if(thisVertex.parents.contains(edgeInd)) {
+            starter = edgeInd;
+        } else {
+            starter = edgeInd.mom;
+        }
+
+        HashSet kids = (HashSet)thisVertex.kids.clone();
+        HashSet parents = (HashSet)thisVertex.parents.clone();
+        ScoreData sd2 = scoreNodeRemainder(starter,kids,parents,scoresByEdges,edgesByInds,edgeInd);
+
+        sd2.indyTrios = sd0.indyTrios;
+        sd2.indySingletons = sd0.indySingletons;
+        Iterator itr = thisVertex.getRelatedMembers(edgeInd).iterator();
+        while (itr.hasNext()){
+            Individual i = (Individual) itr.next();
+            if (i != edgeInd){
+                if (edgesByInds.containsKey(i)){
+                    ScoreData sd = (ScoreData)scoresByEdges.get(edgesByInds.get(i));
+                    sd2.indyTrios.addAll(sd.indyTrios);
+                    sd2.indySingletons.addAll(sd.indySingletons);
+                }
+            }
+        }
+
+        return sd2;
+    }
+
+    ScoreData scoreNodeRemainder (Individual seed, HashSet kids, HashSet parents,
+                                  Hashtable scoresByEdges, Hashtable edgesByInds, Individual edgeInd){
+
+        Hashtable scores = new Hashtable();
+        Trio curBestTrio = null;
+        Individual curBestSingleton = null;
+        boolean lookInNode = true;
+
+        Iterator sitr = seed.spouses.iterator();
+        while (sitr.hasNext()){
+            Individual currentSpouse = (Individual) sitr.next();
+            if (parents.contains(currentSpouse)){
+                HashSet p = (HashSet) parents.clone();
+                HashSet k = (HashSet) kids.clone();
+                p.remove(seed);
+                k.removeAll(seed.kids);
+                //we first score the spouse's stuff in the rest of the node
+                scores.put(currentSpouse, scoreNodeRemainder(currentSpouse,k,p,scoresByEdges,edgesByInds,edgeInd));
+            }
+        }
+
+        if (edgesByInds.containsKey(seed)){
+            scores.put(seed, (ScoreData) scoresByEdges.get(edgesByInds.get(seed)));
+            //if seed's edge is better than a trio
+            if (((ScoreData)scores.get(seed)).getScoreDifference() > TRIO_DIFFERENTIAL){
+                curBestSingleton = seed;
+                lookInNode = false;
+            }
+        }else{
+            HashSet tmp = new HashSet();
+            if (!seed.missing){
+                tmp.add(seed);
+            }
+            scores.put(seed,new ScoreData(new NodeScore(new Vector(), new HashSet()),
+                    new NodeScore(new Vector(), tmp)));
+        }
+        Iterator kitr = seed.kids.iterator();
+        while (kitr.hasNext()){
+            Individual kid = (Individual) kitr.next();
+            if (kids.contains(kid)){
+                if (edgesByInds.containsKey(kid)){
+                    scores.put(kid, (ScoreData) scoresByEdges.get(edgesByInds.get(kid)));
+                }else{
+                    HashSet tmp = new HashSet();
+                    if (!kid.missing){
+                        tmp.add(kid);
+                    }
+                    scores.put(kid,new ScoreData(new NodeScore(new Vector(), new HashSet()),
+                            new NodeScore(new Vector(), tmp)));
+                }
+            }
+        }
+
+        if(lookInNode) {
+            //find the best usage of this node.
+            ScoreData seScore = (ScoreData) scores.get(seed);
+            sitr = seed.spouses.iterator();
+            while (sitr.hasNext()){
+                Individual currentSpouse = (Individual) sitr.next();
+                if(parents.contains(currentSpouse)) {
+                    ScoreData spScore = (ScoreData) scores.get(currentSpouse);
+                    kitr = seed.kids.iterator();
+                    while (kitr.hasNext()){
+                        Individual curKid = (Individual) kitr.next();
+                        if ((curKid.mom == currentSpouse || curKid.dad == currentSpouse) &&
+                                !curKid.missing && !curKid.mom.missing && !curKid.dad.missing){
+                            Trio curTrio = new Trio(curKid, curKid.mom, curKid.dad);
+                            boolean addTrio = false;
+                            if (spScore.getScoreDifference() < TRIO_DIFFERENTIAL &&
+                                    seScore.getScoreDifference() < TRIO_DIFFERENTIAL){
+                                addTrio = true;
+                            }else if (spScore.getScoreDifference() == TRIO_DIFFERENTIAL){
+                                double compScore = curTrio.scaledGenoSum;
+                                if (curTrio.dad == seed){
+                                    compScore -= curTrio.dad.genotypePercent;
+                                }else{
+                                    compScore -= curTrio.mom.genotypePercent;
+                                }
+                                if (compScore > spScore.getGenoSumDifference()){
+                                    addTrio = true;
+                                }
+                            }else if (seScore.getScoreDifference() == TRIO_DIFFERENTIAL){
+                                double compScore = curTrio.scaledGenoSum;
+                                if (curTrio.dad == seed){
+                                    compScore -= curTrio.mom.genotypePercent;
+                                }else{
+                                    compScore -= curTrio.dad.genotypePercent;
+                                }
+                                if (compScore > seScore.getGenoSumDifference()){
+                                    addTrio = true;
+                                }
+                            }
+                            if(addTrio){
+                                if (curBestTrio != null){
+                                    ScoreData nssScore = null;
+                                    if (curBestTrio.dad == seed){
+                                        nssScore = (ScoreData)scores.get(curBestTrio.mom);
+                                    }else{
+                                        nssScore = (ScoreData)scores.get(curBestTrio.dad);
+                                    }
+                                    if(nssScore.getScoreDifference() > spScore.getScoreDifference()){
+                                        curBestTrio = curTrio;
+                                    }else if (nssScore.getScoreDifference() == spScore.getScoreDifference() &&
+                                            curTrio.scaledGenoSum > curBestTrio.scaledGenoSum) {
+                                        curBestTrio = curTrio;
+                                    }
+                                } else {
+                                    curBestTrio = curTrio;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+
+            //best kid of a missing non-seed parent
+            Individual curBestKid = null;
+            Individual curBestMPKid = null;
+            ScoreData curBestSD = new ScoreData();
+            double curBestGSDiff = 0;
+            ScoreData curBestMPSD = new ScoreData();
+
+            ScoreData seedScore = (ScoreData) scores.get(seed);
+            Iterator itr = seed.spouses.iterator();
+            while(itr.hasNext()) {
+                Individual curSpouse = (Individual) itr.next();
+                if(parents.contains(curSpouse)) {
+                    ScoreData curSpouseScore = (ScoreData) scores.get(curSpouse);
+                    kitr = curSpouse.kids.iterator();
+                    while(kitr.hasNext()) {
+                        Individual curKid = (Individual) kitr.next();
+                        if (curKid.dad == seed || curKid.mom == seed){
+                            ScoreData ckScore = (ScoreData)scores.get(curKid);
+                            if (curSpouseScore.getScoreDifference() == 0){
+                                //kids with a missing non-seed parent are preferred
+                                if (ckScore.getScoreDifference() > curBestMPSD.getScoreDifference()){
+                                    curBestMPKid = curKid;
+                                    curBestMPSD = ckScore;
+                                }else if (ckScore.getScoreDifference() == curBestMPSD.getScoreDifference()){
+                                    if (ckScore.getGenoSumDifference() > curBestMPSD.getGenoSumDifference()){
+                                        curBestMPKid = curKid;
+                                        curBestMPSD = ckScore;
+                                    }
+                                }
+                            }else{
+                                //otherwise find the best general kid who's score is better than the sum of his
+                                //parents or is equal to his parents but better genotyped.
+                                if ((ckScore.getScoreDifference() > curSpouseScore.getScoreDifference() + seedScore.getScoreDifference()) ||
+                                        (ckScore.getScoreDifference() == curSpouseScore.getScoreDifference() + seedScore.getScoreDifference() &&
+                                        ckScore.getGenoSumDifference() > curSpouseScore.getGenoSumDifference())){
+                                    if (ckScore.getScoreDifference() > curBestSD.getScoreDifference()){
+                                        curBestKid = curKid;
+                                        curBestSD = ckScore;
+                                        curBestGSDiff = ckScore.getGenoSumDifference() - curSpouseScore.getGenoSumDifference();
+                                    }else if(ckScore.getScoreDifference() == curBestSD.getScoreDifference()){
+                                        if(ckScore.getGenoSumDifference() - curSpouseScore.getGenoSumDifference() >
+                                                curBestGSDiff) {
+                                            //only get in here if ck is better in both score diff from non seed parent
+                                            //and in genoPC
+                                            curBestKid = curKid;
+                                            curBestSD = ckScore;
+                                            curBestGSDiff = ckScore.getGenoSumDifference() - curSpouseScore.getGenoSumDifference();
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            //set curBestKid to be the best all-around kid found above
+            if (curBestMPKid != null){
+                if (curBestKid != null){
+                    if (curBestMPSD.getScoreDifference() >= curBestSD.getScoreDifference()){
+                        curBestSingleton = curBestMPKid;
+                    }else{
+                        curBestSingleton = curBestKid;
+                    }
+                }else{
+                    curBestSingleton = curBestMPKid;
+                }
+            }else{
+                curBestSingleton = curBestKid;
+            }
+            //if seed is better than the best kid use seed instead.
+            if (curBestSingleton != null){
+                if (seedScore.getScoreDifference() > ((ScoreData)scores.get(curBestSingleton)).getScoreDifference()){
+                    curBestSingleton = seed;
+                }else if (seedScore.getScoreDifference() == ((ScoreData)scores.get(curBestSingleton)).getScoreDifference()){
+                    if (seedScore.getGenoSumDifference() > ((ScoreData)scores.get(curBestSingleton)).getGenoSumDifference()){
+                        curBestSingleton = seed;
+                    }
+                }
+            }else{
+                curBestSingleton = seed;
+            }
+
+            if (curBestSingleton != null && curBestTrio != null &&
+                    ((ScoreData)scores.get(curBestSingleton)).getScoreDifference() > 1){
+                //if the best singleton has a trio sized edge score compare and take based on genopc
+                double compScore = curBestTrio.scaledGenoSum;
+                if (curBestTrio.dad == seed){
+                    compScore -= curBestTrio.mom.genotypePercent;
+                }else{
+                    compScore -= curBestTrio.dad.genotypePercent;
+                }
+                if (compScore < ((ScoreData)scores.get(curBestSingleton)).getGenoSumDifference()){
+                    curBestTrio = null;
+                }
+            }
+        }
+
+        NodeScore zeroScore = new NodeScore(new Vector(), new HashSet());
+        Iterator itr = scores.keySet().iterator();
+        while (itr.hasNext()){
+            Individual s = (Individual) itr.next();
+            ScoreData sd = (ScoreData) scores.get(s);
+            if (parents.contains(s) && s != seed){
+                if (s == edgeInd.mom || s == edgeInd.dad){
+                    zeroScore.trios.addAll(sd.indyTrios);
+                    zeroScore.singletons.addAll(sd.indySingletons);
+                }else{
+                    zeroScore.trios.addAll(sd.lineageTrios);
+                    zeroScore.singletons.addAll(sd.lineageSingletons);
+                }
+            }else{
+                zeroScore.trios.addAll(sd.indyTrios);
+                zeroScore.singletons.addAll(sd.indySingletons);
+            }
+        }
+        NodeScore twoScore = new NodeScore(new Vector(), new HashSet());
+        twoScore.trios.addAll(zeroScore.trios);
+        twoScore.singletons.addAll(zeroScore.singletons);
+
+        if (curBestTrio != null){
+            //use the trio in the returned score.
+            if(curBestTrio.mom == edgeInd.mom && curBestTrio.dad == edgeInd.dad) {
+                //if edgeInd or his full sib is the kid in this trio, we remove non-seed parents
+                //0-score (so it gets added below)
+                ScoreData sdata = null;
+                if(seed == edgeInd.mom) {
+                    sdata = ((ScoreData)scores.get(edgeInd.dad));
+                } else {
+                    sdata = ((ScoreData)scores.get(edgeInd.mom));
+                }
+                twoScore.trios.removeAll(sdata.indyTrios);
+                twoScore.singletons.removeAll(sdata.indySingletons);
+            } else if(curBestTrio.mom == edgeInd.mom || curBestTrio.dad == edgeInd.dad) {
+                ScoreData sdata = null;
+                if(seed == edgeInd.mom && parents.contains(edgeInd.dad)) {
+                    sdata = ((ScoreData)scores.get(edgeInd.dad));
+                } else if(parents.contains(edgeInd.mom)){
+                    sdata = ((ScoreData)scores.get(edgeInd.mom));
+                }
+                if(sdata != null) {
+                    twoScore.trios.removeAll(sdata.indyTrios);
+                    twoScore.singletons.removeAll(sdata.indySingletons);
+                    twoScore.trios.addAll(sdata.lineageTrios);
+                    twoScore.singletons.addAll(sdata.lineageSingletons);
+                }
+            }
+
+
+            twoScore.trios.add(curBestTrio);
+            ScoreData sd = null;
+            if (curBestTrio.mom == seed){
+                sd = (ScoreData) scores.get(curBestTrio.dad);
+            }else {
+                sd = (ScoreData) scores.get(curBestTrio.mom);
+            }
+            if (sd != null){
+                twoScore.trios.removeAll(sd.lineageTrios);
+                twoScore.trios.addAll(sd.indyTrios);
+                twoScore.singletons.removeAll(sd.lineageSingletons);
+                twoScore.singletons.addAll(sd.indySingletons);
+            }
+
+
+
+        }else if (curBestSingleton != null){
+            ScoreData sd = (ScoreData) scores.get(curBestSingleton);
+            twoScore.trios.removeAll(sd.indyTrios);
+            twoScore.singletons.removeAll(sd.indySingletons);
+            twoScore.trios.addAll(sd.lineageTrios);
+            twoScore.singletons.addAll(sd.lineageSingletons);
+
+            if (curBestSingleton != seed){
+
+                if (!(curBestSingleton.mom == edgeInd.mom && curBestSingleton.dad == edgeInd.dad)){
+                    if (curBestSingleton.mom == seed){
+                        sd = (ScoreData) scores.get(curBestSingleton.dad);
+                    }else{
+                        sd = (ScoreData) scores.get(curBestSingleton.mom);
+                    }
+                    twoScore.trios.removeAll(sd.lineageTrios);
+                    twoScore.trios.addAll(sd.indyTrios);
+                    twoScore.singletons.removeAll(sd.lineageSingletons);
+                    twoScore.singletons.addAll(sd.indySingletons);
+
+                    ScoreData sdata = null;
+                    if(seed == edgeInd.mom && parents.contains(edgeInd.dad)) {
+                        sdata = (ScoreData) scores.get(edgeInd.dad);
+                    } else if(seed == edgeInd.dad && parents.contains(edgeInd.mom)) {
+                        sdata = (ScoreData) scores.get(edgeInd.mom);
+                    }
+                    if(sdata != null) {
+                        twoScore.trios.removeAll(sdata.indyTrios);
+                        twoScore.trios.addAll(sdata.lineageTrios);
+                        twoScore.singletons.removeAll(sdata.indySingletons);
+                        twoScore.singletons.addAll(sdata.lineageSingletons);
+                    }
+                }
+            } else {
+                ScoreData sdata = null;
+                if(seed == edgeInd.mom && parents.contains(edgeInd.dad)) {
+                    sdata = (ScoreData) scores.get(edgeInd.dad);
+                } else if(seed == edgeInd.dad && parents.contains(edgeInd.mom)) {
+                    sdata = (ScoreData) scores.get(edgeInd.mom);
+                }
+                if(sdata != null) {
+                    twoScore.trios.removeAll(sdata.indyTrios);
+                    twoScore.trios.addAll(sdata.lineageTrios);
+                    twoScore.singletons.removeAll(sdata.indySingletons);
+                    twoScore.singletons.addAll(sdata.lineageSingletons);
+                }
+            }
+        }
+
+        return new ScoreData(zeroScore,twoScore);
+    }
+
+    class Trio{
+        Individual kid, mom, dad;
+        double scaledGenoSum;
+
+        Trio (Individual k, Individual m, Individual d){
+            kid = k;
+            mom = m;
+            dad = d;
+            scaledGenoSum = 2*((k.genotypePercent + m.genotypePercent + d.genotypePercent)/3);
+        }
+
+        public String toString(){
+            return "k: " + kid + " m: " + mom + " d: " + dad;
+        }
+    }
+
+    class NodeScore{
+        Vector trios;
+        HashSet singletons;
+        NodeScore (Vector t, HashSet s){
+            trios = t;
+            singletons = s;
+        }
+    }
+
+    class ScoreData {
+
+        Vector lineageTrios, indyTrios;
+        HashSet lineageSingletons, indySingletons;
+
+        double getLineageScore(){
+            return ((lineageTrios.size()) * 2.001) + lineageSingletons.size();
+        }
+
+        double getIndyScore(){
+            return ((indyTrios.size())*2.001) + indySingletons.size();
+        }
+
+        double getScoreDifference(){
+            return getLineageScore() - getIndyScore();
+        }
+
+        public String toString(){
+            return "lin: " + getLineageScore() + " indy: " + getIndyScore();
+        }
+
+        public String membersToString(){
+            StringBuffer retStr = new StringBuffer();
+            if (lineageTrios.size() > 0){
+                retStr.append("LT: ");
+                for (int i = 0; i < lineageTrios.size(); i++) {
+                    Trio trio = (Trio) lineageTrios.elementAt(i);
+                    retStr.append("[");
+                    retStr.append(trio);
+                    retStr.append("] ");
+                }
+                retStr.append("\n");
+            }
+            if (lineageSingletons.size() > 0){
+                retStr.append("LS: ");
+                for (Iterator iterator = lineageSingletons.iterator(); iterator.hasNext();) {
+                    Object o = (Object) iterator.next();
+                    retStr.append(o);
+                    retStr.append(" ");
+                }
+                retStr.append("\n");
+            }
+
+            if (indyTrios.size() > 0){
+                retStr.append("IT: ");
+                for (int i = 0; i < indyTrios.size(); i++) {
+                    Trio trio = (Trio) indyTrios.elementAt(i);
+                    retStr.append("[");
+                    retStr.append(trio);
+                    retStr.append("] ");
+                }
+                retStr.append("\n");
+            }
+
+            if (indySingletons.size() > 0){
+                retStr.append("IS: ");
+                for (Iterator iterator = indySingletons.iterator(); iterator.hasNext();) {
+                    Object o = (Object) iterator.next();
+                    retStr.append(o);
+                    retStr.append(" ");
+                }
+                retStr.append("\n");
+            }
+
+            return retStr.toString();
+        }
+
+        public boolean hasSameLineageScore(ScoreData comp){
+            SdComparator sdc = new SdComparator();
+            int z = sdc.compare(this,comp);
+            if (z == 0){
+                return true;
+            }else{
+                return false;
+            }
+        }
+
+        ScoreData(NodeScore us0, NodeScore us2){
+            lineageTrios = us2.trios;
+            lineageSingletons = us2.singletons;
+
+            indyTrios = us0.trios;
+            indySingletons = us0.singletons;
+        }
+
+        ScoreData(){
+            lineageTrios = new Vector();
+            indyTrios = new Vector();
+
+            lineageSingletons = new HashSet();
+            indySingletons = new HashSet();
+        }
+
+        public void merge(ScoreData data, boolean fromLineage, boolean toLineage) {
+            if (fromLineage && toLineage){
+                lineageTrios.addAll(data.lineageTrios);
+                lineageSingletons.addAll(data.lineageSingletons);
+            }else if (fromLineage && !toLineage){
+                indyTrios.addAll(data.lineageTrios);
+                indySingletons.addAll(data.lineageSingletons);
+            }else if (!fromLineage && toLineage){
+                lineageTrios.addAll(data.indyTrios);
+                lineageSingletons.addAll(data.indySingletons);
+            }else{
+                indyTrios.addAll(data.indyTrios);
+                indySingletons.addAll(data.indySingletons);
+            }
+        }
+
+        public double getLineageGenoSum() {
+            double total = 0;
+
+            Iterator itr = lineageSingletons.iterator();
+            while(itr.hasNext()) {
+                total += ((Individual)itr.next()).genotypePercent;
+            }
+
+            itr = lineageTrios.iterator();
+            while(itr.hasNext()) {
+                total += (((Trio)itr.next()).scaledGenoSum);
+            }
+
+            return total;
+        }
+
+        public double getIndyGenoSum() {
+            double total = 0;
+
+            Iterator itr = indySingletons.iterator();
+            while(itr.hasNext()) {
+                total += ((Individual)itr.next()).genotypePercent;
+            }
+
+            itr = indyTrios.iterator();
+            while(itr.hasNext()) {
+                total += (((Trio)itr.next()).scaledGenoSum);
+            }
+
+            return total;
+        }
+
+        public double getGenoSumDifference() {
+            return getLineageGenoSum() - getIndyGenoSum();
+        }
+
+        class SdComparator implements Comparator{
+            public int compare(Object o1, Object o2) {
+                ScoreData sd1  = (ScoreData) o1;
+                ScoreData sd2 = (ScoreData) o2;
+
+                if (sd1.lineageSingletons.size() != sd2.lineageSingletons.size()){
+                    return -1;
+                }
+                Iterator itr = sd1.lineageSingletons.iterator();
+                while (itr.hasNext()){
+                    if (!sd2.lineageSingletons.contains(itr.next())){
+                        return -1;
+                    }
+                }
+
+                if (sd1.lineageTrios.size() != sd2.lineageTrios.size()){
+                    return -1;
+                }
+                for (int x = 0; x < sd1.lineageTrios.size(); x++){
+                    Trio t1 = (Trio) sd1.lineageTrios.elementAt(x);
+                    boolean found = false;
+                    for (int y = 0; y < sd2.lineageTrios.size(); y++){
+                        Trio t2 = (Trio) sd2.lineageTrios.elementAt(y);
+                        if (t1.mom == t2.mom && t1.dad == t2.dad && t1.kid == t2.kid){
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (!found){
+                        return -1;
+                    }
+                }
+                return 0;
+            }
+        }
+    }
+}
diff --git a/edu/mit/wi/pedparser/PedTreeNode.java b/edu/mit/wi/pedparser/PedTreeNode.java
new file mode 100755
index 0000000..4979cf3
--- /dev/null
+++ b/edu/mit/wi/pedparser/PedTreeNode.java
@@ -0,0 +1,79 @@
+package edu.mit.wi.pedparser;
+
+import java.util.Vector;
+import java.util.HashSet;
+import java.util.Iterator;
+
+public class PedTreeNode {
+
+    HashSet parents, kids;
+    boolean visited = false;
+
+    PedTreeNode (){
+        parents = new HashSet();
+        kids = new HashSet();
+    }
+
+    void addParent(Individual i){
+        parents.add(i);
+    }
+
+    void addChildren(Vector v){
+        kids.addAll(v);
+    }
+
+    HashSet getNodeMembers(){
+        HashSet h = new HashSet();
+        h.addAll(kids);
+        h.addAll(parents);
+
+        return h;
+    }
+
+    HashSet getRelatedMembers(Individual i)throws PedigreeException{
+        //if he's a kid in this node
+        HashSet h = new HashSet();
+        h.add(i);
+        if (kids.contains(i)){
+            if (i.mom != null){
+                h.addAll(i.mom.kids);
+                h.add(i.mom);
+            }
+            if (i.dad != null){
+                h.addAll(i.dad.kids);
+                h.add(i.dad);
+            }
+            return h;
+        }else if (parents.contains(i)){
+            h.addAll(i.kids);
+            return h;
+        }
+        throw new PedigreeException("Individual " + i + " is not in this node.");
+    }
+
+    HashSet getUnrelatedMembers(Individual i) throws PedigreeException{
+        HashSet h = getNodeMembers();
+        h.removeAll(getRelatedMembers(i));
+        return h;
+    }
+
+
+    public String toString(){
+        StringBuffer retStr = new StringBuffer("p: ");
+        Iterator pi = parents.iterator();
+        Iterator ki = kids.iterator();
+
+        while(pi.hasNext()){
+            retStr.append(((Individual)pi.next()).id);
+            retStr.append(" ");
+        }
+
+        retStr.append("k: ");
+        while (ki.hasNext()){
+            retStr.append(((Individual)ki.next()).id);
+            retStr.append(" ");
+        }
+
+        return retStr.toString();
+    }
+}
diff --git a/edu/mit/wi/pedparser/PedigreeException.java b/edu/mit/wi/pedparser/PedigreeException.java
new file mode 100755
index 0000000..e3796d8
--- /dev/null
+++ b/edu/mit/wi/pedparser/PedigreeException.java
@@ -0,0 +1,9 @@
+package edu.mit.wi.pedparser;
+
+public class PedigreeException extends Exception{
+    static final long serialVersionUID = 3581399139874892769L;
+    
+    public PedigreeException(String msg){
+        super(msg);
+    }
+}
diff --git a/edu/mit/wi/plink/AssociationResult.java b/edu/mit/wi/plink/AssociationResult.java
new file mode 100755
index 0000000..cf3a091
--- /dev/null
+++ b/edu/mit/wi/plink/AssociationResult.java
@@ -0,0 +1,39 @@
+package edu.mit.wi.plink;
+
+import java.util.Vector;
+
+public class AssociationResult {
+
+    private Marker thisMarker;
+    private Vector data;
+    private int index;
+
+    public AssociationResult(int i, Marker m, Vector values){
+        thisMarker = m;
+        data = values;
+        index = i;
+    }
+
+    public AssociationResult(int i, Vector values){
+        data = values;
+        index = i;
+    }
+
+    public int getIndex(){
+        return index;
+    }
+
+    public Marker getMarker(){
+        return thisMarker;
+    }
+
+    public Vector getValues(){
+        return data;
+    }
+
+    public void addValues(Vector values){
+        for (int i = 0; i < values.size(); i++){
+            data.add(values.get(i));
+        }
+    }
+}
diff --git a/edu/mit/wi/plink/Marker.java b/edu/mit/wi/plink/Marker.java
new file mode 100755
index 0000000..b398725
--- /dev/null
+++ b/edu/mit/wi/plink/Marker.java
@@ -0,0 +1,33 @@
+package edu.mit.wi.plink;
+
+
+public class Marker {
+
+    private short chromosome;
+    private String markerID;
+    private long position;
+    private static String[] chroms = {"","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","X","Y","XY","MT"};
+
+    public Marker(short chrom, String marker, long pos){
+        chromosome = chrom;
+        markerID = marker;
+        position = pos;
+    }
+
+    public String getChromosome(){
+        return chroms[chromosome];
+    }
+
+    public short getChromosomeIndex(){
+        return chromosome;
+    }
+
+    public String getMarkerID(){
+        return markerID;
+    }
+
+    public long getPosition(){
+        return position;
+    }
+
+}
\ No newline at end of file
diff --git a/edu/mit/wi/plink/Plink.java b/edu/mit/wi/plink/Plink.java
new file mode 100755
index 0000000..343303d
--- /dev/null
+++ b/edu/mit/wi/plink/Plink.java
@@ -0,0 +1,695 @@
+package edu.mit.wi.plink;
+
+import edu.mit.wi.haploview.StatFunctions;
+import edu.mit.wi.haploview.Util;
+
+import java.io.*;
+import java.util.Vector;
+import java.util.StringTokenizer;
+import java.util.Hashtable;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+
+/** @noinspection RedundantStringConstructorCall*/
+public class Plink {
+
+    private Vector results = null;
+    private Vector columns = null;
+    private Vector ignoredMarkers;
+    private boolean dupMarkers = false;
+
+    public void parseWGA(String wga, String map, boolean embed, String chromFilter, Vector columnFilter) throws PlinkException {
+        results = new Vector();
+        columns = new Vector();
+        columns.add("CHROM");
+        columns.add("MARKER");
+        columns.add("POSITION");
+
+        Hashtable markerHash = new Hashtable(1,1);
+        ignoredMarkers = new Vector();
+        short chrFilter = 0;
+        BufferedReader mapReader;
+        String mapName = null;
+        String wgaName = null;
+
+
+        if (chromFilter != null){
+            if (!chromFilter.equals("")){
+                chrFilter = Short.parseShort(chromFilter);
+            }
+        }
+
+        if (!embed){
+            try{
+                URL mapURL = new URL(map);
+                mapName = map;
+                mapReader = new BufferedReader(new InputStreamReader(mapURL.openStream()));
+            }catch(MalformedURLException mfe){
+                File mapFile = new File(map);
+                mapName = mapFile.getName();
+                try{
+                    if (mapFile.length() < 1){
+                        throw new PlinkException("Map file is empty or nonexistent: " + mapName);
+                    }
+                    mapReader = new BufferedReader(new FileReader(mapFile));
+                }catch(IOException ioe){
+                    throw new PlinkException("Error reading the map file: " + mapName);
+                }
+            }catch(IOException ioe){
+                throw new PlinkException("Could not connect to " + mapName);
+            }
+
+            String mapLine;
+            int line = 0;
+            int numColumns = -1;
+
+            try{
+                while((mapLine = mapReader.readLine())!=null) {
+                    if (mapLine.length() == 0){
+                        //skip blank lines
+                        line++;
+                        continue;
+                    }
+
+                    StringTokenizer st = new StringTokenizer(mapLine,"\t ");
+
+                    if (numColumns == -1){
+                        numColumns = st.countTokens();
+                    }else{
+                        if (numColumns != st.countTokens()){
+                            throw new PlinkException("Inconsistent number of map file columns on line " + (line+1));
+                        }
+                    }
+
+                    if (numColumns != 3 && numColumns != 4 && numColumns != 6){
+                        throw new PlinkException("Improper map file formatting on line " + (line+1));
+                    }
+
+                    String chrom = st.nextToken();
+                    short chr;
+                    if (chrom.equalsIgnoreCase("X")){
+                        chr = 23;
+                    }else if (chrom.equalsIgnoreCase("Y")){
+                        chr = 24;
+                    }else if (chrom.equalsIgnoreCase("XY")){
+                        chr = 25;
+                    }else if (chrom.equalsIgnoreCase("MT")){
+                        chr = 26;
+                    }else if (chrom.equals("-9")){
+                        chr = 0;
+                    }else{
+                        try{
+                            chr = Short.parseShort(chrom);
+                        }catch(NumberFormatException nfe){
+                            throw new PlinkException("Invalid chromosome specification on line " + (line +1) + ": " + chrom);
+                        }
+                        if (chr < 0 || chr > 26){
+                            throw new PlinkException("Invalid chromosome specification on line " + (line +1) + ": " + chrom);
+                        }
+                    }
+                    if (chrFilter > 0){
+                        if (chr != chrFilter){
+                            line++;
+                            continue;
+                        }
+                    }
+                    String marker = new String(st.nextToken());
+                    if (numColumns == 4 || numColumns == 6){
+                        //useless morgan distance
+                        st.nextToken();
+                    }
+                    String pos = st.nextToken();
+                    if (pos.startsWith("-")){
+                        line++;
+                        continue;
+                    }
+                    long position;
+                    try{
+                        position = Long.parseLong(pos);
+                    }catch(NumberFormatException nfe){
+                        throw new PlinkException("Invalid position specification on line " + (line +1) + ": " + pos);
+                    }
+
+                    Marker mark = new Marker(chr, marker, position);
+                    markerHash.put(mark.getMarkerID(), mark);
+                    line++;
+                }
+            }catch(IOException ioe){
+                throw new PlinkException("Error reading the map file: " + mapName);
+            }
+        }
+
+        BufferedReader wgaReader;
+
+        try{
+            URL wgaURL = new URL(wga);
+            wgaName = wga;
+            wgaReader = new BufferedReader(new InputStreamReader(wgaURL.openStream()));
+        }catch(MalformedURLException mfe){
+            File wgaFile = new File(wga);
+            wgaName = wgaFile.getName();
+            try{
+                if (wgaFile.length() < 1){
+                    throw new PlinkException("Results file is empty or nonexistent: " + wgaName);
+                }
+                wgaReader = new BufferedReader(new FileReader(wgaFile));
+            }catch(IOException ioe){
+                throw new PlinkException("Error reading the results file: " + wgaName);
+            }
+        }catch(IOException ioe){
+            throw new PlinkException("Could not connect to " + wgaName);
+        }
+
+        int colIndex = 0;
+        int markerColumn = -1;
+        int chromColumn = -1;
+        int positionColumn = -1;
+        String headerLine;
+        try{
+            headerLine = wgaReader.readLine();
+        }catch(IOException ioe){
+            throw new PlinkException("Error reading the results file: " + wgaName);
+        }
+        StringTokenizer headerSt = new StringTokenizer(headerLine);
+        boolean[] filteredColIndex = new boolean[headerSt.countTokens()];
+        int counter;
+        while (headerSt.hasMoreTokens()){
+            String column = headerSt.nextToken();
+            if (column.equalsIgnoreCase("SNP")){
+                markerColumn = colIndex;
+            }else if (column.equalsIgnoreCase("CHR")){
+                chromColumn = colIndex;
+            }else if (column.equalsIgnoreCase("POS") || column.equalsIgnoreCase("POSITION") || column.equalsIgnoreCase("BP")){
+                positionColumn = colIndex;
+            }else{
+                if (columnFilter != null){
+                    if (columnFilter.contains(column)){
+                        filteredColIndex[colIndex] = true;
+                    }else{
+                        if(columns.contains(column)){
+                            counter = 1;
+                            String dupColumn = column + "-" + counter;
+                            while (columns.contains(dupColumn)){
+                                counter++;
+                                dupColumn = column + "-" + counter;
+                            }
+                            columns.add(dupColumn);
+                        }else{
+                            columns.add(column);
+                        }
+                    }
+                }else{
+                    if(columns.contains(column)){
+                        counter = 1;
+                        String dupColumn = column + "-" + counter;
+                        while (columns.contains(dupColumn)){
+                            counter++;
+                            dupColumn = column + "-" + counter;
+                        }
+                        columns.add(dupColumn);
+                    }else{
+                        columns.add(column);
+                    }
+                }
+            }
+            colIndex++;
+        }
+
+        if (markerColumn == -1){
+            throw new PlinkException("Results file must contain a SNP column.");
+        }
+
+        if (embed){
+            if (chromColumn == -1 || positionColumn == -1){
+                throw new PlinkException("Results files with embedded map files must contain CHR and POS columns.");
+            }
+        }
+
+        String wgaLine;
+        int lineNumber = 0;
+        Hashtable markerDups = new Hashtable(1,1);
+
+        try{
+            while((wgaLine = wgaReader.readLine())!=null){
+                if (wgaLine.length() == 0){
+                    //skip blank lines
+                    continue;
+                }
+                int tokenNumber = 0;
+                StringTokenizer tokenizer = new StringTokenizer(wgaLine);
+                if (tokenizer.countTokens() != colIndex){
+                    throw new PlinkException("Inconsistent column number on line " + (lineNumber+1));
+                }
+                String marker = null;
+                String chromosome, position;
+                short chr = 0;
+                long pos = 0;
+                Vector values = new Vector();
+                while(tokenizer.hasMoreTokens()){
+                    if (tokenNumber == markerColumn){
+                        marker = new String(tokenizer.nextToken());
+                        if (markerDups.containsKey(marker)){
+                            dupMarkers = true;
+                        }else{
+                            markerDups.put(marker,"");
+                        }
+                    }else if (tokenNumber == chromColumn){
+                        //new String() stops StringTokenizer from wasting memory
+                        chromosome = new String(tokenizer.nextToken());
+                        if(chromosome.equalsIgnoreCase("X")){
+                            chr = 23;
+                        }else if(chromosome.equalsIgnoreCase("Y")){
+                            chr = 24;
+                        }else if(chromosome.equalsIgnoreCase("XY")){
+                            chr = 25;
+                        }else if(chromosome.equalsIgnoreCase("MT")){
+                            chr = 26;
+                        }else if (chromosome.equals("-9")){
+                            chr = 0;
+                        }else{
+                            try{
+                                chr = Short.parseShort(chromosome);
+                            }catch(NumberFormatException nfe){
+                                throw new PlinkException("Invalid chromosome specification on line " + (lineNumber +1) + ": " + chromosome);
+                            }
+                            if (chr < 0 || chr > 26){
+                                throw new PlinkException("Invalid chromosome specification on line " + (lineNumber +1) + ": " + chromosome);
+                            }
+                        }
+                    }else if (tokenNumber == positionColumn){
+                        if (embed){
+                            position = new String(tokenizer.nextToken());
+                            try{
+                                pos = Long.parseLong(position);
+                            }catch(NumberFormatException nfe){
+                                throw new PlinkException("Invalid position specification on line " + (lineNumber +1) + ": " + position);
+                            }
+                        }else{
+                            tokenizer.nextToken();
+                        }
+                    }else{
+                        if (filteredColIndex[tokenNumber]){
+                            tokenizer.nextToken();
+                        }else{
+                            String val = tokenizer.nextToken();
+                            if (val.equalsIgnoreCase("NA")){
+                                values.add(new Double(Double.NaN));
+                            }else{
+                                try{
+                                    values.add(new Double(val));
+                                }catch (NumberFormatException n){
+                                    values.add(new String(val));
+                                }
+                            }
+                        }
+                    }
+                    tokenNumber++;
+                }
+
+                if (chrFilter > 0){
+                    if (chr != chrFilter){
+                        lineNumber++;
+                        continue;
+                    }
+                }
+
+                Marker assocMarker;
+                if (!embed){
+                    assocMarker = (Marker)markerHash.get(marker);
+
+                    if (assocMarker == null){
+                        ignoredMarkers.add(marker);
+                        lineNumber++;
+                        continue;
+                    }else if (assocMarker.getChromosomeIndex() != chr && chromColumn != -1){
+                        throw new PlinkException("Incompatible chromosomes for marker " + marker +
+                                "\non line " + (lineNumber + 1) + " " + assocMarker.getChromosomeIndex() + " " + chr);
+                    }
+                }else{
+                    assocMarker = new Marker(chr,marker,pos);
+                }
+
+                AssociationResult result = new AssociationResult(lineNumber,assocMarker,values);
+                results.add(result);
+                lineNumber++;
+            }
+        }catch(IOException ioe){
+            throw new PlinkException("Error reading the results file: " + wgaName);
+        }
+    }
+
+    public void parseNonSNP(String name, Vector columnFilter) throws PlinkException{
+        results = new Vector();
+        columns = new Vector();
+
+        String wgaName = null;
+        BufferedReader wgaReader;
+        try{
+            URL wgaURL = new URL(name);
+            wgaName = name;
+            wgaReader = new BufferedReader(new InputStreamReader(wgaURL.openStream()));
+        }catch(MalformedURLException mfe){
+            File wgaFile = new File(name);
+            wgaName = wgaFile.getName();
+            try{
+                if (wgaFile.length() < 1){
+                    throw new PlinkException("Results file is empty or nonexistent: " + wgaName);
+                }
+                wgaReader = new BufferedReader(new FileReader(wgaFile));
+            }catch(IOException ioe){
+                throw new PlinkException("Error reading the results file: " + wgaName);
+            }
+        }catch(IOException ioe){
+            throw new PlinkException("Could not connect to " + wgaName);
+        }
+
+        int numColumns = 0;
+        String headerLine;
+        try{
+            headerLine = wgaReader.readLine();
+        }catch(IOException ioe){
+            throw new PlinkException("Error reading the results file: " + wgaName);
+        }
+        StringTokenizer headerSt = new StringTokenizer(headerLine);
+        boolean[] filteredColIndex = new boolean[headerSt.countTokens()];
+        while (headerSt.hasMoreTokens()){
+            String column = headerSt.nextToken();
+            if (columnFilter != null){
+                if (columnFilter.contains(column)){
+                    filteredColIndex[numColumns] = true;
+                    numColumns++;
+                }else{
+                    columns.add(column);
+                    numColumns++;
+                }
+            }else{
+                columns.add(column);
+                numColumns++;
+            }
+        }
+
+        String wgaLine;
+        int lineNumber = 0;
+        try{
+            while((wgaLine = wgaReader.readLine())!=null){
+                if (wgaLine.length() == 0){
+                    //skip blank lines
+                    continue;
+                }
+                int tokenNumber = 0;
+                StringTokenizer tokenizer = new StringTokenizer(wgaLine);
+                if (tokenizer.countTokens() != numColumns){
+                    throw new PlinkException("Inconsistent column number on line " + (lineNumber+1));
+                }
+                Vector values = new Vector();
+                while(tokenizer.hasMoreTokens()){
+                    if (filteredColIndex[tokenNumber]){
+                        tokenizer.nextToken();
+                    } else{
+                        String val = tokenizer.nextToken();
+                        if (val.equalsIgnoreCase("NA")){
+                            values.add(new Double(Double.NaN));
+                        }else{
+                            try{
+                                values.add(new Double(val));
+                            }catch (NumberFormatException n){
+                                values.add(new String(val));
+                            }
+                        }
+                    }
+                    tokenNumber++;
+                }
+
+                AssociationResult result = new AssociationResult(lineNumber,values);
+                results.add(result);
+                lineNumber++;
+            }
+        }catch(IOException ioe){
+            throw new PlinkException("Error reading the results file: " + wgaName);
+        }
+    }
+
+    public Vector getIgnoredMarkers() {
+        return ignoredMarkers;
+    }
+
+    public Vector parseMoreResults(String wga, Vector columnFilter) throws PlinkException {
+        File moreResultsFile = new File(wga);
+        Vector newColumns = new Vector();
+        ignoredMarkers = new Vector();
+        Vector duplicateColumns = new Vector();
+        boolean addColumns = false;
+        int numValues = columns.size()-3;
+
+
+        try{
+            if (moreResultsFile.length() < 1){
+                throw new PlinkException("Results file is empty or nonexistent: " + moreResultsFile.getName());
+            }
+
+            BufferedReader moreResultsReader = new BufferedReader(new FileReader(moreResultsFile));
+            int numColumns = 0;
+            int markerColumn = -1;
+            int chromColumn = -1;
+            int posColumn = -1;
+            String headerLine = moreResultsReader.readLine();
+            StringTokenizer headerSt = new StringTokenizer(headerLine);
+            boolean[] filteredColIndex = new boolean[headerSt.countTokens()];
+
+            while (headerSt.hasMoreTokens()){
+                String column = new String(headerSt.nextToken());
+
+                if (column.equalsIgnoreCase("SNP")){
+                    if (markerColumn != -1){
+                        throw new PlinkException("Results file contains more then one SNP column.");
+                    }
+                    markerColumn = numColumns;
+                    numColumns++;
+                }else if (column.equalsIgnoreCase("CHR")){
+                    chromColumn = numColumns;
+                    numColumns++;
+                }else if (column.equalsIgnoreCase("POS") || column.equalsIgnoreCase("POSITION") || column.equalsIgnoreCase("BP")){
+                    posColumn = numColumns;
+                    numColumns++;
+                }else{
+                    if (columnFilter != null){
+                        if (columnFilter.contains(column)){
+                            filteredColIndex[numColumns] = true;
+                            numColumns++;
+                        }else{
+                            if(columns.contains(column)){
+                                int counter = 1;
+                                String dupColumn = column + "-" + counter;
+                                while (columns.contains(dupColumn)){
+                                    counter++;
+                                    dupColumn = column + "-" + counter;
+                                }
+                                duplicateColumns.add(dupColumn);
+                                newColumns.add(dupColumn);
+                            }else{
+                                newColumns.add(column);
+                            }
+                            numColumns++;
+                        }
+                    }else{
+                        if(columns.contains(column)){
+                            int counter = 1;
+                            String dupColumn = column + "-" + counter;
+                            while (columns.contains(dupColumn)){
+                                counter++;
+                                dupColumn = column + "-" + counter;
+                            }
+                            duplicateColumns.add(dupColumn);
+                            newColumns.add(dupColumn);
+                        }else{
+                            newColumns.add(column);
+                        }
+                        numColumns++;
+                    }
+                }
+            }
+
+            if (markerColumn == -1){
+                throw new PlinkException("Results file must contain a SNP column.");
+            }
+
+            String wgaLine;
+            int lineNumber = 0;
+            Hashtable markerDups = new Hashtable(1,1);
+
+            Hashtable resultsHash = new Hashtable(1,1);
+            for (int i = 0; i < results.size(); i++){
+                String markerID = ((AssociationResult)results.get(i)).getMarker().getMarkerID();
+                resultsHash.put(markerID,new Integer(i));
+            }
+
+            while((wgaLine = moreResultsReader.readLine())!=null){
+                if (wgaLine.length() == 0){
+                    //skip blank lines
+                    continue;
+                }
+                int tokenNumber = 0;
+                StringTokenizer tokenizer = new StringTokenizer(wgaLine);
+                if (tokenizer.countTokens() != numColumns){
+                    throw new PlinkException("Inconsistent column number on line " + (lineNumber+1));
+                }
+                String marker = null;
+                Vector values = new Vector();
+                while(tokenizer.hasMoreTokens()){
+                    if (tokenNumber == markerColumn){
+                        marker = new String(tokenizer.nextToken());
+                        if (markerDups.containsKey(marker)){
+                            throw new PlinkException("Marker: " + marker +
+                                    " appears more than once.");
+                        }else{
+                            markerDups.put(marker,"");
+                        }
+                    }else if(tokenNumber == chromColumn || tokenNumber == posColumn){
+                        //we don't give a toss for the chromosome or position...
+                        tokenizer.nextToken();
+                    }else{
+                        if (filteredColIndex[tokenNumber]){
+                            tokenizer.nextToken();
+                        }else{
+                            String val = tokenizer.nextToken();
+                            if (val.equalsIgnoreCase("NA")){
+                                values.add(new Double(Double.NaN));
+                            }else{
+                                try{
+                                    values.add(new Double(val));
+                                }catch (NumberFormatException n){
+                                    values.add(new String(val));
+                                }
+                            }
+                        }
+                    }
+                    tokenNumber++;
+                }
+
+
+                AssociationResult currentResult;
+
+                if (resultsHash.containsKey(marker)){
+                    addColumns = true;
+                    currentResult = (AssociationResult)results.get(((Integer)resultsHash.get(marker)).intValue());
+                    if (currentResult.getValues().size() < numValues){
+                        int nullsToAdd = numValues - currentResult.getValues().size();
+                        Vector nullPads = new Vector();
+                        for (int i = 0; i < nullsToAdd; i++){
+                            nullPads.add(null);
+                        }
+                        currentResult.addValues(nullPads);
+                    }
+                    currentResult.addValues(values);
+                }else{
+                    ignoredMarkers.add(marker);
+                }
+
+                lineNumber++;
+            }
+        }catch(IOException ioe){
+            throw new PlinkException("Error reading the results file: " + moreResultsFile.getName());
+        }
+
+        if (addColumns){
+            for (int i = 0; i < newColumns.size(); i++){
+                columns.add(newColumns.get(i));
+            }
+        }else{
+            duplicateColumns = new Vector();
+        }
+        return duplicateColumns;
+    }
+
+    public void doFisherCombined(Vector cols) throws PlinkException{
+        int numValues = columns.size()-3;
+
+        for (int i = 0; i < results.size(); i ++){
+            AssociationResult currentResult = (AssociationResult)results.get(i);
+            Vector values = currentResult.getValues();
+            Vector pValues = new Vector();
+            Vector valuesToAdd = new Vector();
+            for (int j = 0; j < cols.size(); j++){
+                int value = ((Integer)cols.get(j)).intValue();
+                Double pv;
+                try{
+                    if (values.size() > value){
+                        if (values.get(value) != null){
+                            if (values.get(value) instanceof Double){
+                                if (!((values.get(value)).equals(new Double(Double.NaN)))){
+                                    pv = (Double)values.get(value);
+                                    pValues.add(pv);
+                                }
+                            }
+                        }
+                    }
+                }catch(NumberFormatException nfe){
+                    throw new PlinkException("One or more of the selected columns does not contain\n" +
+                            "properly formatted P-values.");
+                }
+            }
+            int numPvals = pValues.size();
+            double sumLns = 0;
+            for (int j = 0; j < numPvals; j++){
+                if (pValues.get(j) != null){
+                    sumLns += Math.log(((Double)pValues.get(j)).doubleValue());
+                }
+            }
+            double chisq = -2*sumLns;
+            if (chisq == 0){
+                valuesToAdd.add(new Double(1));
+            }else if (chisq > Double.MAX_VALUE){ //in case of infinite chisq due to pvalue of 0
+                valuesToAdd.add(new Double("1.0E-16"));
+            }else{
+                double df = 2*numPvals;
+                try{
+                    Double p;
+                    if (1-StatFunctions.pchisq(chisq,df) == 0){
+                        p = new Double("1.0E-16");
+                    }else{
+                        p = new Double(Util.formatPValue(1-StatFunctions.pchisq(chisq,df)));
+                    }
+                    valuesToAdd.add(p);
+                }catch(IllegalArgumentException iae){
+                    throw new PlinkException("One or more of the selected columns does not contain\n" +
+                            "properly formatted P-values.");
+                }
+            }
+            if (currentResult.getValues().size() < numValues){
+                int nullsToAdd = numValues - currentResult.getValues().size();
+                Vector nullPads = new Vector();
+                for (int j = 0; j < nullsToAdd; j++){
+                    nullPads.add(null);
+                }
+                currentResult.addValues(nullPads);
+            }
+            currentResult.addValues(valuesToAdd);
+        }
+        String column = "P_COMBINED";
+        if(columns.contains(column)){
+            int counter = 1;
+            String dupColumn = column + "-" + counter;
+            while (columns.contains(dupColumn)){
+                counter++;
+                dupColumn = column + "-" + counter;
+            }
+            columns.add(dupColumn);
+        }else{
+            columns.add(column);
+        }
+    }
+
+    public Vector getResults(){
+        return results;
+    }
+
+    public Vector getColumnNames(){
+        return columns;
+    }
+
+    public boolean getPlinkDups(){
+        return dupMarkers;
+    }
+
+}
\ No newline at end of file
diff --git a/edu/mit/wi/plink/PlinkException.java b/edu/mit/wi/plink/PlinkException.java
new file mode 100755
index 0000000..6206e8d
--- /dev/null
+++ b/edu/mit/wi/plink/PlinkException.java
@@ -0,0 +1,9 @@
+package edu.mit.wi.plink;
+
+
+public class PlinkException extends Exception{
+    static final long serialVersionUID = -1110988738928063776L;
+    PlinkException(String s){
+        super(s);
+    }
+}
\ No newline at end of file
diff --git a/edu/mit/wi/plink/PlinkGraph.java b/edu/mit/wi/plink/PlinkGraph.java
new file mode 100755
index 0000000..b2cfe8b
--- /dev/null
+++ b/edu/mit/wi/plink/PlinkGraph.java
@@ -0,0 +1,806 @@
+package edu.mit.wi.plink;
+
+import edu.mit.wi.haploview.Options;
+import edu.mit.wi.haploview.Constants;
+import edu.mit.wi.haploview.PlinkResultsPanel;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYDataset;
+import org.jfree.chart.*;
+import org.jfree.chart.labels.StandardXYToolTipGenerator;
+import org.jfree.chart.entity.EntityCollection;
+import org.jfree.chart.entity.XYItemEntity;
+import org.jfree.chart.entity.ChartEntity;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.renderer.xy.XYDotRenderer;
+import org.jfree.chart.renderer.xy.XYItemRendererState;
+import org.jfree.chart.plot.*;
+import org.jfree.ui.RefineryUtilities;
+import org.jfree.ui.RectangleEdge;
+import org.w3c.dom.DOMImplementation;
+import org.w3c.dom.Document;
+import org.apache.batik.svggen.SVGGraphics2D;
+import org.apache.batik.dom.GenericDOMImplementation;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.geom.Rectangle2D;
+import java.util.Hashtable;
+import java.util.Vector;
+import java.util.StringTokenizer;
+import java.io.*;
+
+
+public class PlinkGraph implements Constants, ChartMouseListener {
+    private JTable table;
+    private PlinkResultsPanel parent;
+    private PlinkTableModel plinkTableModel;
+
+    private Hashtable nonChrInfo;
+    private Hashtable[] chrInfo;
+    private int[] seriesKeys, thresholdSigns, thresholdAxes;
+    private JFrame plotFrame;
+    private int yPlotType, xPlotType, baseDotSize;
+    private double suggestive, significant;
+    private boolean threeSizes, chroms, useSig, useSug;
+
+    public PlinkGraph(String title, int yType, int yCol, int xType, int xCol, double sug, double sig, boolean suggest, boolean signif, int[] signs, int[] thresholds, int dotSize, int colorKey, boolean grid, File svgFile, int width, int height, JTable theTable, PlinkTableModel theModel, PlinkResultsPanel theParent){
+        yPlotType = yType;
+        xPlotType = xType;
+        thresholdSigns = signs;
+        thresholdAxes = thresholds;
+        baseDotSize = dotSize;
+        useSug = suggest;
+        useSig = signif;
+        threeSizes = (signs[0] == signs[1]) && (thresholds[0] == thresholds[1]) && useSug && useSig;
+        chroms = Options.getSNPBased() && xCol == 2;
+        table = theTable;
+        parent = theParent;
+        plinkTableModel = theModel;
+
+
+        final XYSeriesCollection dataSet;
+        if (chroms){
+            dataSet = makeChrDataSet(yCol);
+        }else{
+            dataSet = makeDataSet(yCol,xCol,colorKey);
+        }
+
+        if (dataSet == null){
+            return;
+        }
+
+        significant = sig;
+        suggestive = sug;
+
+        String rangeAxisName;
+        if (yCol == -1){
+            rangeAxisName = null;
+        }else{
+            if (yPlotType == UNTRANSFORMED_PLOT){
+                rangeAxisName = table.getColumnName(yCol);
+            }else{
+                rangeAxisName = PLOT_TYPES[yPlotType] + "(" + table.getColumnName(yCol) + ")";
+            }
+        }
+
+        String domainAxisName;
+        if (xCol == -1 || chroms){
+            domainAxisName = null;
+        }else{
+            if (xPlotType == UNTRANSFORMED_PLOT){
+                domainAxisName = table.getColumnName(xCol);
+            }else{
+                domainAxisName = PLOT_TYPES[xPlotType] + "(" + table.getColumnName(xCol) + ")";
+            }
+        }
+
+        boolean legend = false;
+        if (chroms || (colorKey != -1 && dataSet.getSeriesCount() > 1)){
+            legend = true;
+        }
+
+        JFreeChart chart = ChartFactory.createScatterPlot(title,domainAxisName,rangeAxisName,dataSet, PlotOrientation.VERTICAL,legend,true,false);
+
+        XYPlot thePlot = chart.getXYPlot();
+        if (useSug){
+            if (thresholds[0] == 0){
+                thePlot.addRangeMarker(new ValueMarker(sug,Color.blue,new BasicStroke()));
+            }else{
+                thePlot.addDomainMarker(new ValueMarker(sug,Color.blue,new BasicStroke()));
+            }
+        }
+        if (useSig){
+            if (thresholds[1] == 0){
+                thePlot.addRangeMarker(new ValueMarker(sig,Color.red,new BasicStroke()));
+            }else{
+                thePlot.addDomainMarker(new ValueMarker(sig,Color.red,new BasicStroke()));
+            }
+        }
+        if (chroms){
+            thePlot.setDomainGridlinesVisible(false);
+            thePlot.getDomainAxis().setTickMarksVisible(false);
+            thePlot.getDomainAxis().setTickLabelsVisible(false);
+        }
+
+        if (!grid){
+            thePlot.setDomainGridlinesVisible(false);
+            thePlot.setRangeGridlinesVisible(false);
+        }
+        thePlot.setRenderer(new PlinkScatterPlotRenderer());
+        //TODO: Update Build File & Delete old Jars from CVS
+        thePlot.getRenderer().setBaseToolTipGenerator(new PlinkToolTipGenerator());
+        chart.setAntiAlias(false);
+
+        Shape[] shapes = new Shape[]{new Rectangle2D.Double(-2,-3,20,5)};
+        //shapes[0] = new Rectangle2D.Double(-2,-3,20,5);
+        Paint[] paints = new Paint[]{
+                new Color(0xFF, 0x55, 0x55),
+                new Color(0x55, 0x55, 0xFF),
+                //new Color(0x55, 0xFF, 0x55), //light green
+                //new Color(0xFF, 0xFF, 0x55), //light yellow
+                new Color(0xFF, 0x55, 0xFF),
+                //new Color(0x55, 0xFF, 0xFF), //light cyan
+                Color.pink,
+                Color.gray,
+                ChartColor.DARK_RED,
+                ChartColor.DARK_BLUE,
+                ChartColor.DARK_GREEN,
+                ChartColor.DARK_YELLOW,
+                ChartColor.DARK_MAGENTA,
+                ChartColor.DARK_CYAN,
+                Color.darkGray,
+                ChartColor.LIGHT_RED,
+                ChartColor.LIGHT_BLUE,
+                //ChartColor.LIGHT_GREEN,
+                //ChartColor.LIGHT_YELLOW,
+                ChartColor.LIGHT_MAGENTA,
+                //ChartColor.LIGHT_CYAN,
+                Color.lightGray,
+                ChartColor.VERY_DARK_RED,
+                ChartColor.VERY_DARK_BLUE,
+                ChartColor.VERY_DARK_GREEN,
+                ChartColor.VERY_DARK_YELLOW,
+                ChartColor.VERY_DARK_MAGENTA,
+                ChartColor.VERY_DARK_CYAN,
+                ChartColor.VERY_LIGHT_RED,
+                ChartColor.VERY_LIGHT_BLUE,
+                //ChartColor.VERY_LIGHT_GREEN,
+                //ChartColor.VERY_LIGHT_YELLOW,
+                ChartColor.VERY_LIGHT_MAGENTA,
+                //ChartColor.VERY_LIGHT_CYAN
+        };
+        DrawingSupplier supplier = new DefaultDrawingSupplier(
+                paints,
+                DefaultDrawingSupplier.DEFAULT_OUTLINE_PAINT_SEQUENCE,
+                DefaultDrawingSupplier.DEFAULT_STROKE_SEQUENCE,
+                DefaultDrawingSupplier.DEFAULT_OUTLINE_STROKE_SEQUENCE,
+                shapes
+        );
+        thePlot.setDrawingSupplier(supplier);
+
+        if (svgFile != null){
+            DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation();
+            Document document = domImpl.createDocument(null, "svg", null);
+            SVGGraphics2D svgGenerator = new SVGGraphics2D(document);
+            svgGenerator.getGeneratorContext().setPrecision(6);
+            chart.draw(svgGenerator, new Rectangle2D.Double(0, 0, width, height), null);
+            boolean useCSS = true;
+            try{
+                Writer out = new OutputStreamWriter(new FileOutputStream(svgFile), "UTF-8");
+                svgGenerator.stream(out, useCSS);
+            }catch (IOException ioe){
+                JOptionPane.showMessageDialog(parent,
+                        "Error saving svg file.",
+                        "IO Error",
+                        JOptionPane.ERROR_MESSAGE);
+                return;
+            }
+        }
+
+        ChartPanel panel = new ChartPanel(chart, true);
+        panel.setPreferredSize(new Dimension(width,height));
+        panel.setMinimumDrawHeight(10);
+        panel.setMaximumDrawHeight(2000);
+        panel.setMinimumDrawWidth(20);
+        panel.setMaximumDrawWidth(2000);
+        panel.addChartMouseListener(this);
+        plotFrame = new JFrame("Plot");
+        plotFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+        plotFrame.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent e) {
+                parent.disposePlot();
+            }
+        });
+        plotFrame.setContentPane(panel);
+        plotFrame.pack();
+        RefineryUtilities.centerFrameOnScreen(plotFrame);
+        plotFrame.setVisible(true);
+        plotFrame.requestFocus();
+        plotFrame.toFront();
+    }
+
+    public XYSeriesCollection makeChrDataSet(int col){
+        int numRows = table.getRowCount();
+        long[] maxPositions = new long[26];
+
+        for (int i = 0; i < numRows; i++){
+            String chrom = (String)table.getValueAt(i,0);
+            if (chrom.equals("")){
+                continue;
+            }
+            int chr;
+            if (chrom.equalsIgnoreCase("X")){
+                chr = 23;
+            }else if (chrom.equalsIgnoreCase("Y")){
+                chr = 24;
+            }else if (chrom.equalsIgnoreCase("XY")){
+                chr = 25;
+            }else if (chrom.equalsIgnoreCase("MT")){
+                chr = 26;
+            }else{
+                chr = Integer.parseInt(chrom);
+            }
+            if (chr < 1){
+                continue;
+            }
+            long position = ((Long)table.getValueAt(i,2)).longValue();
+            if (position > maxPositions[chr-1]){
+                maxPositions[chr-1] = position;
+            }
+        }
+
+        long[] addValues = new long[27];
+        long addValue = 0;
+        addValues[0] = 0;
+
+        for (int i = 1; i < 27; i++){
+            addValue += maxPositions[i-1];
+            addValues[i] = addValue;
+        }
+
+        XYSeries[] xyArray = new XYSeries[27];
+        for(int i = 1; i < 23; i++){
+            xyArray[i] = new XYSeries("Chr" + i);
+        }
+        xyArray[23] = new XYSeries("ChrX");
+        xyArray[24] = new XYSeries("ChrY");
+        xyArray[25] = new XYSeries("ChrXY");
+        xyArray[26] = new XYSeries("ChrMT");
+
+        chrInfo = new Hashtable[26];
+        for (int i = 0; i < 26; i++){
+            chrInfo[i] = new Hashtable();
+        }
+
+        for (int i = 0; i < numRows; i++){
+            String chrom = (String)table.getValueAt(i,0);
+            if (chrom.equals("")){
+                continue;
+            }
+            int chr;
+            if (chrom.equalsIgnoreCase("X")){
+                chr = 23;
+            }else if (chrom.equalsIgnoreCase("Y")){
+                chr = 24;
+            }else if (chrom.equalsIgnoreCase("XY")){
+                chr = 25;
+            }else if (chrom.equalsIgnoreCase("MT")){
+                chr = 26;
+            }else{
+                chr = Integer.parseInt(chrom);
+            }
+            if (chr < 1){
+                continue;
+            }
+            double c = Double.parseDouble(String.valueOf(table.getValueAt(i,2)));
+            c += addValues[chr-1];
+            c = c/1000;
+            double f = -1;
+
+            if (table.getValueAt(i,col) == null){
+                continue;
+            }else if ((table.getValueAt(i,col)).equals(new Double(Double.NaN))){
+                continue;
+            }
+            else{
+                if (table.getValueAt(i,col) instanceof Double){
+                    f = ((Double)table.getValueAt(i,col)).doubleValue();
+                }else{
+                    JOptionPane.showMessageDialog(parent,
+                            "The selected column does not appear to be numerical.",
+                            "Invalid column",
+                            JOptionPane.ERROR_MESSAGE);
+                }
+            }
+
+            if (yPlotType == LOG10_PLOT){
+                if (f < 0 || f > 1){
+                    JOptionPane.showMessageDialog(parent,
+                            "The selected column is not formatted correctly \n" +
+                                    "for a -log10 plot.",
+                            "Invalid column",
+                            JOptionPane.ERROR_MESSAGE);
+                    return null;
+                }
+                f = (Math.log(f)/Math.log(10))*-1;
+            }else if (yPlotType == LN_PLOT){
+                f = Math.log(f);
+            }
+            long kbPos = Long.parseLong(String.valueOf(table.getValueAt(i,2)))/1000;
+            String infoString = table.getValueAt(i,1) + ", Chr" + chrom + ":" + kbPos + ", " + table.getValueAt(i,col);
+            chrInfo[chr-1].put(new Double(c),infoString);
+            xyArray[chr].add(c,f);
+        }
+
+        XYSeriesCollection dataset = new XYSeriesCollection();
+
+        seriesKeys = new int[27];
+        int seriesIndex = 0;
+        for (int i = 1; i < 27; i++){
+            if (xyArray[i].getItemCount() > 0){
+                seriesKeys[seriesIndex] = i;
+                seriesIndex++;
+                dataset.addSeries(xyArray[i]);
+            }
+        }
+        return dataset;
+    }
+
+    public XYSeriesCollection makeDataSet(int yCol, int xCol, int colorCol){
+        int numRows = table.getRowCount();
+        XYSeries[] xyArray = new XYSeries[0];
+        XYSeries xys = new XYSeries("Data");
+        Hashtable colorKey = new Hashtable();
+        if (colorCol != -1){
+            Vector seriesNames = new Vector();
+            for (int i = 0; i < numRows; i++){
+                if (table.getValueAt(i,colorCol) != null){
+                    if (!colorKey.containsKey(table.getValueAt(i,colorCol))){
+                        colorKey.put(table.getValueAt(i,colorCol),new Integer(seriesNames.size()));
+                        if (table.getValueAt(i,colorCol) instanceof Double){
+                            seriesNames.add(String.valueOf(table.getValueAt(i,colorCol)));
+                        }else{
+                            seriesNames.add(table.getValueAt(i,colorCol));
+                        }
+                        if (seriesNames.size() > 50){
+                            JOptionPane.showMessageDialog(parent,
+                                    "The selected color key column contains more than 50 values.",
+                                    "Invalid column",
+                                    JOptionPane.ERROR_MESSAGE);
+                            colorKey = null;
+                            break;
+                        }
+                    }
+                }
+            }
+            if (colorKey != null){
+                xyArray = new XYSeries[seriesNames.size()];
+                for (int i = 0; i < seriesNames.size(); i++){
+                    xyArray[i] = new XYSeries((String)seriesNames.get(i));
+                }
+            }
+        }else{
+            colorKey = null;
+        }
+        nonChrInfo = new Hashtable();
+        for (int i = 0; i < numRows; i++){
+
+            double y;
+            if (yCol == -1){
+                y = i;
+            }else{
+                if (table.getValueAt(i,yCol) == null){
+                    continue;
+                }else if ((table.getValueAt(i,yCol)).equals(new Double(Double.NaN))){
+                    continue;
+                }else{
+                    if (table.getValueAt(i,yCol) instanceof Double){
+                        y = ((Double)table.getValueAt(i,yCol)).doubleValue();
+                    }else{
+                        JOptionPane.showMessageDialog(parent,
+                                "The selected column does not appear to be numerical.",
+                                "Invalid column",
+                                JOptionPane.ERROR_MESSAGE);
+                        return null;
+                    }
+                }
+
+                if (yPlotType == LOG10_PLOT){
+                    if (y < 0 || y > 1){
+                        JOptionPane.showMessageDialog(parent,
+                                "The selected column is not formatted correctly \n" +
+                                        "for a -log10 plot.",
+                                "Invalid column",
+                                JOptionPane.ERROR_MESSAGE);
+                        return null;
+                    }
+                    y = (Math.log(y)/Math.log(10))*-1;
+                }else if (yPlotType == LN_PLOT){
+                    y = Math.log(y);
+                }
+            }
+
+            double x;
+            if (xCol == -1){
+                x = i;
+            }else{
+                if (table.getValueAt(i,xCol) == null){
+                    continue;
+                }else if ((table.getValueAt(i,xCol)).equals(new Double(Double.NaN))){
+                    continue;
+                }else{
+                    if (table.getValueAt(i,xCol) instanceof Double){
+                        x = ((Double)table.getValueAt(i,xCol)).doubleValue();
+                    }else{
+                        JOptionPane.showMessageDialog(parent,
+                                "The selected column does not appear to be numerical.",
+                                "Invalid column",
+                                JOptionPane.ERROR_MESSAGE);
+                        return null;
+                    }
+                }
+                if (xPlotType == LOG10_PLOT){
+                    if (x < 0 || x > 1){
+                        JOptionPane.showMessageDialog(parent,
+                                "The selected column is not formatted correctly \n" +
+                                        "for a -log10 plot.",
+                                "Invalid column",
+                                JOptionPane.ERROR_MESSAGE);
+                        return null;
+                    }
+                    x = (Math.log(x)/Math.log(10))*-1;
+                }else if (xPlotType == LN_PLOT){
+                    x = Math.log(x);
+                }
+            }
+
+            if (colorKey == null){
+                xys.add(x,y);
+            }else{
+                int series = ((Integer)colorKey.get(table.getValueAt(i,colorCol))).intValue();
+                xyArray[series].add(x,y);
+            }
+
+            String key = String.valueOf(x) + " " +  String.valueOf(y);
+            String value;
+            if (Options.getSNPBased()){
+                value = table.getValueAt(i,1) + ", Chr" + table.getValueAt(i,0) + ":" + table.getValueAt(i,2);
+                nonChrInfo.put(key,value);
+            }else{
+                if (plinkTableModel.getFIDColumn() != -1 && plinkTableModel.getIIDColumn() != -1){
+                    value = "FID: " + table.getValueAt(i,plinkTableModel.getFIDColumn()) + ", IID: " + table.getValueAt(i,plinkTableModel.getIIDColumn());
+                    nonChrInfo.put(key,value);
+                }else{
+                    nonChrInfo.put(key,key);
+                }
+            }
+        }
+
+        XYSeriesCollection dataset = new XYSeriesCollection();
+        if (colorKey == null){
+            dataset.addSeries(xys);
+        }else{
+            for (int i = 0; i < xyArray.length; i++){
+                dataset.addSeries(xyArray[i]);
+            }
+        }
+
+        return dataset;
+    }
+
+    public void disposePlot(){
+        if (plotFrame != null){
+            plotFrame.dispose();
+            plotFrame.setContentPane(new JPanel());
+        }
+        chrInfo = null;
+        nonChrInfo = null;
+        seriesKeys = null;
+        thresholdSigns = null;
+        thresholdAxes = null;
+        plotFrame = null;
+    }
+
+    public void chartMouseClicked(ChartMouseEvent chartMouseEvent) {
+        ChartEntity ce = chartMouseEvent.getEntity();
+        if (ce != null && ce.getToolTipText() != null){
+            if (Options.getSNPBased()){
+                StringTokenizer st = new StringTokenizer(ce.getToolTipText(),",");
+                parent.jumpToMarker(st.nextToken());
+            }else{
+                if (plinkTableModel.getFIDColumn() != -1 && plinkTableModel.getIIDColumn() != -1){
+                    StringTokenizer st = new StringTokenizer(ce.getToolTipText(),", ");
+                    st.nextToken(); //FID:
+                    String fid = st.nextToken();
+                    st.nextToken(); //IID:
+                    String iid = st.nextToken();
+                    parent.jumpToNonSNP(fid,iid);
+                }
+            }
+        }
+    }
+
+    public void chartMouseMoved(ChartMouseEvent chartMouseEvent) {
+    }
+
+    class PlinkScatterPlotRenderer extends XYDotRenderer {
+	static final long serialVersionUID = 7966308423644407904L;
+        public PlinkScatterPlotRenderer(){
+            super();
+        }
+
+        public void drawItem(Graphics2D graphics2D, XYItemRendererState xyItemRendererState, Rectangle2D rectangle2D, PlotRenderingInfo plotRenderingInfo, XYPlot xyPlot, ValueAxis valueAxis, ValueAxis valueAxis1, XYDataset xyDataset, int series, int item, CrosshairState crosshairState, int pass) {
+            EntityCollection entities = null;
+            Shape entityArea = null;
+            if (plotRenderingInfo != null) {
+                entities = plotRenderingInfo.getOwner().getEntityCollection();
+            }
+            double x = xyDataset.getXValue(series, item);
+            double y = xyDataset.getYValue(series, item);
+            if (!(new Double(y).equals(new Double(Double.NaN))) && !(new Double(x).equals(new Double(Double.NaN)))) {
+                RectangleEdge xAxisLocation = xyPlot.getDomainAxisEdge();
+                RectangleEdge yAxisLocation = xyPlot.getRangeAxisEdge();
+                double transX = valueAxis.valueToJava2D(x, rectangle2D, xAxisLocation);
+                double transY = valueAxis1.valueToJava2D(y, rectangle2D,yAxisLocation);
+
+                graphics2D.setPaint(this.getItemPaint(series, item));
+                int dotSize = baseDotSize;
+                PlotOrientation orientation = xyPlot.getOrientation();
+                if (orientation == PlotOrientation.HORIZONTAL){ //JFreeChart allows the user to flip the axes
+                    transX = valueAxis1.valueToJava2D(y, rectangle2D,yAxisLocation);
+                    transY = valueAxis.valueToJava2D(x, rectangle2D, xAxisLocation);
+                }
+                if (useSug || useSig){
+                    if (threeSizes){ //this means that both signs must be equivalent and both suggestive & significant are not -1
+                        if (thresholdAxes[0] == 0){ //both are for y axis
+                            if (thresholdSigns[0] == 0){  //>
+                                if (y > suggestive && y <= significant){
+                                    dotSize += 2;
+                                }else if (y > significant){
+                                    dotSize += 4;
+                                }
+                            }else{  //<
+                                if (y < suggestive && y >= significant){
+                                    dotSize += 2;
+                                }else if (y < significant){
+                                    dotSize += 4;
+                                }
+                            }
+                        }else{ //both are for x axis
+                            if (thresholdSigns[0] == 0){  //>
+                                if (x > suggestive && x <= significant){
+                                    dotSize += 2;
+                                }else if (x > significant){
+                                    dotSize += 4;
+                                }
+                            }else{  //<
+                                if (x < suggestive && x >= significant){
+                                    dotSize += 2;
+                                }else if (x < significant){
+                                    dotSize += 4;
+                                }
+                            }
+                        }
+                    }else{
+                        if (thresholdAxes[0] == 0 && thresholdAxes[1] == 0){ //both y
+                            if (!useSig){  //only use suggestive
+                                if (thresholdSigns[0] == 0){  //>
+                                    if (y > suggestive){
+                                        dotSize += 2;
+                                    }
+                                }else { //<
+                                    if (y < suggestive){
+                                        dotSize += 2;
+                                    }
+                                }
+                            }else if (!useSug){ //only use significant
+                                if (thresholdSigns[1] == 0){ //>
+                                    if (y > significant){
+                                        dotSize += 2;
+                                    }
+                                }else { //<
+                                    if (y < significant){
+                                        dotSize += 2;
+                                    }
+                                }
+                            }else{ //use both
+                                if (thresholdSigns[0] == 0 && thresholdSigns[1] == 1){  //suggestive is >, significant is <
+                                    if (y > suggestive || y < significant){
+                                        dotSize += 2;
+                                    }
+                                }else if (thresholdSigns[0] == 1 && thresholdSigns[1] == 0){ //suggestive is <, significant is >
+                                    if (y < suggestive || y > significant){
+                                        dotSize += 2;
+                                    }
+                                }else if (thresholdSigns[0] == 0){ //both >
+                                    if (y > suggestive || y > significant){
+                                        dotSize += 2;
+                                    }
+                                }else{  //both <
+                                    if (y < suggestive || y < significant){
+                                        dotSize += 2;
+                                    }
+                                }
+                            }
+                        }else if (thresholdAxes[0] == 1 && thresholdAxes[1] == 1){ //both x
+                            if (!useSig){  //only use suggestive
+                                if (thresholdSigns[0] == 0){  //>
+                                    if (x > suggestive){
+                                        dotSize += 2;
+                                    }
+                                }else { //<
+                                    if (x < suggestive){
+                                        dotSize += 2;
+                                    }
+                                }
+                            }else if (!useSug){ //only use significant
+                                if (thresholdSigns[1] == 0){ //>
+                                    if (x > significant){
+                                        dotSize += 2;
+                                    }
+                                }else { //<
+                                    if (x < significant){
+                                        dotSize += 2;
+                                    }
+                                }
+                            }else{ //use both
+                                if (thresholdSigns[0] == 0 && thresholdSigns[1] == 1){  //suggestive is >, significant is <
+                                    if (x > suggestive || x < significant){
+                                        dotSize += 2;
+                                    }
+                                }else if (thresholdSigns[0] == 1 && thresholdSigns[1] == 0){ //suggestive is <, significant is >
+                                    if (x < suggestive || x > significant){
+                                        dotSize += 2;
+                                    }
+                                }else if (thresholdSigns[0] == 0){ //both >
+                                    if (x > suggestive || x > significant){
+                                        dotSize += 2;
+                                    }
+                                }else{  //both <
+                                    if (x < suggestive || x < significant){
+                                        dotSize += 2;
+                                    }
+                                }
+                            }
+                        }else if (thresholdAxes[0] == 0 && thresholdAxes[1] == 1){ //sug y, sig x
+                            if (!useSig){  //only use suggestive
+                                if (thresholdSigns[0] == 0){  //>
+                                    if (y > suggestive){
+                                        dotSize += 2;
+                                    }
+                                }else { //<
+                                    if (y < suggestive){
+                                        dotSize += 2;
+                                    }
+                                }
+                            }else if (!useSug){ //only use significant
+                                if (thresholdSigns[1] == 0){ //>
+                                    if (x > significant){
+                                        dotSize += 2;
+                                    }
+                                }else { //<
+                                    if (x < significant){
+                                        dotSize += 2;
+                                    }
+                                }
+                            }else{ //use both
+                                if (thresholdSigns[0] == 0 && thresholdSigns[1] == 1){  //suggestive is >, significant is <
+                                    if (y > suggestive || x < significant){
+                                        dotSize += 2;
+                                    }
+                                }else if (thresholdSigns[0] == 1 && thresholdSigns[1] == 0){ //suggestive is <, significant is >
+                                    if (y < suggestive || x > significant){
+                                        dotSize += 2;
+                                    }
+                                }else if (thresholdSigns[0] == 0){ //both >
+                                    if (y > suggestive || x > significant){
+                                        dotSize += 2;
+                                    }
+                                }else{  //both <
+                                    if (y < suggestive || x < significant){
+                                        dotSize += 2;
+                                    }
+                                }
+                            }
+                        }else{ //sug x, sig y
+                            if (!useSig){  //only use suggestive
+                                if (thresholdSigns[0] == 0){  //>
+                                    if (x > suggestive){
+                                        dotSize += 2;
+                                    }
+                                }else { //<
+                                    if (x < suggestive){
+                                        dotSize += 2;
+                                    }
+                                }
+                            }else if (!useSug){ //only use significant
+                                if (thresholdSigns[1] == 0){ //>
+                                    if (y > significant){
+                                        dotSize += 2;
+                                    }
+                                }else { //<
+                                    if (y < significant){
+                                        dotSize += 2;
+                                    }
+                                }
+                            }else{ //use both
+                                if (thresholdSigns[0] == 0 && thresholdSigns[1] == 1){  //suggestive is >, significant is <
+                                    if (x > suggestive || y < significant){
+                                        dotSize += 2;
+                                    }
+                                }else if (thresholdSigns[0] == 1 && thresholdSigns[1] == 0){ //suggestive is <, significant is >
+                                    if (x < suggestive || y > significant){
+                                        dotSize += 2;
+                                    }
+                                }else if (thresholdSigns[0] == 0){ //both >
+                                    if (x > suggestive || y > significant){
+                                        dotSize += 2;
+                                    }
+                                }else{  //both <
+                                    if (x < suggestive || y < significant){
+                                        dotSize += 2;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                graphics2D.fillRect((int) transX, (int) transY, dotSize, dotSize);
+
+                // add an entity for the item...
+                if (entities != null) {
+                    if (entityArea == null) {
+                        entityArea = new Rectangle2D.Double(transX, transY, dotSize, dotSize);
+                    }
+                    String tip = "";
+                    if (getToolTipGenerator(series,item) != null) {
+                        tip = getToolTipGenerator(series,item).generateToolTip(xyDataset, series, item);
+                    }
+                    String url = null;
+                    if (getURLGenerator() != null) {
+                        url = getURLGenerator().generateURL(xyDataset, series, item);
+                    }
+                    XYItemEntity entity = new XYItemEntity(entityArea, xyDataset, series, item, tip, url);
+                    entities.add(entity);
+                }
+
+                // do we need to update the crosshair values?
+                if (xyPlot.isDomainCrosshairLockedOnData()) {
+                    if (xyPlot.isRangeCrosshairLockedOnData()) {
+                        // both axes
+                        crosshairState.updateCrosshairPoint(x, y, 0, 0, transX, transY, orientation); //added 0s to these methods to account for multiple axes which we don't use
+                    }
+                    else {
+                        // just the horizontal axis...
+                        crosshairState.updateCrosshairX(x,0);
+                    }
+                }
+                else {
+                    if (xyPlot.isRangeCrosshairLockedOnData()) {
+                        // just the vertical axis...
+                        crosshairState.updateCrosshairY(y,0);
+                    }
+                }
+            }
+        }
+    }
+
+    class PlinkToolTipGenerator extends StandardXYToolTipGenerator {
+	static final long serialVersionUID = -8804283563456993497L;
+        public PlinkToolTipGenerator(){
+            super();
+        }
+
+        public String generateToolTip(XYDataset dataset, int series, int item){
+            if (chroms){
+                return (String)chrInfo[seriesKeys[series]-1].get(new Double(dataset.getXValue(series,item)));
+            }else{
+                return (String)nonChrInfo.get(String.valueOf(dataset.getXValue(series,item)) + " " + String.valueOf(dataset.getYValue(series,item)));
+            }
+        }
+    }
+
+}
diff --git a/edu/mit/wi/plink/PlinkTableModel.java b/edu/mit/wi/plink/PlinkTableModel.java
new file mode 100755
index 0000000..523db5a
--- /dev/null
+++ b/edu/mit/wi/plink/PlinkTableModel.java
@@ -0,0 +1,314 @@
+package edu.mit.wi.plink;
+
+
+import edu.mit.wi.haploview.Options;
+
+import javax.swing.table.AbstractTableModel;
+import java.util.Vector;
+import java.util.Comparator;
+import java.util.Collections;
+import java.util.StringTokenizer;
+
+public class PlinkTableModel extends AbstractTableModel{
+    static final long serialVersionUID = -826740142478947102L;
+
+    private Vector columnNames;
+    private Vector data;
+    private Vector filtered;
+    private Vector unknownColumns;
+    private Vector snps;
+
+    private int CHROM_COLUMN = 0;
+    private int MARKER_COLUMN = 1;
+    private int POSITION_COLUMN = 2;
+    private int HAPLOTYPE_COLUMN = -1;
+    private int A1_COLUMN = -1;
+    private int A2_COLUMN = -1;
+    private int FID_COLUMN = -1;
+    private int IID_COLUMN = -1;
+
+    public PlinkTableModel(Vector c, Vector d){
+        columnNames=c;
+        data=d;
+        unknownColumns = new Vector();
+        unknownColumns.add("");
+
+        if (Options.getSNPBased()){
+            snps = new Vector();
+            for (int i = 0; i < d.size(); i++){
+                snps.add(((AssociationResult)d.get(i)).getMarker().getMarkerID());
+            }
+            for (int j = 3; j < columnNames.size(); j++){
+                String column = (String)columnNames.get(j);
+
+                if (column.equalsIgnoreCase("HAPLOTYPE")){ //TODO: change to starts with?
+                    HAPLOTYPE_COLUMN = j;
+                }else if (column.equalsIgnoreCase("A1")){
+                    A1_COLUMN = j;
+                }else if (column.equalsIgnoreCase("A2")){
+                    A2_COLUMN = j;
+                }
+                unknownColumns.add(column);
+            }
+        }else{ //not snp based
+            CHROM_COLUMN = -1;
+            MARKER_COLUMN = -1;
+            POSITION_COLUMN = -1;
+            for (int i = 0; i < columnNames.size(); i++){
+                String column = (String)columnNames.get(i);
+                if (column.equalsIgnoreCase("FID")){
+                    FID_COLUMN = i;
+                }else if (column.equalsIgnoreCase("IID")){
+                    IID_COLUMN = i;
+                }else{
+                    unknownColumns.add(column);
+                }
+            }
+        }
+
+        filtered = new Vector();
+
+        for (int i = 0; i < data.size(); i++){
+            filtered.add(new Integer(i));
+        }
+    }
+
+    public String getColumnName(int i){
+        return (String)columnNames.elementAt(i);
+    }
+
+    public Class getColumnClass(int c){
+        //things look nicer if we use the String renderer to left align all the cols.
+        return String.class;
+    }
+
+    public int getColumnCount(){
+        return columnNames.size();
+    }
+
+    public int getRowCount(){
+        //return data.size();
+        return filtered.size();
+    }
+
+    public Vector getUnknownColumns(){
+        return unknownColumns;
+    }
+
+    public Object getValueAt(int row, int column){
+        int realIndex = ((Integer)filtered.get(row)).intValue();
+        //AssociationResult result = (AssociationResult)data.get(row);
+        AssociationResult result = (AssociationResult)data.get(realIndex);
+        Object value;
+        if (Options.getSNPBased()){
+            Marker marker = result.getMarker();
+            if (column == CHROM_COLUMN){
+                value = marker.getChromosome();
+            }else if (column == MARKER_COLUMN){
+                value = marker.getMarkerID();
+            }else if (column == POSITION_COLUMN){
+                value = new Long(marker.getPosition());
+            }else if (column == HAPLOTYPE_COLUMN || column == A1_COLUMN || column == A2_COLUMN){
+                if (result.getValues().size() <= column-3){
+                    value = null;
+                }else{
+                    if(result.getValues().get(column-3) != null){
+                        if (result.getValues().get(column-3) instanceof String){
+                            value = result.getValues().get(column-3);
+                        }else{
+                            value = String.valueOf(((Double)(result.getValues().get(column-3))).intValue());
+                        }
+                    }else{
+                        value = null;
+                    }
+                }
+            }else{
+                try{
+                    if (result.getValues().size() <= column-3){
+                        value = null;
+                    }else{
+                        value = result.getValues().get(column-3);
+                    }
+                }catch (NumberFormatException nfe){
+                    value = result.getValues().get(column-3);
+                    if ((value).equals("NA")){
+                        value = new Double(Double.NaN);
+                    }
+                }
+            }
+        }else{
+            if (column == IID_COLUMN || column == FID_COLUMN){
+                if (result.getValues().get(column) instanceof Double){
+                    value = (result.getValues().get(column)).toString();
+                }else{
+                    value = result.getValues().get(column);
+                }
+            }else{
+                value = result.getValues().get(column);
+            }
+        }
+
+        return value;
+    }
+
+    public void setValueAt(Object o, int row, int column){
+        //This method shouldn't get called...
+        //((Vector)data.elementAt(row)).set(column,o);
+        fireTableCellUpdated(row, column);
+    }
+
+    public void filterAll(String chr, int start, int end, Vector filters){
+        resetFilters();
+        int rows = getRowCount();
+        Vector newFiltered = new Vector();
+        boolean chromPass = false;
+        boolean genericPass = false;
+        long realStart = start*1000;
+        long realEnd = end*1000;
+
+        for (int i = 0; i < rows; i++){
+            if (!(chr.equals(""))){
+                if (((String)getValueAt(i,CHROM_COLUMN)).equalsIgnoreCase(chr)){
+                    if ((((Long)getValueAt(i,POSITION_COLUMN)).longValue() >= realStart) || (start == -1)){
+                        if ((((Long)getValueAt(i,POSITION_COLUMN)).longValue() <= realEnd) || (end == -1)){
+                            chromPass = true;
+                        }
+                    }
+                }
+            }else{
+                chromPass = true;
+            }
+
+            if (filters.size() > 0){
+                boolean[] genericPasses = new boolean[filters.size()];
+
+                for (int j = 0; j < filters.size(); j++){
+                    String column;
+                    String sign;
+                    String value;
+                    double rowVal;
+                    double val = 0;
+                    String stringVal = null;
+                    int col = -1;
+
+                    StringTokenizer st = new StringTokenizer((String)filters.get(j));
+                    column = st.nextToken();
+                    sign = st.nextToken();
+                    value = st.nextToken();
+
+                    try{
+                        val = Double.parseDouble(value);
+                    }catch (NumberFormatException nfe){
+                        stringVal = value;
+                    }
+
+                    if (col == -1){
+                        for (int k = 0; k < columnNames.size(); k++){
+                            if(column.equalsIgnoreCase((String)columnNames.get(k))){
+                                col = k;
+                                break;
+                            }
+                        }
+                    }
+
+                    if (col == HAPLOTYPE_COLUMN || col == A1_COLUMN || col == A2_COLUMN){
+                        stringVal = value;
+                    }
+
+                    if (getValueAt(i,col) != null){
+                        if (stringVal != null && getValueAt(i,col) instanceof String){
+                            if (((String)getValueAt(i,col)).equalsIgnoreCase(stringVal)){
+                                genericPasses[j] = true;
+                            }
+                        }else{
+                            if (getValueAt(i,col) instanceof Double){
+                                rowVal = ((Double)getValueAt(i,col)).doubleValue();
+                            }else{
+                                rowVal = Double.NaN;
+                            }
+                            if (sign.equals(">=")){
+                                if (rowVal >= val){
+                                    genericPasses[j] = true;
+                                }
+                            }else if (sign.equals(">")){
+                                if (rowVal > val){
+                                    genericPasses[j] = true;
+                                }
+                            }else if (sign.equals("<=")){
+                                if (rowVal <= val){
+                                    genericPasses[j] = true;
+                                }
+                            }else if (sign.equals("<")){
+                                if (rowVal < val){
+                                    genericPasses[j] = true;
+                                }
+                            }else{
+                                if (rowVal == val){
+                                    genericPasses[j] = true;
+                                }
+                            }
+                        }
+                    }
+                }
+
+                boolean fail = false;
+                for (int m = 0; m < genericPasses.length; m++){
+                    if (!genericPasses[m]){
+                        fail = true;
+                    }
+                }
+                genericPass = !fail;
+            }else{
+                genericPass = true;
+            }
+
+
+
+            if (chromPass && genericPass){
+                newFiltered.add(new Integer(i));
+            }
+
+            chromPass = false;
+            genericPass = false;
+        }
+        filtered = newFiltered;
+        if (Options.getSNPBased()){
+            snps = new Vector();
+            for (int i = 0; i < filtered.size(); i++){
+                int realIndex = ((Integer)filtered.get(i)).intValue();
+                AssociationResult result = (AssociationResult)data.get(realIndex);
+                snps.add(result.getMarker().getMarkerID());
+            }
+        }
+    }
+
+    public void resetFilters(){
+        filtered = new Vector();
+
+        Collections.sort(data,new IndexComparator());
+
+        for (int i = 0; i < data.size(); i++){
+            filtered.add(new Integer(i));
+        }
+    }
+
+    public Vector getSNPs(){
+        return snps;
+    }
+
+    public int getFIDColumn(){
+        return FID_COLUMN;
+    }
+
+    public int getIIDColumn(){
+        return IID_COLUMN;
+    }
+
+    class IndexComparator implements Comparator {
+        public int compare (Object o1, Object o2) {
+            Integer i1 = new Integer(((AssociationResult)o1).getIndex());
+            Integer i2 = new Integer(((AssociationResult)o2).getIndex());
+            return i1.compareTo(i2);
+        }
+    }
+}
\ No newline at end of file
diff --git a/edu/mit/wi/plink/PlotOptionDialog.java b/edu/mit/wi/plink/PlotOptionDialog.java
new file mode 100755
index 0000000..8afaf9c
--- /dev/null
+++ b/edu/mit/wi/plink/PlotOptionDialog.java
@@ -0,0 +1,355 @@
+package edu.mit.wi.plink;
+
+import edu.mit.wi.haploview.*;
+
+import javax.swing.*;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.awt.*;
+import java.util.Vector;
+import java.io.File;
+
+public class PlotOptionDialog extends JDialog implements ActionListener, Constants {
+    static final long serialVersionUID = -6028138645962418263L;
+    private JComboBox yColumnChooser, xColumnChooser, yPlotChooser, xPlotChooser, signChooser1, signChooser2, thresholdChooser1, thresholdChooser2, dotChooser, colorKeyChooser;
+    private JLabel label1, label2;
+    private NumberTextField sigThresh, sugThresh, widthField, heightField;
+    private JTextField titleField;
+    private JCheckBox showGrid, exportSVG;
+    private JButton svgButton;
+    private String[] signs = {">","<"};
+    private String[] thresholds = {"Y-Axis","X-Axis"};
+    private String[] dotSizes = {"Small","Medium","Large"};
+
+    private PlinkTableModel theModel;
+    private PlinkResultsPanel thePanel;
+
+    private File svgFile;
+
+    public PlotOptionDialog (HaploView hv, PlinkResultsPanel panel, String title, PlinkTableModel model) {
+        super(hv,title);
+        theModel = model;
+        thePanel = panel;
+
+        Vector columns = new Vector(model.getUnknownColumns());
+        columns.add("Index");
+
+        Vector xCols = new Vector();
+        if (Options.getSNPBased()){
+            xCols.add("Chromosomes");
+            for (int i = 1; i < columns.size(); i++){
+                xCols.add(columns.get(i));
+            }
+        }else{
+            for (int i = 0; i < columns.size(); i++){
+                xCols.add(columns.get(i));
+            }
+        }
+
+        JPanel contents = new JPanel(new GridBagLayout());
+        GridBagConstraints c = new GridBagConstraints();
+        c.anchor = GridBagConstraints.CENTER;
+        c.insets = new Insets(0,0,10,0);
+        c.gridwidth = 2;
+
+        JPanel titlePanel = new JPanel();
+        titlePanel.add(new JLabel("Title:"));
+        titleField = new JTextField(25);
+        titlePanel.add(titleField);
+        contents.add(titlePanel,c);
+        c.insets = new Insets(0,0,0,0);
+        c.gridwidth = 1;
+        c.anchor = GridBagConstraints.WEST;
+        c.gridy = 1;
+        JPanel xPanel = new JPanel();
+        xPanel.add(new JLabel("X-Axis:"));
+        xColumnChooser = new JComboBox(xCols);
+        xColumnChooser.addActionListener(this);
+        xPanel.add(xColumnChooser);
+        contents.add(xPanel, c);
+        c.gridx = 1;
+        JPanel xScalePanel = new JPanel();
+        xScalePanel.add(new JLabel("Scale:"));
+        xPlotChooser = new JComboBox(PLOT_TYPES);
+        xScalePanel.add(xPlotChooser);
+        contents.add(xScalePanel,c);
+        c.gridy = 2;
+        c.gridx = 0;
+        JPanel yPanel = new JPanel();
+        yPanel.add(new JLabel("Y-Axis:"));
+        yColumnChooser = new JComboBox(columns);
+        yColumnChooser.addActionListener(this);
+        yPanel.add(yColumnChooser);
+        contents.add(yPanel,c);
+        c.gridx = 1;
+        JPanel yScalePanel = new JPanel();
+        yScalePanel.add(new JLabel("Scale:"));
+        yPlotChooser = new JComboBox(PLOT_TYPES);
+        yPlotChooser.addActionListener(this);
+        yScalePanel.add(yPlotChooser);
+        contents.add(yScalePanel,c);
+        c.gridwidth = 2;
+        c.gridy = 3;
+        c.gridx = 0;
+        c.anchor = GridBagConstraints.CENTER;
+        c.insets = new Insets(5,0,0,0);
+        JPanel sugPanel = new JPanel();
+        label1 = new JLabel("Threshold 1 (Blue Line)");
+        sugPanel.add(label1);
+        signChooser1 = new JComboBox(signs);
+        sugPanel.add(signChooser1);
+        sugThresh = new NumberTextField("",6,true,true);
+        sugPanel.add(sugThresh);
+        thresholdChooser1 = new JComboBox(thresholds);
+        sugPanel.add(thresholdChooser1);
+        contents.add(sugPanel,c);
+        c.insets = new Insets(0,0,5,0);
+        c.gridy = 4;
+        JPanel sigPanel = new JPanel();
+        label2 = new JLabel("Threshold 2 (Red Line) ");
+        sigPanel.add(label2);
+        signChooser2 = new JComboBox(signs);
+        sigPanel.add(signChooser2);
+        sigThresh = new NumberTextField("",6,true,true);
+        sigPanel.add(sigThresh);
+        thresholdChooser2 = new JComboBox(thresholds);
+        sigPanel.add(thresholdChooser2);
+        contents.add(sigPanel,c);
+        c.gridy = 5;
+        c.gridwidth = 2;
+        c.anchor = GridBagConstraints.CENTER;
+        c.insets = new Insets(0,0,0,0);
+        JPanel dotPanel = new JPanel();
+        dotPanel.add(new JLabel("Data Point Size:"));
+        dotChooser = new JComboBox(dotSizes);
+        dotPanel.add(dotChooser);
+        dotPanel.add(new JLabel("Color Key:"));
+        colorKeyChooser = new JComboBox(theModel.getUnknownColumns());
+        dotPanel.add(colorKeyChooser);
+        contents.add(dotPanel,c);
+        c.gridy = 6;
+        c.gridwidth = 2;
+        c.anchor = GridBagConstraints.WEST;
+        JPanel gridPanel = new JPanel();
+        showGrid = new JCheckBox("Show Gridlines",true);
+        gridPanel.add(showGrid);
+        gridPanel.add(new JLabel("Width:"));
+        widthField = new NumberTextField("750",5,false,false);
+        gridPanel.add(widthField);
+        gridPanel.add(new JLabel("Height:"));
+        heightField = new NumberTextField("300",5,false,false);
+        gridPanel.add(heightField);
+        contents.add(gridPanel,c);
+        c.gridy = 7;
+        c.anchor = GridBagConstraints.CENTER;
+        c.gridwidth = 2;
+        JPanel svgPanel = new JPanel();
+        exportSVG = new JCheckBox("Export to SVG:");
+        exportSVG.setActionCommand("svg");
+        exportSVG.addActionListener(this);
+        exportSVG.setSelected(false);
+        svgPanel.add(exportSVG);
+        svgButton = new JButton("Browse");
+        svgButton.addActionListener(this);
+        svgButton.setEnabled(false);
+        svgPanel.add(svgButton);
+        contents.add(svgPanel,c);
+
+
+        if (Options.getSNPBased()){
+            yColumnChooser.setSize(xColumnChooser.getSize());
+            xPlotChooser.setEnabled(false);
+            thresholdChooser1.setEnabled(false);
+            thresholdChooser2.setEnabled(false);
+            colorKeyChooser.setEnabled(false);
+        }
+
+        JPanel choicePanel = new JPanel();
+        JButton okButton = new JButton("OK");
+        okButton.addActionListener(this);
+        this.getRootPane().setDefaultButton(okButton);
+        choicePanel.add(okButton);
+        JButton cancelButton = new JButton("Cancel");
+        cancelButton.addActionListener(this);
+        choicePanel.add(cancelButton);
+        c.gridy = 8;
+        c.insets = new Insets(10,0,0,0);
+        contents.add(choicePanel,c);
+
+        setContentPane(contents);
+
+        this.setLocation(this.getParent().getX() + 100,
+                this.getParent().getY() + 100);
+        this.setModal(true);
+        this.setResizable(false);
+        this.getRootPane().setDefaultButton(okButton);
+    }
+
+    public void actionPerformed(ActionEvent e) {
+        String command = e.getActionCommand();
+        if(command.equals("Cancel")) {
+            this.dispose();
+        }else if (command.equals("OK")){
+            if (xColumnChooser.getSelectedIndex() == 0 && !Options.getSNPBased()){
+                JOptionPane.showMessageDialog(this,
+                        "Please select a column to plot on the X-Axis.",
+                        "Invalid value",
+                        JOptionPane.ERROR_MESSAGE);
+                return;
+            }
+            if (yColumnChooser.getSelectedIndex() == 0){
+                JOptionPane.showMessageDialog(this,
+                        "Please select a column to plot on the Y-Axis.",
+                        "Invalid value",
+                        JOptionPane.ERROR_MESSAGE);
+                return;
+            }
+            int yColumn = yColumnChooser.getSelectedIndex()-1; //accounts for ""
+            int yPlotType = yPlotChooser.getSelectedIndex();
+            int xColumn = xColumnChooser.getSelectedIndex();
+            int xPlotType = xPlotChooser.getSelectedIndex();
+            int colorColumn = colorKeyChooser.getSelectedIndex()-1;
+            if (Options.getSNPBased()){
+                yColumn += 3; //accounts for 3 known columns (chrom,marker,position)
+                colorColumn += 3;
+                xColumn += 2;
+            }else{
+                xColumn -= 1;
+                if (theModel.getFIDColumn() != -1){
+                    yColumn += 1;
+                    xColumn += 1;
+                    colorColumn += 1;
+                }
+                if (theModel.getIIDColumn() != -1){
+                    yColumn += 1;
+                    xColumn += 1;
+                    colorColumn += 1;
+                }
+            }
+            if ((xColumnChooser.getSelectedItem().equals("Index") && yColumnChooser.getSelectedItem().equals("Index")) ||
+                    (xColumnChooser.getSelectedItem().equals("Chromosomes") && yColumnChooser.getSelectedItem().equals("Index"))){
+                JOptionPane.showMessageDialog(this,
+                        "You must have at least one explicit axis.",
+                        "Invalid value",
+                        JOptionPane.ERROR_MESSAGE);
+                return;
+            }else{
+                if (xColumnChooser.getSelectedItem().equals("Index")){
+                    xColumn = -1;
+                }else if (yColumnChooser.getSelectedItem().equals("Index")){
+                    yColumn = -1;
+                }
+            }
+            if (colorKeyChooser.getSelectedIndex() < 1){
+                colorColumn = -1;
+            }
+
+            double suggestive, significant;
+            boolean useSug, useSig;
+            try{
+                if (sugThresh.getText().equals("")){
+                    useSug = false;
+                    suggestive = -1;
+                }else{
+                    suggestive = Double.parseDouble(sugThresh.getText());
+                    useSug = true;
+                }
+
+                if (sigThresh.getText().equals("")){
+                    useSig = false;
+                    significant = -1;
+                }else{
+                    significant = Double.parseDouble(sigThresh.getText());
+                    useSig = true;
+                }
+            }catch(NumberFormatException nfe){
+                JOptionPane.showMessageDialog(this,
+                        "Thresholds must be numerical.",
+                        "Invalid value",
+                        JOptionPane.ERROR_MESSAGE);
+                return;
+            }
+
+            int[] signs = new int[2];
+            signs[0] = signChooser1.getSelectedIndex();
+            signs[1] = signChooser2.getSelectedIndex();
+            int[] thresholds = new int[2];
+            thresholds[0] = thresholdChooser1.getSelectedIndex();
+            thresholds[1] = thresholdChooser2.getSelectedIndex();
+            int dotSize = 2 + (dotChooser.getSelectedIndex()*2);
+            int width, height;
+            if (!widthField.getText().equals("")){
+                width = Integer.parseInt(widthField.getText());
+            }else{
+                width = 750;
+            }
+            if (!heightField.getText().equals("")){
+                height = Integer.parseInt(heightField.getText());
+            }else{
+                height = 300;
+            }
+
+            if (!exportSVG.isSelected()){
+                svgFile = null;
+            }else{
+                if (svgFile == null){
+                   JOptionPane.showMessageDialog(this,
+                        "Please specify a filename for the SVG export.",
+                        "Invalid value",
+                        JOptionPane.ERROR_MESSAGE);
+                return;
+                }
+            }
+
+            this.dispose();
+            thePanel.makeChart(titleField.getText(),yPlotType,yColumn,xPlotType,xColumn,suggestive,significant,useSug,useSig,signs,thresholds,dotSize,colorColumn,showGrid.isSelected(),svgFile,width,height);
+        }else if (e.getSource() instanceof JComboBox){
+            if (xColumnChooser.getSelectedItem().equals("Chromosomes")){
+                xPlotChooser.setSelectedIndex(0);
+                xPlotChooser.setEnabled(false);
+                thresholdChooser1.setSelectedIndex(0);
+                thresholdChooser1.setEnabled(false);
+                thresholdChooser2.setSelectedIndex(0);
+                thresholdChooser2.setEnabled(false);
+                colorKeyChooser.setSelectedIndex(0);
+                colorKeyChooser.setEnabled(false);
+            }else if (xColumnChooser.getSelectedItem().equals("Index")){
+                xPlotChooser.setSelectedIndex(0);
+                xPlotChooser.setEnabled(false);
+                thresholdChooser1.setEnabled(true);
+                thresholdChooser2.setEnabled(true);
+                colorKeyChooser.setEnabled(true);
+            }else{
+                xPlotChooser.setEnabled(true);
+                thresholdChooser1.setEnabled(true);
+                thresholdChooser2.setEnabled(true);
+                colorKeyChooser.setEnabled(true);
+            }
+
+            if (yColumnChooser.getSelectedItem().equals("Index")){
+                yPlotChooser.setSelectedIndex(0);
+                yPlotChooser.setEnabled(false);
+            }else{
+                yPlotChooser.setEnabled(true);
+            }
+            if (yPlotChooser.getSelectedItem().equals("-log10")){
+                label1.setText("Suggestive (Blue Line)");
+                label2.setText("Significant (Red Line) ");
+            }else{
+                label1.setText("Threshold 1 (Blue Line)");
+                label2.setText("Threshold 2 (Red Line) ");
+            }
+        }else if (command.equals("Browse")){
+            HaploView.fc.setSelectedFile(new File(""));
+            if (HaploView.fc.showSaveDialog(this) == JFileChooser.APPROVE_OPTION){
+                svgFile = HaploView.fc.getSelectedFile();
+            }
+        }else if (command.equals("svg")){
+            if (exportSVG.isSelected()){
+                svgButton.setEnabled(true);
+            }else{
+                svgButton.setEnabled(false);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/edu/mit/wi/tagger/Allele.java b/edu/mit/wi/tagger/Allele.java
new file mode 100755
index 0000000..7eadf2d
--- /dev/null
+++ b/edu/mit/wi/tagger/Allele.java
@@ -0,0 +1,50 @@
+package edu.mit.wi.tagger;
+
+public class Allele{
+    private static final char[] alleleNumChars = {'0','1','2','3','4'};
+    private static final char[] alleleCodes = {'X','A','C','G','T'};
+
+    private VariantSequence locus;
+    private String numericGenotypeString;
+
+    public Allele(VariantSequence locus, String genotypeString) {
+        this.locus = locus;
+        this.numericGenotypeString = genotypeString;
+    }
+
+    public VariantSequence getLocus() {
+        return locus;
+    }
+
+    public String getGenotypeString(){
+        String disp = new String(numericGenotypeString);
+        for (int i = 0; i < alleleCodes.length; i++){
+            disp = disp.replace(alleleNumChars[i],alleleCodes[i]);
+        }
+
+        return disp;
+    }
+
+    public String getTestFileFormat(){
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < numericGenotypeString.length()-1; i++){
+            sb.append(numericGenotypeString.charAt(i)).append(",");
+        }
+        sb.append(numericGenotypeString.charAt(numericGenotypeString.length()-1));
+        return sb.toString();
+    }
+
+    public boolean equals(Object o) {
+        if (o instanceof Allele){
+            Allele a = (Allele)o;
+            if (a.locus.equals(locus) && a.numericGenotypeString.equals(numericGenotypeString)){
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public int hashCode(){
+        return locus.hashCode() + numericGenotypeString.hashCode();
+    }
+}
diff --git a/edu/mit/wi/tagger/AlleleCorrelator.java b/edu/mit/wi/tagger/AlleleCorrelator.java
new file mode 100755
index 0000000..6823edb
--- /dev/null
+++ b/edu/mit/wi/tagger/AlleleCorrelator.java
@@ -0,0 +1,11 @@
+package edu.mit.wi.tagger;
+
+import java.util.HashSet;
+
+public interface AlleleCorrelator {
+
+    public LocusCorrelation getCorrelation(VariantSequence v1, VariantSequence v2);
+
+    public void phaseAndCache(HashSet snpList);
+
+}
diff --git a/edu/mit/wi/tagger/Block.java b/edu/mit/wi/tagger/Block.java
new file mode 100755
index 0000000..b79dcff
--- /dev/null
+++ b/edu/mit/wi/tagger/Block.java
@@ -0,0 +1,65 @@
+package edu.mit.wi.tagger;
+
+import java.util.Vector;
+import java.util.Iterator;
+
+public class Block extends VariantSequence{
+    //this will have tagger SNP objects
+    private Vector snps;
+
+    public Block(Vector snps) {
+        this.snps = snps;
+    }
+
+    public boolean equals(Object o) {
+        if (o instanceof Block){
+            Block b = (Block)o;
+            if(b.snps.size() != snps.size()) {
+                return false;
+            }
+            for (int i = 0; i < snps.size(); i++){
+                if (!b.snps.contains(snps.get(i))){
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public int hashCode() {
+        int h = 0;
+        Iterator itr = snps.iterator();
+        while(itr.hasNext()) {
+            Object obj = itr.next();
+            h += obj.hashCode();
+        }
+        return h;
+    }
+
+    public String getName() {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < snps.size()-1; i++){
+            sb.append(((SNP)snps.get(i)).getName()).append(",");
+        }
+        sb.append(((SNP)snps.get(snps.size()-1)).getName());
+
+        return sb.toString();
+    }
+
+    public int getMarkerCount() {
+        return snps.size();
+    }
+
+    public SNP getSNP(int i) {
+        return (SNP) snps.get(i);
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public Vector getSnps() {
+        return snps;
+    }
+}
diff --git a/edu/mit/wi/tagger/LocusCorrelation.java b/edu/mit/wi/tagger/LocusCorrelation.java
new file mode 100755
index 0000000..f2077ac
--- /dev/null
+++ b/edu/mit/wi/tagger/LocusCorrelation.java
@@ -0,0 +1,21 @@
+package edu.mit.wi.tagger;
+
+//import java.util.Hashtable;
+
+public class LocusCorrelation {
+    Allele a;
+    double rsq;
+
+    public LocusCorrelation(Allele a, double rsq) {
+        this.a = a;
+        this.rsq = rsq;
+    }
+
+    public Allele getAllele(){
+        return a;
+    }
+
+    public double getRsq(){
+        return rsq;
+    }
+}
diff --git a/edu/mit/wi/tagger/SNP.java b/edu/mit/wi/tagger/SNP.java
new file mode 100755
index 0000000..2a4d24e
--- /dev/null
+++ b/edu/mit/wi/tagger/SNP.java
@@ -0,0 +1,59 @@
+package edu.mit.wi.tagger;
+
+//import java.util.Vector;
+//import java.util.Comparator;
+import java.util.HashSet;
+
+public class SNP extends VariantSequence {
+    private String name;
+    private long location;
+    private double MAF;
+    private HashSet LDList;
+
+    public SNP(String n, long l,double maf) {
+        name = n;
+        location =l;
+        MAF = maf;
+        LDList = new HashSet();
+    }
+
+    public SNP(String n, long l) {
+        this(n,l,-1);
+    }
+
+    public SNP(String n) {
+        this(n,-1);
+    }
+
+    public boolean equals(Object o) {
+        if (o instanceof SNP){
+            SNP s = (SNP)o;
+            if(name.equals(s.name) && location == s.location) {
+                return true;
+            }else {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public double getMAF() {
+        return MAF;
+    }
+
+    public long getLocation() {
+        return location;
+    }
+
+    public void addToLDList(SNP s){
+        LDList.add(s);
+    }
+
+    public HashSet getLDList() {
+        return LDList;
+    }
+ }
diff --git a/edu/mit/wi/tagger/Tag.java b/edu/mit/wi/tagger/Tag.java
new file mode 100755
index 0000000..4ae983c
--- /dev/null
+++ b/edu/mit/wi/tagger/Tag.java
@@ -0,0 +1,17 @@
+package edu.mit.wi.tagger;
+
+
+
+interface Tag {
+
+
+    /**
+     * Adds a Taggable object to the set of sites which are tagged.
+     *
+     * @param t The Taggable site to add to the list of sites tagged by this object
+     */
+    public void addTagged(Taggable t);
+
+    public String getName();
+
+}
diff --git a/edu/mit/wi/tagger/TagSequence.java b/edu/mit/wi/tagger/TagSequence.java
new file mode 100755
index 0000000..f87892c
--- /dev/null
+++ b/edu/mit/wi/tagger/TagSequence.java
@@ -0,0 +1,73 @@
+package edu.mit.wi.tagger;
+
+import java.util.Vector;
+
+
+public class TagSequence implements Tag {
+    private Allele allele;
+    private Vector tagged;
+    private VariantSequence sequence;
+
+    public TagSequence(Allele a) {
+        allele = a;
+        sequence = a.getLocus();
+        tagged = new Vector();
+    }
+
+    public TagSequence(VariantSequence snp){
+        tagged = new Vector();
+        sequence = snp;
+        allele = null;
+    }
+
+    public void addTagged(Taggable t) {
+        tagged.add(t);
+        t.addTag(this);
+    }
+
+    public Vector getTagged() {
+        return tagged;
+    }
+
+    public Allele getAllele() {
+        return allele;
+    }
+
+    public VariantSequence getSequence(){
+        return sequence;
+    }
+
+    public String getName(){
+        if (allele == null){
+            return sequence.getName();
+        }else{
+            return sequence.getName() + " : " + allele.getGenotypeString();
+        }
+    }
+
+    public String getTestName(){
+        if (allele == null){
+            return sequence.getName();
+        }else{
+            return sequence.getName() + "\t" + allele.getTestFileFormat();
+        }
+    }
+
+    public Vector getBestTagged() {
+        Vector result = new Vector();
+
+        for (int i = 0; i < tagged.size(); i++) {
+            Taggable taggable = (Taggable) tagged.elementAt(i);
+            if(taggable.getBestTag() == this) {
+                result.add(taggable);
+            }
+        }
+        return result;
+    }
+
+    //TODO: should isTagged check if Taggable t is a subsequence of something that is tagged?
+    public boolean isTagged(Taggable t) {
+        return tagged.contains(t);
+    }
+
+}
diff --git a/edu/mit/wi/tagger/Taggable.java b/edu/mit/wi/tagger/Taggable.java
new file mode 100755
index 0000000..f090847
--- /dev/null
+++ b/edu/mit/wi/tagger/Taggable.java
@@ -0,0 +1,32 @@
+package edu.mit.wi.tagger;
+
+import java.util.List;
+import java.util.Comparator;
+
+interface Taggable {
+
+    /**
+     * Adds Tag t to the list of sites that tag this Taggable object.
+     * @param t Tag object which tags this Taggable object 
+     */
+    public void addTag(Tag t);
+
+    public void removeTag(Tag t);
+
+    public void setTagComparator(Comparator tc);
+
+    public Comparator getTagComparator();
+
+    /**
+     * returns a list of the Tags which capture this site
+     * @return List list of Tag objects
+     */
+    public List getTags();
+
+    /**
+     * returns the "best" Tag that captures this site. best is
+     * defined by the particular implementation
+     * @return TagSequence
+     */
+    public TagSequence getBestTag();
+}
diff --git a/edu/mit/wi/tagger/Tagger.java b/edu/mit/wi/tagger/Tagger.java
new file mode 100755
index 0000000..615575e
--- /dev/null
+++ b/edu/mit/wi/tagger/Tagger.java
@@ -0,0 +1,843 @@
+package edu.mit.wi.tagger;
+
+import edu.mit.wi.haploview.Util;
+import edu.mit.wi.haploview.Options;
+import edu.mit.wi.haploview.HaploText;
+
+import java.util.*;
+import java.io.*;
+
+public class Tagger {
+    public static final double DEFAULT_RSQ_CUTOFF = 0.8;
+    public static final double DEFAULT_LOD_CUTOFF = 3.0;
+    public static final short DEFAULT_MIN_DISTANCE = 0;
+    public static final int PAIRWISE_ONLY = 0;
+    public static final int AGGRESSIVE_DUPLE = 1;
+    public static final int AGGRESSIVE_TRIPLE = 2;
+    public static final int NONE = 4;
+    private static final long DEFAULT_MAXDIST = 500000;
+    public static final int DEFAULT_MAXNUMTAGS = 0;
+    public static final double DEFAULT_MIN_DESIGNSCORE = 0;
+
+    //vector of SNP objects, which contains every SNP (tags and non-tags)
+    private Vector snps;
+
+    //vector of SNPs which must be included in the set of Tags
+    //no object may be present in forceInclude and forceExclude concurrently.
+    private Vector forceInclude;
+
+    //vector of SNPs which can never be included in the set of Tags
+    //no object may be present in forceInclude and forceExclude concurrently.
+    private Vector forceExclude;
+
+    private Hashtable designScores;
+
+    private AlleleCorrelator alleleCorrelator;
+    private double meanRSq;
+    private int percentCapped;
+    private double minRSquared;
+    private int aggression;
+    private int maxNumTags;
+    private long maxComparisonDistance;
+    private boolean findTags;
+    private boolean printAllTags;
+
+    //Vector of Tag objects determined by the most recent call to findTags()
+    private Vector tags;
+
+    //vector of sites which arent tagged by anything in the tags vector
+    private Vector untagged;
+
+    public int taggedSoFar;
+
+    public Tagger(Vector s, Vector include, Vector exclude, Hashtable design, AlleleCorrelator ac) throws TaggerException{
+        this(s,include,exclude,design,ac,DEFAULT_RSQ_CUTOFF,AGGRESSIVE_TRIPLE, DEFAULT_MAXDIST, DEFAULT_MAXNUMTAGS,true,false);
+    }
+
+    public Tagger(Vector s, Vector include, Vector exclude, Hashtable design, AlleleCorrelator ac, double rsqCut,
+                  int aggressionLevel, long maxCompDist, int maxNumTags, boolean findTags, boolean printAllTags)
+            throws TaggerException{
+        minRSquared = rsqCut;
+        aggression = aggressionLevel;
+        this.maxNumTags = maxNumTags;
+        this.findTags = findTags;
+        this.printAllTags = printAllTags;
+
+        if (maxNumTags > 0 && maxNumTags < include.size()){
+            throw new TaggerException("Number of forced-in tags greater than max number of tags:\n"+
+            include.size() + " > " + maxNumTags);
+        }
+
+        if(maxCompDist < 0 ) {
+            maxComparisonDistance = DEFAULT_MAXDIST;
+        } else {
+            maxComparisonDistance = maxCompDist;
+        }
+
+        if(s != null) {
+            snps = s;
+        } else {
+            snps = new Vector();
+        }
+
+        if(include != null) {
+            forceInclude = (Vector)include.clone();
+        } else {
+            forceInclude = new Vector();
+        }
+
+        if(exclude != null) {
+            forceExclude = (Vector)exclude.clone();
+        } else {
+            forceExclude = new Vector();
+        }
+
+        designScores = design;
+
+        alleleCorrelator = ac;
+
+
+        for(int i=0;i<snps.size();i++) {
+            VariantSequence curVarSeq = (VariantSequence) snps.get(i);
+            if(curVarSeq.getTagComparator() == null) {
+                TagRSquaredComparator trsc = new TagRSquaredComparator(curVarSeq);
+                curVarSeq.setTagComparator(trsc);
+            }
+        }
+    }
+
+    public double getPairwiseCompRsq(VariantSequence a, VariantSequence b){
+        return getPairwiseComp(a,b).getRsq();
+    }
+
+    public LocusCorrelation getPairwiseComp(VariantSequence a, VariantSequence b) {
+        return alleleCorrelator.getCorrelation(a,b);
+    }
+
+    /**
+     * This method finds Tags for the SNPs in the snps vector, and returns a vector of Tag objects
+     */
+    public Vector findTags() {
+        tags = new Vector();
+        untagged = new Vector();
+        taggedSoFar = 0;
+
+        //potentialTagsHash stores the PotentialTag objects keyed on the corresponding sequences
+        Hashtable potentialTagByVarSeq = new Hashtable();
+        PotentialTagComparator ptcomp = new PotentialTagComparator();
+        VariantSequence currentVarSeq;
+
+        //create SequenceComparison objects for each potential Tag, and
+        //add any comparisons which have an r-squared greater than the minimum.
+        for(int i=0;i<snps.size();i++) {
+            currentVarSeq = (VariantSequence)snps.get(i);
+
+            if(!(forceExclude.contains(currentVarSeq) ) ){
+                PotentialTag tempPT = new PotentialTag(currentVarSeq);
+                for(int j=0;j<snps.size();j++) {
+                    if( maxComparisonDistance == 0 || Math.abs(((SNP)currentVarSeq).getLocation() - ((SNP)snps.get(j)).getLocation()) <= maxComparisonDistance)  {
+                        if( getPairwiseCompRsq(currentVarSeq,(VariantSequence) snps.get(j)) >= minRSquared) {
+                            tempPT.addTagged((VariantSequence) snps.get(j));
+                        }
+                    }
+                }
+                potentialTagByVarSeq.put(currentVarSeq,tempPT);
+            }
+        }
+
+        Vector sitesToCapture = (Vector) snps.clone();
+
+
+        HaploText.logger.debug("snps to tag: " + sitesToCapture.size());
+
+
+        int countTagged = 0;
+        //add Tags for the ones which are forced in.
+        Vector includedPotentialTags = new Vector();
+        //construct a list of PotentialTag objects for forced in sequences
+        for (int i = 0; i < forceInclude.size(); i++) {
+            VariantSequence variantSequence = (VariantSequence) forceInclude.elementAt(i);
+            if(variantSequence != null && potentialTagByVarSeq.containsKey(variantSequence)) {
+                includedPotentialTags.add((PotentialTag) potentialTagByVarSeq.get(variantSequence));
+            }
+        }
+
+        Vector toRemove = new Vector();
+        //add each forced in sequence to the list of tags
+        for(int i=0;i<includedPotentialTags.size();i++) {
+            PotentialTag curPT = (PotentialTag) includedPotentialTags.get(i);
+            HashSet newlyTagged = addTag(curPT,potentialTagByVarSeq);
+            countTagged += newlyTagged.size();
+            sitesToCapture.removeAll(newlyTagged);
+            sitesToCapture.remove(curPT.sequence);
+
+            if(Options.getTaggerMinDistance() !=0){
+                long tagLocation = ((SNP)curPT.sequence).getLocation();
+                Iterator itr = potentialTagByVarSeq.keySet().iterator();
+                while(itr.hasNext()) {
+                    PotentialTag pt = (PotentialTag) potentialTagByVarSeq.get(itr.next());
+                    if(Math.abs(((SNP)pt.sequence).getLocation() - tagLocation) <= Options.getTaggerMinDistance()  ){
+                        toRemove.add(pt.sequence);
+                    }
+                }
+            }
+        }
+
+        if(Options.getTaggerMinDistance() !=0){
+            for(int i=0;i<toRemove.size();i++){
+                potentialTagByVarSeq.remove(toRemove.get(i));
+            }
+        }
+
+        if(designScores != null){ //Deal with minimum design scores
+            if (Options.getTaggerMinDesignScore() != 0){
+                Vector tagsToRemove = new Vector();
+                double score;
+                Iterator itr = potentialTagByVarSeq.keySet().iterator();
+                while(itr.hasNext()) {
+                    PotentialTag pt = (PotentialTag) potentialTagByVarSeq.get(itr.next());
+                    if (designScores.containsKey(pt.sequence.getName())){
+                        score = ((Double)designScores.get(pt.sequence.getName())).doubleValue();
+                    }else{
+                        score = 0;
+                    }
+                    if (score < Options.getTaggerMinDesignScore()){
+                        tagsToRemove.add(pt.sequence);
+                    }
+                }
+                for(int i =0;i<tagsToRemove.size();i++){
+                    potentialTagByVarSeq.remove(tagsToRemove.get(i));
+                }
+            }
+        }
+
+        if (findTags){
+            //loop until all snps are tagged
+            while(sitesToCapture.size() > 0) {
+                Vector potentialTags = new Vector(potentialTagByVarSeq.values());
+                if(potentialTags.size() == 0) {
+                    //we still have sites left to capture, but we have no more available tags.
+                    //this should only happen if the sites remaining in sitesToCapture were specifically
+                    //excluded from being tags. Since we can't add any more tags, break out of the loop.
+                    break;
+                }
+
+                //sorts the array of potential tags according to the number of untagged sites they can tag.
+                //the last element is the one which tags the most untagged sites, so we choose that as our next tag.
+                Collections.sort(potentialTags,ptcomp);
+                PotentialTag currentBestTag = (PotentialTag) potentialTags.lastElement();
+                //look at the last one and ones before it that tag the same number of things
+                //from that set choose based on design score
+                if (designScores != null){
+                    double currentBestScore = 0;
+                    if (designScores.containsKey(currentBestTag.sequence.getName())){
+                        currentBestScore = ((Double)designScores.get(currentBestTag.sequence.getName())).doubleValue();
+                    }
+                    HaploText.logger.debug("Current Best Tag: " + currentBestTag.sequence.getName() + "\tDesign Score: " + currentBestScore +
+                    " # Captured: " + currentBestTag.taggedCount());
+                    for (int i = potentialTags.size()-1; i>0; i--){
+                        if (currentBestTag.taggedCount() == ((PotentialTag)potentialTags.get(i)).taggedCount()){
+                            double previousScore = 0;
+                            if (designScores.containsKey(((PotentialTag)potentialTags.get(i)).sequence.getName())){
+                                previousScore = ((Double)designScores.get(((PotentialTag)potentialTags.get(i)).sequence.getName())).doubleValue();
+                            }
+                            if (previousScore > currentBestScore){
+                                HaploText.logger.debug("Choosing " + ((PotentialTag)potentialTags.get(i)).sequence.getName() + " with Design Score " +
+                                previousScore + " instead of " + currentBestTag.sequence.getName() + " with Design Score " + currentBestScore);
+                                currentBestTag = (PotentialTag)potentialTags.get(i);
+                                currentBestScore = previousScore;
+                            }
+                        }else{
+                            break;
+                        }
+                    }
+                }
+
+                HashSet newlyTagged = addTag(currentBestTag,potentialTagByVarSeq);
+                countTagged += newlyTagged.size();
+
+                sitesToCapture.removeAll(newlyTagged);
+                sitesToCapture.remove(currentBestTag.sequence);
+
+                if(Options.getTaggerMinDistance() !=0){
+                    Vector tooClose = new Vector();
+                    Vector tagsToRemove = new Vector();
+                    long tagLocation = ((SNP)currentBestTag.sequence).getLocation();
+                    Iterator itr = potentialTagByVarSeq.keySet().iterator();
+                    while(itr.hasNext()) {
+                        PotentialTag pt = (PotentialTag) potentialTagByVarSeq.get(itr.next());
+                        if(Math.abs(((SNP)pt.sequence).getLocation() - tagLocation) <= Options.getTaggerMinDistance()  ){
+                            tagsToRemove.add(pt);
+                            tooClose.add(pt.sequence);
+                        }
+                    }
+                    potentialTags.removeAll(tagsToRemove);
+                    for(int i =0;i<tooClose.size();i++){
+                        potentialTagByVarSeq.remove(tooClose.get(i));
+                    }
+                }
+
+            }
+        }
+        taggedSoFar = countTagged;
+
+        if(sitesToCapture.size() > 0) {
+            //any sites left in sitesToCapture could not be tagged, so we add them all to the untagged Vector
+            untagged.addAll(sitesToCapture);
+        }
+
+        HaploText.logger.debug("tagged " + countTagged + " SNPS using " + tags.size() +" tags" );
+        HaploText.logger.debug("# of SNPs that could not be tagged: " + untagged.size());
+
+        if (aggression != PAIRWISE_ONLY){
+            //peelback starting with the worst tag (i.e. the one that tags the fewest other snps.
+            Vector tags2BPeeled = (Vector)tags.clone();
+            Collections.reverse(tags2BPeeled);
+            peelBack(tags2BPeeled);
+        }
+
+        //we've done the best we can. now we check to see if there's a limit to the
+        //num of tags we're allowed to choose.
+        if (maxNumTags > 0){
+            //if so we need to chuck out the extras. figure out the utility of each tagSNP
+            //i.e. how many SNPs for which they and their combos are the only tags
+
+            while (getTagSNPs().size() > maxNumTags){
+                Vector tagSNPs = getTagSNPs();
+                potentialTagByVarSeq = new Hashtable();
+                Hashtable tagSeqByPotentialTag = new Hashtable();
+                //account for stuff tagged by snps themselves
+                for (int i = 0; i < tagSNPs.size(); i++){
+                    TagSequence ts = (TagSequence) tagSNPs.get(i);
+                    PotentialTag pt = new PotentialTag(ts.getSequence());
+                    pt.addTagged(ts.getTagged());
+                    potentialTagByVarSeq.put(ts.getSequence(),pt);
+                    tagSeqByPotentialTag.put(pt,ts);
+                }
+                //go through all pt's and add their utilities as members of combos
+                Vector tagHaps = getTagHaplotypes();
+                for (int i = 0; i < tagHaps.size(); i++){
+                    TagSequence ts = (TagSequence) tagHaps.get(i);
+                    Block b = (Block) ts.getSequence();
+                    for (int j = 0; j < b.getSnps().size(); j++){
+                        ((PotentialTag)potentialTagByVarSeq.get(b.getSNP(j))).addTagged(ts.getTagged());
+                    }
+                }
+
+                //now perform the steps of sorting and peeling
+                Vector potTagVec = new Vector(potentialTagByVarSeq.values());
+                Collections.sort(potTagVec,ptcomp);
+
+                int count = 0;
+                PotentialTag dumpedPT = (PotentialTag)potTagVec.elementAt(count);
+                while (forceInclude.contains(dumpedPT.sequence)){
+                    count++;
+                    dumpedPT = (PotentialTag)potTagVec.elementAt(count);
+                }
+                TagSequence dumpedTS = (TagSequence) tagSeqByPotentialTag.get(dumpedPT);
+                Vector taggedByCurTag = dumpedTS.getTagged();
+                for (int j = 0; j < taggedByCurTag.size(); j++){
+                    //note for everything tagged by this guy that they're no longer tagged by him
+                    VariantSequence vs =  (VariantSequence)taggedByCurTag.get(j);
+                    vs.removeTag(dumpedTS);
+                    if (vs.getTags().size() == 0){
+                        taggedSoFar--;
+                    }
+                }
+                tagHaps = getTagHaplotypes();
+                for (int i = 0; i < tagHaps.size(); i++){
+                    TagSequence ts = (TagSequence) tagHaps.get(i);
+                    Block b = (Block) ts.getSequence();
+                    if (b.getSnps().contains(dumpedTS.getSequence())){
+                        //this hap tag is now defunct because it was comprised in part by dumpedTS
+                        Vector taggedByHap = ts.getTagged();
+                        for (int j = 0; j < taggedByHap.size(); j++){
+                            VariantSequence vs =  (VariantSequence)taggedByHap.get(j);
+                            vs.removeTag(ts);
+                            if (vs.getTags().size() == 0){
+                                taggedSoFar--;
+                            }
+                        }
+                        tags.remove(ts);
+                    }
+                }
+                tags.remove(dumpedTS);
+            }
+        }
+
+
+        int count = 0;
+        meanRSq = 0;
+        Iterator itr = snps.iterator();
+        while (itr.hasNext()){
+            SNP s = (SNP) itr.next();
+            TagSequence ts = s.getBestTag();
+            if (ts != null){
+                meanRSq += getPairwiseComp(s, ts.getSequence()).getRsq();
+                count++;
+            }
+        }
+        //apparently some people think untagged SNPS should be averaged in as zeroes...leaving commented out for now.
+        //count += untagged.size();
+        meanRSq /= count;
+        percentCapped = (int)(((double)taggedSoFar/(double)snps.size())*100);
+
+        return new Vector(tags);
+    }
+
+    private void peelBack(Vector tagsToBePeeled){
+        Hashtable blockTagsByAllele = new Hashtable();
+        HashSet snpsInBlockTags = new HashSet();
+
+        HaploText.logger.debug("starting peelback. untagged.size() = " + untagged.size());
+
+        Vector availTagSNPs = new Vector();
+        for (int j = 0; j < tags.size(); j++){
+            availTagSNPs.add(((TagSequence)tags.get(j)).getSequence());
+        }
+
+        ListIterator uitr = untagged.listIterator();
+
+        //try to tag things that weren't taggable in pairwise with haps
+        while(uitr.hasNext()) {
+            SNP curSnp = (SNP) uitr.next();
+            HashSet comprehensiveBlock = new HashSet();
+
+            comprehensiveBlock.add(curSnp);
+            HashSet victor = curSnp.getLDList();
+            victor.retainAll(availTagSNPs);
+            comprehensiveBlock.addAll(victor);
+
+            alleleCorrelator.phaseAndCache(comprehensiveBlock);
+
+            LocusCorrelation bestPredictor = null;
+            Vector potentialTests = generateTests(curSnp, (Vector) tags.clone());
+            for (int j = 0; j < potentialTests.size(); j++){
+                LocusCorrelation lc = getPairwiseComp((VariantSequence)potentialTests.get(j),
+                        curSnp);
+                if (lc.getRsq() >= minRSquared){
+                    if (bestPredictor != null){
+                        if (lc.getRsq() >
+                                bestPredictor.getRsq()){
+                            bestPredictor = lc;
+                        }
+                    }else{
+                        bestPredictor= lc;
+                    }
+                }
+            }
+
+            if(bestPredictor != null) {
+                Allele bpAllele = bestPredictor.getAllele();
+                snpsInBlockTags.addAll(((Block)bpAllele.getLocus()).getSnps());
+                if (blockTagsByAllele.containsKey(bpAllele)){
+                    TagSequence ts = (TagSequence)blockTagsByAllele.get(bpAllele);
+                    ts.addTagged(curSnp);
+                }else{
+                    TagSequence ts = new TagSequence(bpAllele);
+                    ts.addTagged(curSnp);
+                    tags.add(ts);
+                    blockTagsByAllele.put(bpAllele,ts);
+                }
+                uitr.remove();
+                //note that we've caught another SNP
+                taggedSoFar++;
+            }
+        }
+
+        HaploText.logger.debug("finished attempt at pairwise untaggables. untagged.size() = " + untagged.size());
+
+        for (int i = 0; i < tagsToBePeeled.size(); i++){
+            TagSequence curTag = (TagSequence) tagsToBePeeled.get(i);
+            if (forceInclude.contains(curTag.getSequence()) ||
+                    snpsInBlockTags.contains(curTag.getSequence())){
+                continue;
+            }
+            Vector taggedByCurTag = curTag.getTagged();
+
+            //a hashset that contains all snps tagged by curtag
+            //and all tag snps in LD with any of them
+            HashSet comprehensiveBlock = new HashSet();
+            availTagSNPs = new Vector();
+            for (int j = 0; j < tags.size(); j++){
+                availTagSNPs.add(((TagSequence)tags.get(j)).getSequence());
+            }
+            availTagSNPs.remove(curTag.getSequence());
+            for (int j = 0; j < taggedByCurTag.size(); j++) {
+                SNP snp = (SNP) taggedByCurTag.elementAt(j);
+                comprehensiveBlock.add(snp);
+                HashSet victor = snp.getLDList();
+                victor.retainAll(availTagSNPs);
+                comprehensiveBlock.addAll(victor);
+            }
+            alleleCorrelator.phaseAndCache(comprehensiveBlock);
+
+            Hashtable bestPredictor = new Hashtable();
+            boolean peelSuccessful = true;
+            for (int k = 0; k < taggedByCurTag.size(); k++){
+                //look to see if we can find a predictor for each thing curTag tags
+                SNP thisTaggable = (SNP) taggedByCurTag.get(k);
+                Vector victor = (Vector) tags.clone();
+                victor.remove(curTag);
+                Vector potentialTests = generateTests(thisTaggable, victor);
+                for (int j = 0; j < potentialTests.size(); j++){
+                    LocusCorrelation lc = getPairwiseComp((VariantSequence)potentialTests.get(j),
+                            thisTaggable);
+                    if (lc.getRsq() >= minRSquared){
+                        if (bestPredictor.containsKey(thisTaggable)){
+                            if (lc.getRsq() >
+                                    ((LocusCorrelation)bestPredictor.get(thisTaggable)).getRsq()){
+                                bestPredictor.put(thisTaggable,lc);
+                            }
+                        }else{
+                            bestPredictor.put(thisTaggable,lc);
+                        }
+                    }
+                }
+                if (thisTaggable.getTags().size() == 1 && !bestPredictor.containsKey(thisTaggable)){
+                    peelSuccessful = false;
+                    break;
+                }
+            }
+            if (peelSuccessful){
+                for (int k = 0; k < taggedByCurTag.size(); k++){
+                    SNP thisTaggable = (SNP) taggedByCurTag.get(k);
+                    //if more than one snp is tagged by the same
+                    if (bestPredictor.containsKey(thisTaggable)){
+                        Allele bpAllele = ((LocusCorrelation)bestPredictor.get(thisTaggable)).getAllele();
+                        snpsInBlockTags.addAll(((Block)bpAllele.getLocus()).getSnps());
+                        if (blockTagsByAllele.containsKey(bpAllele)){
+                            TagSequence ts = (TagSequence)blockTagsByAllele.get(bpAllele);
+                            ts.addTagged(thisTaggable);
+                        }else{
+                            TagSequence ts = new TagSequence(bpAllele);
+                            ts.addTagged(thisTaggable);
+                            tags.add(ts);
+                            blockTagsByAllele.put(bpAllele,ts);
+                        }
+                    }
+                    thisTaggable.removeTag(curTag);
+                }
+                tags.remove(curTag);
+            }
+        }
+
+        //this removes multimarker tags that are not the best tag for any alleles
+         for (int i = 0; i < tags.size(); i++){
+            if (((TagSequence)tags.get(i)).getBestTagged().size() == 0){
+                TagSequence ts = (TagSequence)tags.get(i);
+                Vector taggedByHap = ts.getTagged();
+                for (int j = 0; j < taggedByHap.size(); j++){
+                    VariantSequence vs =  (VariantSequence)taggedByHap.get(j);
+                    vs.removeTag(ts);
+                    if (vs.getTags().size() == 0){
+                       //this should never happen!
+                    }
+                }
+                HaploText.logger.debug(((TagSequence)tags.get(i)).getSequence().getName() + ": " + ((TagSequence)tags.get(i)).getTagged().size() + " " + ((TagSequence)tags.get(i)).getBestTagged().size());
+                HaploText.logger.debug("Removing the above tag since it isn't the best tag for anything.");
+                tags.remove(i);
+                i--;
+            }
+         }
+    }
+
+    private Vector generateTests(SNP s, Vector availTags){
+        //returns all duples and triples from availTags which are in LD
+        //with SNP s, and with each other
+        HashSet tagsInLD = new HashSet(s.getLDList());
+        Vector availTagSNPs = new Vector();
+        for (int i = 0; i < availTags.size(); i++){
+            availTagSNPs.add(((TagSequence)availTags.get(i)).getSequence());
+        }
+        tagsInLD.retainAll(availTagSNPs);
+        HashSet tests = new HashSet();
+        Iterator lditr = tagsInLD.iterator();
+        while (lditr.hasNext()){
+            SNP curTag = (SNP) lditr.next();
+            HashSet hs = new HashSet(curTag.getLDList());
+            hs.retainAll(tagsInLD);
+            Vector victor = new Vector(hs);
+            if (aggression == AGGRESSIVE_DUPLE || aggression == AGGRESSIVE_TRIPLE){
+                //2 marker blocks
+                for (int i = 0; i < victor.size(); i++) {
+                    Vector block = new Vector();
+                    block.add(curTag);
+                    block.add(victor.get(i));
+                    tests.add(new Block(block));
+                    if (aggression == AGGRESSIVE_TRIPLE){
+                        //3 marker blocks
+                        for(int j=i+1;j<victor.size();j++) {
+                            //make sure these two snps are in LD with each other
+                            if (((SNP)victor.get(i)).getLDList().contains(victor.get(j))){
+                                Vector block2 = (Vector) block.clone();
+                                block2.add(victor.get(j));
+                                tests.add(new Block(block2));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return new Vector(tests);
+    }
+
+    private HashSet addTag(PotentialTag theTag,Hashtable potentialTagHash) {
+        Vector potentialTags = new Vector(potentialTagHash.values());
+
+        potentialTags.remove(theTag);
+        potentialTagHash.remove(theTag.sequence);
+        //newlyTagged contains alleles which were not tagged by anything in the set of tags before,
+        //and are now tagged by theTag.
+        HashSet newlyTagged = ((PotentialTag)theTag).tagged;
+
+        TagSequence tagSeq = new TagSequence(theTag.sequence);
+        tags.add(tagSeq);
+
+        Iterator itr = potentialTagHash.keySet().iterator();
+        Vector toRemove = new Vector();
+        //iterate through the list of available tags, and remove the newly tagged alleles from
+        //the list of alleles that each PotentialTag can tag. (since we want to choose our next tag
+        // according to which will tag the most untagged alleles )
+        while(itr.hasNext()) {
+            PotentialTag pt = (PotentialTag) potentialTagHash.get(itr.next());
+            pt.removeTagged(newlyTagged);
+            //if a PotentialTag cannot tag any other uncaptured sites, then we want to remove it from contention,
+            //unless its sequence still needs to be captured.
+            if(pt.taggedCount() == 0){
+                toRemove.add(pt.sequence);
+            }
+        }
+
+        for(int i=0;i<toRemove.size();i++) {
+            potentialTags.remove(potentialTagHash.remove(toRemove.get(i)));
+        }
+
+        //loop through the list of alleles the newly added tag can capture, and
+        //add them to the TagSequence object.
+        //we add all the alleles the tag can capture, _not_ just the newly tagged alleles.
+        Iterator ptitr = theTag.allTagged.iterator();
+        while(ptitr.hasNext()) {
+            tagSeq.addTagged((VariantSequence)ptitr.next());
+        }
+
+        return newlyTagged;
+    }
+
+    public void dumpTests(File outFile) throws IOException{
+        BufferedWriter bw = new BufferedWriter(new FileWriter(outFile));
+        for (int i = 0; i < tags.size(); i++){
+            bw.write(((TagSequence)tags.get(i)).getTestName());
+            bw.newLine();
+        }
+        bw.close();
+    }
+
+    public void dumpTags(File outFile) throws IOException{
+        BufferedWriter bw = new BufferedWriter(new FileWriter(outFile));
+        Vector tagSNPS = getTagSNPs();
+        for (int i = 0; i < getTagSNPs().size(); i++){
+            bw.write(((TagSequence)tagSNPS.get(i)).getName());
+            bw.newLine();
+        }
+        bw.close();
+    }
+
+    public void dumpConditionalHaps(File outFile) throws IOException{
+        //TODO Jules: Write this method.
+    }
+
+    class PotentialTag {
+        VariantSequence sequence;
+        // tagged contains the sequences which this sequence can tag, which are not yet tagged
+        //(this is used in the while loop in findTags() )
+        HashSet tagged;
+        //allTagged contains all sequences that this sequence can tag, regardless of what tags have already been chosen
+        HashSet allTagged;
+
+        public PotentialTag(VariantSequence s) {
+            sequence = s;
+            tagged = new HashSet();
+            allTagged = new HashSet();
+        }
+
+        public void addTagged(VariantSequence vs) {
+            tagged.add(vs);
+            allTagged.add(vs);
+        }
+
+        public void addTagged(Collection c){
+            tagged.addAll(c);
+            allTagged.addAll(c);
+        }
+
+        public void removeTagged(VariantSequence vs) {
+            tagged.remove(vs);
+        }
+
+        public void removeTagged(Collection c) {
+            tagged.removeAll(c);
+        }
+
+        //this returns the number of sequences that havent been tagged this sequence can tag
+        public int taggedCount() {
+            return tagged.size();
+        }
+
+    }
+
+    public void setExclude(Vector e) {
+        if(e != null) {
+            forceExclude = (Vector) e.clone();
+        }
+    }
+
+    public void setInclude(Vector e) {
+        if(e != null) {
+            forceInclude = (Vector) e.clone();
+        }
+    }
+
+    public void clearExclude() {
+        forceExclude.removeAllElements();
+    }
+
+    public void clearInclude() {
+        forceInclude.removeAllElements();
+    }
+
+    public Vector getTags() {
+        return tags;
+    }
+
+    public Vector getTagSNPs(){
+        Vector res = new Vector();
+        Iterator itr = tags.iterator();
+        while (itr.hasNext()){
+            TagSequence t = (TagSequence) itr.next();
+            if (t.getSequence() instanceof SNP){
+                res.add(t);
+            }
+        }
+
+        return res;
+    }
+
+    public Vector getTagHaplotypes(){
+        Vector res = new Vector();
+        Iterator itr = tags.iterator();
+        while (itr.hasNext()){
+            TagSequence t = (TagSequence) itr.next();
+            if (t.getSequence() instanceof Block){
+                res.add(t);
+            }
+        }
+
+        return res;
+    }
+
+    public Vector getForceInclude() {
+        return forceInclude;
+    }
+
+    public int getUntaggableCount() {
+        return untagged.size();
+    }
+
+    public void saveResultToFile(File outFile) throws IOException {
+        BufferedWriter bw = new BufferedWriter(new FileWriter(outFile));
+
+        bw.write("#captured " + taggedSoFar + " of " + snps.size() +" alleles at r^2 >= " + minRSquared);
+        bw.newLine();
+        bw.write("#captured " + percentCapped + " percent of alleles with mean r^2 of " + Util.roundDouble(meanRSq, 3));
+        bw.newLine();
+        bw.write("#using " + getTagSNPs().size() + " Tag SNPs in " + tags.size() + " tests.");
+        bw.newLine();
+
+        bw.write("Allele\tBest Test\tr^2 w/test");
+        bw.newLine();
+        for (int i = 0; i < snps.size(); i++) {
+            StringBuffer line = new StringBuffer();
+            SNP snp = (SNP) snps.elementAt(i);
+            line.append(snp.getName()).append("\t");
+            TagSequence theTag = snp.getBestTag();
+            if(theTag != null) {
+                line.append(theTag.getName()).append("\t");
+                line.append(getPairwiseCompRsq(snp,theTag.getSequence())).append("\t");
+            }
+            bw.write(line.toString());
+            bw.newLine();
+        }
+
+        bw.newLine();
+
+        bw.write("Test\tAlleles Captured");
+        bw.newLine();
+        for(int i=0;i<tags.size();i++) {
+            StringBuffer line = new StringBuffer();
+            TagSequence theTag = (TagSequence) tags.get(i);
+            line.append(theTag.getName()).append("\t");
+            Vector tagged;
+            if (printAllTags){
+                tagged = theTag.getTagged();
+            }else{
+                tagged = theTag.getBestTagged();
+            }
+            for (int j = 0; j < tagged.size(); j++) {
+                VariantSequence varSeq = (VariantSequence) tagged.elementAt(j);
+                if(j !=0){
+                    line.append(",");
+                }
+                line.append(varSeq.getName());
+            }
+            bw.write(line.toString());
+            bw.newLine();
+        }
+
+        bw.close();
+    }
+
+    class PotentialTagComparator implements Comparator {
+        public int compare(Object o1, Object o2) {
+            return ((PotentialTag)o1).taggedCount() -  ((PotentialTag)o2).taggedCount();
+        }
+    }
+
+    class TagRSquaredComparator implements Comparator {
+        VariantSequence seq;
+
+        public TagRSquaredComparator(VariantSequence s) {
+            seq = s;
+        }
+
+        public int compare(Object o1, Object o2) {
+            //if one of the compared tags actually is this sequence, always promote
+            //it to the front (i.e. a SNP should always pick itself as its own best tag
+            //if possible).
+            if (seq.equals(((TagSequence)o1).getSequence())){
+                return 1;
+            }else if (seq.equals(((TagSequence)o2).getSequence())){
+                return -1;
+            }
+
+            if(getPairwiseCompRsq(seq,((TagSequence)o1).getSequence()) ==
+                    getPairwiseCompRsq(seq,((TagSequence)o2).getSequence())) {
+                return 0;
+            } else if (getPairwiseCompRsq(seq,((TagSequence)o1).getSequence()) >
+                    getPairwiseCompRsq(seq,((TagSequence)o2).getSequence())) {
+                return 1;
+            } else {
+                return -1;
+            }
+        }
+    }
+
+    public double getMeanRSq() {
+        return meanRSq;
+    }
+
+    public int getPercentCaptured() {
+        return percentCapped;
+    }
+}
diff --git a/edu/mit/wi/tagger/TaggerException.java b/edu/mit/wi/tagger/TaggerException.java
new file mode 100755
index 0000000..c88410d
--- /dev/null
+++ b/edu/mit/wi/tagger/TaggerException.java
@@ -0,0 +1,8 @@
+package edu.mit.wi.tagger;
+
+public class TaggerException extends Exception{
+    static final long serialVersionUID = 6779928630776131238L;
+    public TaggerException(String s){
+             super(s);
+         }
+}
diff --git a/edu/mit/wi/tagger/VariantSequence.java b/edu/mit/wi/tagger/VariantSequence.java
new file mode 100755
index 0000000..6392d51
--- /dev/null
+++ b/edu/mit/wi/tagger/VariantSequence.java
@@ -0,0 +1,62 @@
+package edu.mit.wi.tagger;
+
+import java.util.*;
+
+public abstract class VariantSequence implements Taggable{
+    private Vector variants;
+    private Vector tags;
+    private Comparator tagComparator;
+
+    public VariantSequence() {
+        variants = new Vector();
+        tags = new Vector();
+    }
+
+    public void addTag(Tag t) {
+        tags.add(t);
+    }
+
+    public void removeTag(Tag t){
+        tags.remove(t);
+    }
+
+    public abstract boolean equals(Object o);
+
+    public Vector getVariants() {
+        return variants;
+    }
+
+    public List getTags(){
+        return tags;
+    }
+
+    public void setTagComparator(Comparator tagComparator) {
+        this.tagComparator = tagComparator;
+    }
+
+    public Comparator getTagComparator() {
+        return tagComparator;
+    }
+
+    public abstract String getName();
+
+    /**
+     * finds and then returns the "best" tag for this VariantSequence.
+     * Uses the Comparator tagComparator to decide the "best" tag.
+     * If no comparator has been set, then an arbitrary tag is returned from the Vector tags.
+     * @return Tag. The best Tag for this sequence. 
+     */
+    public TagSequence getBestTag() {
+        if(tags!= null && tags.size()>0) {
+            //if a comparator has been set, then use it to sort the tags. the "best" tag will be the
+            //last one in the sorted vector.
+            //if there isnt a comparator, we have no basis for choosing which tag is "best",
+            //so we just return whatever is currently in the last spot in the
+            if(tagComparator != null) {
+                Collections.sort(tags,tagComparator);
+            }
+            return (TagSequence)tags.lastElement();
+        }
+        return null;
+    }
+}
diff --git a/examples/README b/examples/README
new file mode 100755
index 0000000..f0e5a2e
--- /dev/null
+++ b/examples/README
@@ -0,0 +1,27 @@
+The three files in this directory represent the most commonly used
+file formats. They can be used to test that the program is running
+correctly once it is built. They are:
+
+----------
+
+sample.ped -- a linkage style pedigree file which contains one
+individual per line with family info plus genotypes.
+
+sample.info -- goes with the above and provides marker name and position
+
+These two are used together by selecting the "Linkage Format" tab
+from the load dialog.
+
+----------
+
+sample.hmp -- a hapmap style file with all indiv and marker
+information. 
+
+This one is loaded all by itself by selecting "HapMap Format" tab
+from the load dialog.
+
+----------
+
+More information on file formats can be found at:
+
+http://www.broad.mit.edu/mpg/haploview/
diff --git a/examples/sample.hmp b/examples/sample.hmp
new file mode 100755
index 0000000..8f8bae7
--- /dev/null
+++ b/examples/sample.hmp
@@ -0,0 +1,35 @@
+#Mon Feb  9 14:42:26 2004: HapMap genotype data dump, chr11 from 84132208 to 84382207
+#Settings:
+#   minimum MAF: 0.00
+rs# alleles chrom pos strand assembly# center protLSID assayLSID panelLSID NA06985 NA06991 NA06993 NA06994 NA07000 NA07019 NA07022 NA07029 NA07034 NA07048 NA07055 NA07056 NA07345 NA07348 NA07357 NA10830 NA10831 NA10835 NA10838 NA10839 NA10846 NA10847 NA10851 NA10854 NA10855 NA10856 NA10857 NA10859 NA10860 NA10861 NA10863 NA11829 NA11830 NA11831 NA11832 NA11839 NA11840 NA11881 NA11882 NA11992 NA11993 NA11994 NA11995 NA12003 NA12004 NA12005 NA12006 NA12043 NA12044 NA12056 NA12057 NA12144 N [...]
+rs7121845 C/T chr11 84132386 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:7121845:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 CT CT CC CT CT CT CT CC TT TT TT CC TT TT TT CT TT TT CT TT CC TT TT TT CT TT CT CT CT CT TT TT TT CT TT CT CT CT CT TT CT TT CT CC CT CT CT CT TT TT TT CC CT NN CT TT TT TT CT TT TT CT CT CT CT TT CT TT CC CT TT CC TT CT TT CT CT CC TT CT CT CT CC CC TT CC CC CT CT CC
+rs1940094 A/G chr11 84135785 - ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:1940094:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 GG GG GG GG GG GG GG GG AA AA AG GG AA AG GG AG AG AG AG AA GG GG GG AA GG AG GG GG AG GG AG AA AG GG AG AG AG AG GG AG AG AG AG GG AG AG AG AG AG AG GG GG AG GG AG AG GG AA GG AG AG AG GG AG AG AA GG GG GG GG AA GG GG AG AA AG AG GG AA AG AG AG GG GG AG AG GG AG AG GG
+rs1940092 G/T chr11 84141430 - ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:1940092:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 GT GT GG GT GT GT GT GG GG GG GT GG GG GT TT GG GT GT GG GG GG TT TT GG GT GT GT GT GG GT GT GG GT GT GT GG GG GG GT GT GG GT GG GG GG GG GG GG GT GT TT GG GG TT GG GT TT GG GT GT GT GG GT GG GG GG GT GT GG GT GG GG TT GG GG GG GG GG GG GG GG GG GG GG GT GG GG GG GG GG
+rs472587 A/C chr11 84146440 - ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:472587:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 CC CC CC CC CC CC CC CC AA AA AC CC CC CC CC AC AC CC CC AC CC CC CC CC CC AC CC CC CC CC CC AC AC CC CC CC CC AC CC CC AC CC CC CC CC NN CC AC CC AC CC CC AC CC AC AC CC AC CC AC CC CC CC AC CC AA CC CC CC CC AC CC CC CC AA CC CC CC AC CC CC AC CC CC AC CC CC AC AC CC
+rs1789175 G/T chr11 84158355 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:1789175:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 GT GT GG GT GT GT GT GG GG GG GT GG GG GT TT GG GT TT GG GG GG TT TT GG GT GT GT GT GG GT TT GG GT GG GT GG GG GG GT GT GG GT GG GG GG NN GG GG GT GT TT GG GG TT GG GT TT GT GT GT TT GT GT GG GG GG GT GT GG GT GG GG TT GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG
+rs555867 A/G chr11 84160546 - ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:555867:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 AA AA AA AA AA AA AA AA GG GG AG AA AG AA AA AG AG AA AA AG AA AA AA AG AA AG AA NN AG AA AA GG AG AA AG AG AA AG AA AG AG AG AA AA AA AG AA AG AA AG AA AA AG AA AG GG AA AG AA AG AA AA AA AG AG GG AA AA AA AA GG AA AA AG GG AA AA AA AG AA AA AG AA AA AG AA AA AG AG AA
+rs6592199 G/T chr11 84181667 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:6592199:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 GT GT TT TT GT GT GT TT TT TT GT TT TT GT GG TT TT GT TT TT TT TT GG TT GT GT TT GT TT GT GT TT GT TT GT TT TT TT GT GT TT GT TT TT TT TT TT TT TT GT GT TT TT TT TT GT GT TT GT TT GT TT GT TT TT TT TT TT TT GT TT TT GG TT TT TT TT TT TT TT TT TT TT TT TT TT TT TT TT TT
+rs6592202 C/T chr11 84187163 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:6592202:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 CT CT TT TT CT CT CT TT CC CC CC TT CT CT CC CT CT CT CT CT TT TT CC CC CT CC TT CT CT CT CT CC CC TT CC CT CT CT CT CC CT CC TT TT CT CT TT CT CT CC CT TT CT TT CT CC CT CT CT CT CT TT CT CT CT CC TT TT TT CT CC TT CC CT CC CT CT TT CC CT CT CT TT TT CT TT TT CT CT TT
+rs1943707 C/T chr11 84201225 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:1943707:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 CT CT CC CT CT CT CT CC TT TT TT CC CT CT TT CT TT TT CT CT CC TT TT TT CT TT CT CT CT CT TT TT TT CT TT CT CT CT CT TT CC TT CC CC CT CT CC CT TT CT TT CC CT TT CT TT TT TT CT TT TT CT CT CT CT CT CT CT CC CT TT CC TT CT TT CT CT CC TT CT CT CT CC CC TT CC CC CT CT CC
+rs7110559 A/G chr11 84207525 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:7110559:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG
+rs1943696 A/C chr11 84219543 + ncbi_b34 imsut-riken urn:LSID:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:1943696:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 AC AC AA AA AC AC AC AA CC CC CC AA AC AC CC AC AC AA AC AC AA AA CC CC AC CC AA AC AC AA AC CC CC AA CC AC AC AC AC CC AA AC AA AA AC NN AA AC AC AC AC AA AC AA AC CC AC AC AC AC AA AA AC AC AC AC AA AA AA AC CC AA AC AC CC AC AC AA CC AC AC AC AA AA AC AA AA AC AC AA
+rs1943701 A/G chr11 84223923 + ncbi_b34 illumina urn:LSID:illumina.hapmap.org:Protocol:Golden_Gate_1.0.0:1 urn:LSID:illumina.hapmap.org:Assay:37077:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 AG AG AG AA GG GG AG AG GG GG GG AG GG GG GG AG AG GG AG GG AA AA GG GG AG GG AG AG AG AG GG GG GG AA GG AG AG AA AG GG AG GG AA AA AG AG AG GG AG GG AG AA AG AA AG GG AG GG AG AG GG AG AG AG AG GG AA AG AA AG GG AA GG AG GG AG AG AA GG AG AG AG AA AA AG AG AA GG AG AG
+rs1943705 C/T chr11 84244336 - ncbi_b34 illumina urn:LSID:illumina.hapmap.org:Protocol:Golden_Gate_1.0.0:1 urn:LSID:illumina.hapmap.org:Assay:37078:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 CT CT TT TT CT CT CT TT CC CC CC TT CT CT CC CC CT CT CT CT TT TT CC CC CT CC TT CT CT CT CT CC CC TT CC CT CT CT CT CC TT CC TT TT CT CT TT CT CT CT CT TT CT TT CT CC CT CT CC CT CT TT CT CT CT CT TT TT TT CT CC TT CC CT CC CT CT TT CC CT CT CT TT TT CT TT TT CT CT TT
+rs4370960 C/T chr11 84251005 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:4370960:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 CC CC CC CT CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC TT CC CC CC CC CC CC CC CC CC CC CC CT CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CT CC CC CC CC CC CT CC CC CC CC CC CC CC CT CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CT CC CC CC CC CC
+rs1943699 A/G chr11 84262943 - ncbi_b34 illumina urn:LSID:illumina.hapmap.org:Protocol:Golden_Gate_1.0.0:1 urn:LSID:illumina.hapmap.org:Assay:37079:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 AG AG GG GG AG AG AG GG AA AA AA GG AA AA AA AG AG AA AG AA GG GG AA AA AG AA GG AG AG AG AA AA AA GG AA AG AG AG AG AA AG AA GG GG AG AG AG AG AG AA AG GG AG GG AG AA AG AA AG AG AA AG AG AG AG AA GG GG GG AG AA GG AA AG AA AG AG GG AA AG AG AG GG GG AA AG GG AG AG GG
+rs3802893 A/G chr11 84267711 - ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:3802893:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 AA AA AA AA AA AA AA AA AA AA AA AA AG AG AA AA AA AG AA AG AA AA AA AA AA AA AA AA AA AA AG AG AA AA AA AA AA AA AA AA AG AA AA AA AA NN AG AA AA AG AA AA AA AA AA AG AA AA AA AA AG AG AG AA AA AG AA AA AA AA AG AA AA AG AA AA AA AA AA AA AA AA AA AA AA AG AA AA AA AA
+rs876695 G/T chr11 84271713 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:876695:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 TT TT TT GT TT TT TT TT TT TT TT TT TT TT TT TT TT TT TT TT TT GG TT TT TT TT TT TT TT TT GT TT TT GT TT TT TT TT TT TT GT TT TT TT TT NN TT TT TT GT TT TT TT GT TT GT TT TT TT GT TT TT GT TT TT GT TT TT TT TT TT TT GT TT TT TT TT TT TT TT TT TT TT TT GT TT TT TT TT TT
+rs1943710 A/G chr11 84275450 - ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:1943710:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 AA AA AA AG AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA GG AA AA AA AA AA AA AA AA AG AA AA AG AA AA AA AA AA AA AG AA AA AA AA NN AA AA AA AG AA AA AA AG AA AG AA AA AA AG AA AA AG AA AA AG AA AA AA AA AA AA AG AA AA AA AA AA AA AA AA AA AA AA AG AA AA AA AA AA
+rs952201 A/G chr11 84280115 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:952201:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 GG GG GG AG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG AA GG GG GG GG GG GG GG GG AG GG GG AG GG GG GG GG GG GG AG GG GG GG GG GG GG GG GG AG GG GG GG AG GG AG GG GG GG AG GG GG AG GG GG AG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG AG GG GG GG GG GG
+rs1943715 C/T chr11 84289868 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:1943715:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 CT CT TT TT CT CT CT TT CC CC CC TT CC CC CC CT CT TT CT CC NN TT CC CC CT CC CT CT CT CT CT CC CC TT CC CT CT CT CT CC TT CC TT TT CT CT CC CT CC CT CT TT CT TT CC CT CT CC CT CT TT TT TT CT CT CT TT CT TT CT CC TT CC CC CC CT CT TT CC CC CT CC TT CC CT CT TT CT CC TT
+rs2000815 C/T chr11 84302776 + ncbi_b34 imsut-riken urn:LSID:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:2000815:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
+rs1943725 C/T chr11 84323254 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:1943725:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 CC CC CC CT CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC TT CC CC CC CC CC CC CC CC CT CC CC CT CC CC CC CC CC CC CT CC CC CC CC CC CC CC CC CT CC CC CC CT CC CT CC CC CC CT CC CC CT CC CC CT CC CC CC CC CT CC CC CT CC CC CC CC CC CC CC CC CC CC CT CC CC CC CC CC
+rs1943733 A/G chr11 84326905 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:1943733:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 GG GG GG AG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG GG AA GG GG GG GG GG GG GG GG AG GG GG AG GG GG GG GG GG GG AG GG AG GG GG NN AG NN GG AG GG GG AG AG GG AG GG GG GG AG GG GG AA GG GG AG GG GG GG AG AG AG GG AG GG AG GG AG GG NN GG GG GG GG AG GG AG GG GG GG
+rs957438 C/T chr11 84327685 + ncbi_b34 illumina urn:LSID:illumina.hapmap.org:Protocol:Golden_Gate_1.0.0:1 urn:LSID:illumina.hapmap.org:Assay:37081:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 CT CT CC CC CT CT CT CC TT TT TT CC CT CT TT CT CT CC CT TT CC CC TT TT CT TT CT CT CT CT CT TT TT CC TT CT CT CT CT TT CC TT CC CC CT CT CT CT TT CT CT CC CC CC TT CT CT TT CT CT CC CC CC CT CT CT CC CT CC CT CT CC TT CC TT CT CT CC TT TT CT CT CC CC CT CT CC CT TT CC
+rs6592211 A/G chr11 84331981 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:6592211:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 AG AG GG GG AG AG AG GG AA AA AA GG AG AG AA AG AG GG AG AA GG GG AA AA AG AA AG AG AG AG AG AA AA GG AA AG AG AG AG AA GG AA GG GG AG AG AG AG AA AG AG GG GG GG AA AG AG AA AG AG GG GG GG AG AG AG GG AG GG AG AA GG AA AG AA AG AG GG AA AA AG AA GG AG AG AG GG AG AA GG
+rs1227671 A/T chr11 84337002 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:1227671:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 AA AA AA AT AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA TT AA AA AA AA AA AA NN AA AT AA AA AT AA AA AA AA AA AA AT AA AA AA AA NN AA AA AA AT AA AA AA AT AA AT AA AA AA AT AA AA AT AA AA AT AA AA AA AA AT AA AA AT AA AA AA AA AA AA AA AA AA AA AT AA AA AA AA AA
+rs1940125 A/C chr11 84344825 + ncbi_b34 imsut-riken urn:LSID:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:1940125:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 CC NN AC AC CC CC AC AC CC CC CC CC AC AC CC CC CC CC CC CC AC CC CC CC CC CC CC AC CC AC CC CC CC AC CC CC CC AC CC CC AC AC AC CC AC CC CC CC CC AC CC AC AC CC CC CC CC CC CC CC CC AC CC AC AC AC AC CC AA CC CC CC CC CC CC CC CC AC CC CC CC CC CC CC AC CC CC CC CC CC
+rs7111775 G/T chr11 84355321 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:7111775:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 TT GT GT GG TT TT GT GT GG GG GT TT GG GT GT TT GT GT GT GT GT GG GG GG TT TT NN GT GT GG TT TT GT GT GT GG GT GT TT GT GT GG GG TT GG GT TT GT GG GG GT GT GT GT TT TT GT TT TT GT GT GG GT GT GG GT GT GT GG GG GT GT GT TT GG GG GT GG GT GT GT GT GG TT GG GG GG GT GG TT
+rs2155413 G/T chr11 84361098 - ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:2155413:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 GG GG GT GT GG TT GT GG TT TT TT GT GT GT TT GG GT TT GT GT GG TT TT GT GG TT TT GT GT GT TT TT TT GG NN GT GT GT TT GT GG GT GT GT GT GT GG TT TT GT GT GG GT GT GG TT GT TT GG TT TT GT TT GG GT NN GG GG GG GT GT GG TT GG TT TT TT GT TT GT TT GT TT GG GT GT TT TT TT GT
+rs1073987 C/G chr11 84365007 - ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:1073987:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 CC CC CG CG CC CG CC CC GG GG CG CG CG CG GG CC CC CG CG CG CC GG CG CG CC CC GG CC CG CG CC CC CG CC CC CG CG CG CC CG CC CG CG CG CG CG CC GG GG CC CG CC CC CG CC CC CC CC CC CG CG CG CG CC CG CC CC CC CC CG CG NN CG CC GG GG CG CG CG CG CG CG GG CC CG CG GG GG GG CG
+rs7108021 G/T chr11 84375950 + ncbi_b34 imsut-riken urn:lsid:imsut-riken.hapmap.org:Protocol:genotyping:1 urn:lsid:imsut-riken.hapmap.org:Assay:7108021:1 urn:lsid:dcc.hapmap.org:Panel:CEPH-30-trios:1 TT TT GT GT TT GG GT TT GG GG GG GT GT GT GG TT TT GT GT GT TT GG GT GT TT GT GG GT GT GT GT TT GG TT TT GT GT GT GT GT TT GT GT GT GT NN TT GG GG TT GT TT GT GT TT TT GT GT TT GT GT GT GT TT GT TT TT GT TT GT GT TT GT TT GG GG GT GT GT GT GT GT GG TT GT GT GG GG GG GT
diff --git a/examples/sample.info b/examples/sample.info
new file mode 100755
index 0000000..358d881
--- /dev/null
+++ b/examples/sample.info
@@ -0,0 +1,20 @@
+IGR1118a_1	274044
+IGR1119a_1	274541
+IGR1143a_1	286593
+IGR1144a_1	287261
+IGR1169a_2	299755
+IGR1218a_2	324341
+IGR1219a_2	324379
+IGR1286a_1	358048
+TSC0101718	366811
+IGR1373a_1	395079
+IGR1371a_1	396353
+IGR1369a_2	397334
+IGR1369a_1	397381
+IGR1367a_1	398352
+IGR2008a_2	411823
+IGR2008a_1	411873
+IGR2010a_3	412456
+IGR2011b_1	413233
+IGR2016a_1	415579
+IGR2020a_1      417617
diff --git a/examples/sample.ped b/examples/sample.ped
new file mode 100755
index 0000000..779fdcc
--- /dev/null
+++ b/examples/sample.ped
@@ -0,0 +1,120 @@
+IBD054	430	0	0	1	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	3  3	3  2	1  1	3  3
+IBD054	412	430	431	2	2	1  3	1  3	4  1	4  2	2  1	3  1	4  2	3  2	3  3	2  4	2  4	2  1	1  2	1  3	2  2	2  2	3  3	0  0	1  1	3  3
+IBD054	431	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	0  0	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+IBD058	438	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	3  3	3  2	1  1	3  3
+IBD058	470	438	444	2	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	2  4	2  4	2  1	1  2	1  3	2  2	2  2	3  3	2  3	1  1	3  3
+IBD058	444	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	0  0	2  2	3  3	4  2	0  0	3  1	3  2	1  4	3  3
+IBD069	543	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	0  0	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+IBD069	516	543	513	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	0  0	4  4	4  4	1  1	2  2	3  3	2  4	2  2	3  3	3  2	1  1	3  3
+IBD069	513	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	0  0	2  2	3  3	4  2	2  2	3  3	3  2	1  1	3  3
+IBD076	573	0	0	1	0	0  0	3  1	4  1	4  2	2  1	3  1	2  4	3  2	0  0	4  2	4  2	2  1	2  1	3  1	4  2	3  2	3  1	2  2	1  4	3  3
+IBD076	565	573	574	1	2	0  0	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	1  3	4  2	0  0	1  3	2  3	4  1	3  3
+IBD076	574	0	0	2	0	0  0	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	0  0	1  1	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+IBD092	1011	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  2	4  2	2  1	2  1	3  1	4  2	2  2	3  3	2  2	1  1	3  3
+IBD092	639	1011	641	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	0  0	2  2	3  3	2  4	2  2	3  3	2  3	1  1	3  3
+IBD092	641	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	0  0	2  2	3  3	4  2	3  2	3  1	3  2	1  4	3  3
+IBD119	999	0	0	1	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	0  0	0  0	3  3	3  2	1  1	3  3
+IBD119	734	999	733	1	2	3  3	3  3	1  1	2  2	0  0	1  1	2  2	2  2	3  4	4  4	4  4	1  1	2  2	3  3	4  2	3  2	3  1	2  3	1  4	3  3
+IBD119	733	0	0	2	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	4  3	4  2	4  2	2  1	2  1	3  1	4  2	0  0	3  1	3  2	1  4	3  3
+IBD126	741	0	0	1	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	3  3	3  2	1  1	3  3
+IBD126	944	741	742	1	2	1  3	1  3	4  1	4  2	2  1	3  1	4  2	3  2	3  3	2  4	2  4	2  1	1  2	1  3	2  4	2  3	3  1	2  2	1  4	3  3
+IBD126	742	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  2	3  2	3  1	3  2	1  4	3  3
+IBD128	759	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	3  3	3  2	1  1	3  3
+IBD128	761	759	758	2	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+IBD128	758	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	0  0	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+IBD165	963	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  4	3  3	1  1	2  2	4  4	3  3
+IBD165	846	963	845	1	2	3  3	3  3	1  1	2  2	1  1	0  0	2  2	2  2	3  4	4  4	4  4	1  1	2  2	3  3	4  4	3  3	1  1	2  2	4  4	3  3
+IBD165	845	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	4  3	4  4	4  4	1  1	2  2	3  3	0  0	0  0	3  1	2  2	1  4	3  3
+IBD175	925	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	0  0	3  3	4  4	4  4	1  1	2  2	3  3	4  2	3  2	3  1	3  2	1  4	3  3
+IBD175	972	925	929	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+IBD175	929	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	3  2	1  1	3  3
+IBD182	971	0	0	1	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	3  3	0  0	1  1	3  3
+IBD182	969	971	967	2	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+IBD182	967	0	0	2	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	4  2	2  2	3  3	3  2	1  1	3  3
+IBD186	1014	0	0	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  2	2  2	3  3	2  2	1  1	3  3
+IBD186	1016	1014	1015	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	2  3	1  1	3  3
+IBD186	1015	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	0  0	2  2	3  3	4  2	3  2	3  1	3  2	1  4	3  3
+IBD212	1110	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  2	3  2	3  1	3  2	1  4	3  3
+IBD212	1106	1110	1109	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  4	3  3	1  1	2  2	4  4	3  3
+IBD212	1109	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  4	3  3	1  1	2  2	4  4	3  3
+IBD227	1253	0	0	1	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  4	4  4	1  1	2  2	3  3	4  2	3  2	3  1	3  2	1  4	3  3
+IBD227	1254	1253	1255	1	2	1  3	1  3	4  1	4  2	2  1	3  1	0  0	3  2	3  3	4  4	4  4	0  0	2  2	3  3	4  2	3  2	1  3	2  2	4  1	3  3
+IBD227	1255	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	0  0	2  2	3  3	2  2	2  2	3  3	3  2	1  1	3  3
+IBD4152	1752	0	0	1	0	3  3	0  0	1  1	2  2	1  1	1  1	2  2	0  0	3  3	4  4	4  4	1  1	2  2	3  3	4  2	3  2	3  1	3  2	1  4	3  3
+IBD4152	2041	1752	1751	1	2	3  3	3  3	1  1	2  2	1  1	0  0	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  4	3  2	1  3	2  2	4  1	3  3
+IBD4152	1751	0	0	2	0	3  3	3  3	1  1	2  2	0  0	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  2	2  2	3  3	3  2	1  1	3  3
+TOTDT001	2513	2632	2631	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  2	3  2	0  0	0  0	4  1	3  3
+TOTDT001	2631	0	0	2	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	0  0	0  0	1  1	3  3
+TOTDT001	2632	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	4  3	4  4	4  4	1  1	2  2	3  3	4  4	3  3	0  0	0  0	4  4	3  3
+TOTDT009	2119	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	0  0	2  2	0  0	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	0  0	1  1	3  3
+TOTDT009	2120	0	0	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	0  0	1  1	3  3
+TOTDT009	2146	2120	2119	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	0  0	1  1	2  2	3  3	2  2	2  2	3  3	0  0	1  1	3  3
+TOTDT1030	755	754	762	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  2	3  2	1  3	3  3	0  0	4  3
+TOTDT1030	754	0	0	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	0  0	0  0	3  1	3  2	1  4	0  0
+TOTDT1030	762	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	0  0	2  2	3  3	4  4	4  4	1  1	2  2	3  3	0  0	0  0	3  3	3  3	1  1	0  0
+TOTDT171	956	0	0	2	0	3  3	3  3	1  1	2  2	1  1	0  0	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  4	3  3	1  1	0  0	4  4	3  3
+TOTDT171	2238	955	956	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  4	3  3	1  1	0  0	4  4	3  3
+TOTDT171	955	0	0	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	4  3	4  4	4  4	1  1	2  2	3  3	4  4	3  3	1  1	0  0	4  4	3  3
+TOTDT224	1267	1283	3023	2	2	3  1	3  1	1  4	2  4	1  2	1  3	2  4	2  3	3  4	4  4	4  4	1  1	2  2	3  3	0  0	0  0	1  3	0  0	4  1	3  3
+TOTDT224	1283	0	0	1	0	3  3	3  3	4  1	2  2	1  1	0  0	2  4	2  2	4  3	4  4	4  4	1  1	2  2	3  3	0  0	0  0	3  1	2  2	1  4	3  3
+TOTDT224	3023	0	0	2	0	1  1	1  1	4  4	4  4	0  0	3  3	4  4	3  3	4  3	4  2	4  2	2  1	2  1	3  1	4  2	3  2	3  1	0  0	1  4	4  3
+TOTDT228	1232	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	0  0	2  2	3  3	4  2	2  2	3  3	3  2	1  1	3  3
+TOTDT228	1231	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	0  0	2  2	3  3	4  2	3  2	3  1	3  2	1  4	3  3
+TOTDT228	1242	1231	1232	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+TOTDT233	1287	3488	1411	2	2	1  3	1  3	4  1	4  2	2  1	3  1	4  2	3  2	3  3	2  4	0  0	2  1	1  2	1  3	2  2	2  2	3  3	0  0	0  0	3  3
+TOTDT233	1411	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  2	3  2	3  3	0  0	0  0	3  3
+TOTDT233	3488	0	0	1	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	0  0	0  0	0  0	3  3
+TOTDT236	3595	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	0  0	0  0	3  3
+TOTDT236	3761	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  2	2  2	3  3	0  0	0  0	3  3
+TOTDT236	3887	3595	3761	2	2	3  3	3  3	1  1	2  2	1  1	1  1	0  0	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	0  0	1  1	3  3
+TOTDT240	2497	0	0	1	0	1  3	3  1	4  1	4  2	2  1	0  0	0  0	3  2	0  0	4  2	4  2	2  1	2  1	3  1	0  0	0  0	3  3	0  0	1  1	3  3
+TOTDT240	2498	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	0  0	2  2	4  3	4  4	0  0	1  1	2  2	3  3	4  2	0  0	3  1	0  0	1  4	3  3
+TOTDT240	2638	2497	2498	2	2	3  3	3  3	1  1	2  2	1  1	1  1	0  0	2  2	4  3	4  4	4  4	1  1	2  2	3  3	4  2	0  0	3  1	2  2	1  4	3  3
+TOTDT248	1368	17434	1367	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	0  0	1  1	3  3
+TOTDT248	17434	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	3  2	3  3	0  0	1  1	3  3
+TOTDT248	1367	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	0  0	0  0	3  3	0  0	1  1	3  3
+TOTDT258	1417	1418	1419	1	2	3  1	3  1	1  4	2  4	1  2	1  3	2  4	2  3	3  3	4  2	4  2	1  2	2  1	3  1	4  2	3  2	1  3	2  2	4  1	3  3
+TOTDT258	1418	0	0	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  4	3  3	1  1	2  2	4  4	3  3
+TOTDT258	1419	0	0	2	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	4  3	4  2	4  2	2  1	2  1	3  1	4  2	3  2	3  1	2  2	1  4	3  3
+TOTDT262	1893	2132	2136	2	2	1  3	1  3	4  1	4  2	2  1	3  1	0  0	3  2	3  3	2  4	4  2	2  1	1  2	1  3	2  2	2  2	3  3	0  0	1  1	3  3
+TOTDT262	2132	0	0	1	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	4  2	0  0	3  3	2  2	1  4	3  3
+TOTDT262	2136	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	0  0	1  1	2  2	3  3	2  2	2  2	3  3	0  0	1  1	3  3
+TOTDT268	1616	0	0	1	0	3  3	3  3	4  1	2  2	1  1	3  1	2  2	2  2	4  3	4  2	4  4	0  0	2  2	3  3	4  2	3  2	3  1	3  3	1  4	3  3
+TOTDT268	1508	1616	1615	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	0  0	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+TOTDT268	1615	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	3  2	1  1	3  3
+TOTDT2681	3252	3260	3259	2	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  1	3  3	1  4	0  0
+TOTDT2681	3260	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  1	3  3	1  4	0  0
+TOTDT2681	3259	0	0	2	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	1  1	3  3	4  4	0  0
+TOTDT269	1520	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  2	0  0	3  1	3  2	1  4	0  0
+TOTDT269	1521	1520	1585	1	2	3  3	3  3	1  1	2  2	1  1	0  0	0  0	2  2	3  4	4  4	4  4	1  1	2  2	3  3	2  4	2  3	3  1	3  2	1  4	0  0
+TOTDT269	1585	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	4  3	4  4	4  4	1  1	2  2	3  3	4  4	3  3	1  1	2  2	4  4	0  0
+TOTDT270	1523	16824	1522	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  2	3  2	1  3	2  3	4  1	3  3
+TOTDT270	16824	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  4	3  3	1  1	2  2	4  4	3  3
+TOTDT270	1522	0	0	2	0	3  3	3  3	1  1	2  2	1  1	0  0	2  2	2  2	3  3	4  4	0  0	1  1	2  2	3  3	4  2	3  2	3  1	3  2	1  4	3  3
+TOTDT275	1526	0	0	1	0	1  3	3  1	4  1	4  2	1  1	3  1	2  4	3  2	3  3	4  2	4  4	2  1	2  1	3  1	2  2	2  2	3  3	0  0	1  1	4  3
+TOTDT275	1527	1526	1528	1	2	3  1	1  3	1  4	2  4	0  0	1  3	0  0	2  3	3  3	2  4	4  2	1  2	1  2	1  3	2  2	2  2	3  3	3  2	1  1	4  3
+TOTDT275	1528	0	0	2	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	3  3	3  2	1  1	3  3
+TOTDT277	1678	0	0	1	0	1  3	0  0	4  1	4  2	2  1	3  1	2  4	0  0	3  3	4  4	4  4	1  1	2  2	3  3	2  2	0  0	3  3	3  3	1  1	3  3
+TOTDT277	1570	1678	1708	2	2	3  3	3  3	1  1	2  2	0  0	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	0  0	3  3	3  3	1  1	3  3
+TOTDT277	1708	0	0	2	0	1  3	0  0	4  1	4  2	0  0	3  1	2  4	0  0	0  0	4  2	4  2	2  1	2  1	3  1	2  2	0  0	3  3	3  3	1  1	4  3
+TOTDT279	1552	0	0	2	0	1  3	3  1	4  1	4  2	0  0	3  1	2  4	0  0	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	0  0	1  1	3  3
+TOTDT279	1553	4073	1552	2	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	0  0	1  1	3  3
+TOTDT279	4073	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	0  0	2  2	0  0	4  4	4  4	1  1	2  2	3  3	4  2	3  2	3  1	0  0	1  4	3  3
+TOTDT287	1583	1584	17922	1	2	3  1	1  3	1  4	2  4	1  2	1  3	4  2	2  3	3  3	2  4	2  4	1  2	1  2	1  3	2  2	2  2	3  3	2  3	1  1	3  3
+TOTDT287	1584	0	0	1	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	4  2	3  2	3  1	2  2	1  4	3  3
+TOTDT287	17922	0	0	2	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	3  3	3  2	1  1	3  3
+TOTDT289	1607	2765	1605	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	2  3	1  1	3  3
+TOTDT289	2765	0	0	1	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	3  3	3  2	1  1	3  3
+TOTDT289	1605	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	3  2	1  1	3  3
+TOTDT307	1722	0	0	1	0	3  3	3  3	1  1	2  2	0  0	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+TOTDT307	1723	1722	1721	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+TOTDT307	1721	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	0  0	3  3	4  4	4  4	1  1	2  2	3  3	4  2	3  2	3  1	3  3	1  4	3  3
+TOTDT310	17419	1730	1878	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  2	3  2	1  3	2  3	4  1	3  3
+TOTDT310	1730	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	0  0	1  1	2  2	3  3	4  2	3  2	3  1	3  2	1  4	3  3
+TOTDT310	1878	0	0	2	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	0  0	1  1	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+TOTDT352	2133	17678	1988	1	2	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	2  2	2  2	3  3	3  3	1  1	3  3
+TOTDT352	17678	0	0	1	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	3  3	3  2	1  1	3  3
+TOTDT352	1988	0	0	2	0	1  3	0  0	4  1	4  2	2  1	3  1	2  2	2  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	3  3	3  2	1  1	3  3
+TOTDT353	1989	1991	17702	1	2	3  1	3  1	1  4	2  4	1  2	1  3	2  4	2  3	3  3	4  4	4  4	1  1	2  2	3  3	4  2	3  2	1  3	2  2	4  1	3  3
+TOTDT353	1991	0	0	1	0	3  3	3  3	1  1	2  2	1  1	1  1	2  2	2  2	3  3	4  4	4  4	1  1	2  2	3  3	4  4	3  3	1  1	2  2	4  4	3  3
+TOTDT353	17702	0	0	2	0	1  3	3  1	4  1	4  2	2  1	3  1	2  4	3  2	3  3	4  2	4  2	2  1	2  1	3  1	2  2	2  2	3  3	2  2	1  1	3  3
diff --git a/resources/JimiLicense b/resources/JimiLicense
new file mode 100755
index 0000000..9c93a41
--- /dev/null
+++ b/resources/JimiLicense
@@ -0,0 +1,52 @@
+Sun Microsystems, Inc. 
+Binary Code License Agreement
+
+READ THE TERMS OF THIS AGREEMENT AND ANY PROVIDED SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY "AGREEMENT") CAREFULLY BEFORE DOWNLOADING OR USING THE SOFTWARE. BY DOWNLOADING OR USING THE SOFTWARE, YOU AGREE TO THE TERMS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO ALL THESE TERMS, YOU ARE NOT AUTHORIZED TO DOWNLOAD OR USE THE SOFTWARE IN ANY MANNER.
+
+1. LICENSE TO USE.  Sun grants you a non-exclusive and non-transferable license for the internal use only, for the accompanying software and documentation (collectively "Software"). This is a one time code drop and will not be updated or otherwise supported by Sun.
+
+2. RESTRICTIONS Software is confidential and copyrighted. Title to Software and all associated intellectual property rights is retained by Sun and/or its licensors. Except as specifically authorized in any Supplemental License Terms, you may not make copies of Software, other than a single copy of Software for archival purposes. Unless enforcement is prohibited by applicable law, you may not modify, decompile, reverse engineer Software. 
+
+3. LIMITED WARRANTY. Sun warrants to you that for a period of ninety (90) days from the date of purchase, as evidenced by a copy of the receipt, the media on which Software is furnished (if any) will be free of defects in materials and workmanship under normal use. Except for the foregoing, Software is provided "AS IS". Your exclusive remedy and Sun's entire liability under this limited warranty will be at Sun's option to replace Software media or refund the fee paid for Software.
+
+4. DISCLAIMER OF WARRANTY. UNLESS SPECIFIED IN THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID. YOU ACKNOWLEDGE THAT SOFTWARE IS NOT DESIGNED OR INTENDED FOR USE IN THE DESIGN, CONSTRUCTION, OPERATION, OR MAINTENANCE OF ANY NUCLEAR FACILITY. SUN DISCLAIMS ANY  [...]
+
+5. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. In no event will Sun's liability to you, whether in contract, tort (including n [...]
+paid by you for Software under this Agreement. The foregoing limitations will apply even if the above stated warranty fails of its essential purpose.
+
+6. Termination. This Agreement is effective until terminated. You may terminate this Agreement at any time by destroying all copies of Software. This Agreement will terminate immediately without notice from Sun if you fail to comply with any provision of this Agreement. Upon Termination, you must destroy all copies of Software.
+
+7. Export Regulations. All Software and technical data delivered under this Agreement are subject to US export control laws and may be subject to export or import regulations in other countries. You agree to comply strictly with all such laws and regulations and acknowledge that you have the responsibility to obtain such licenses to export, re-export, or import as may be required after delivery to you.
+
+8. U.S. Government Restricted Rights. Use, duplication, or disclosure by the U.S. Government is subject to restrictions set forth in this Agreement and as provided in DFARS 227.7202-1 (a) and 227.7202-3(a) (1995), DFARS 252.227-7013 (c)(1)(ii)(Oct 1988), FAR 12.212 (a) (1995), FAR 52.227-19 (June 1987), or FAR 52.227-14(ALT III) (June 1987), as applicable.
+
+9. Governing Law. Any action related to this Agreement will be governed by California law and controlling U.S. federal law. No choice of law rules of any jurisdiction will apply.
+
+10. Severability. If any provision of this Agreement is held to be unenforceable, This Agreement will remain in effect with the provision omitted, unless omission would frustrate the intent of the parties, in which case this Agreement will immediately terminate.
+
+11. Integration. This Agreement is the entire agreement between you and Sun relating to its subject matter. It supersedes all prior or contemporaneous oral or written communications, proposals, representations and warranties and prevails over any conflicting or additional terms of any quote, order, acknowledgment, or other communication between the parties relating to its subject matter during the term of this Agreement. No modification of this Agreement will be binding, unless in writin [...]
+
+For inquiries please contact: Sun Microsystems, Inc. 901 San Antonio Road, Palo Alto, California 94303
+
+
+
+JIMI SDK, Version 2.0
+SUPPLEMENTAL LICENSE TERMS
+
+These supplemental terms ("Supplement") add to the terms of the Binary Code License Agreement ("Agreement"). Capitalized terms not defined herein shall have the same meanings ascribed to them in the Agreement. The Supplement terms shall supersede any inconsistent or conflicting terms in the Agreement.
+
+1. Limited License Grant. 
+
+a. Software Development License. Subject to your obligation to indemnify Sun pursuant to Section 3 below, Sun grants to you a non-exclusive, non-transferable limited license to use the Software without fee for evaluation of the Software and for development of Java(TM) applets and applications provided that you may not re-distribute the Software in whole or in part, except as provided in Section 1.b below. The Software may contain source code which is provided for reference purposes only, [...]
+
+b. License to Distribute Runtime. Subject to your obligation to indemnify Sun pursuant to Section 3 below, Sun grants to you a non-exclusive, non-transferable limited, royalty-free license to reproduce, distribute offer to sell and sell the Software provided that you: (i)distribute the Software complete and unmodified (except for error corrections), only as part of, and for the sole purpose of running, your Java applet or application ("Program") into which the Software is incorporated; ( [...]
+specified by Sun in any class file naming convention.
+
+ 2. Java Platform Interface. In the event that Licensee creates an additional API(s) which: (i) extends the functionality of a Java Environment; and, (ii) is exposed to third party software developers for the purpose of developing additional software which invokes such additional API, Licensee must promptly publish broadly an accurate specification for such API for free use by all developers.
+
+3.Indemnity to Sun. As a condition precedent to each license grant in this Agreement, you agree to indemnify, hold harmless, and defend Sun and its licensors from and against any and all claims, lawsuits, liabilities, demands and expenses (including attorneys' fees), that arise or result from the use or distribution of the Software or the Program, including without limitation, those brought by Unisys Corporation, its successors and assigns, with respect to U.S. Patent Number 4,558,302 an [...]
+
+4. Trademarks and Logos. This Agreement does not authorize you to use any Sun name, trademark or logo. Licensee acknowledges as between it and Sun that Sun owns the Java trademark and all Java-related trademarks, logos and icons including the Coffee Cup and Duke ("Java Marks") and agrees to comply with the Java Trademark Guidelines at http://java.sun.com/trademarks.html.
+
+
+
diff --git a/resources/JimiProClasses.zip b/resources/JimiProClasses.zip
new file mode 100755
index 0000000..bfbb9f3
Binary files /dev/null and b/resources/JimiProClasses.zip differ
diff --git a/resources/axiom/axiom-api-1.2.5.jar b/resources/axiom/axiom-api-1.2.5.jar
new file mode 100755
index 0000000..cb2a74c
Binary files /dev/null and b/resources/axiom/axiom-api-1.2.5.jar differ
diff --git a/resources/axiom/axiom-dom-1.2.5.jar b/resources/axiom/axiom-dom-1.2.5.jar
new file mode 100755
index 0000000..8661a95
Binary files /dev/null and b/resources/axiom/axiom-dom-1.2.5.jar differ
diff --git a/resources/axiom/axiom-impl-1.2.5.jar b/resources/axiom/axiom-impl-1.2.5.jar
new file mode 100755
index 0000000..08e9687
Binary files /dev/null and b/resources/axiom/axiom-impl-1.2.5.jar differ
diff --git a/resources/axiom/axiom-license.txt b/resources/axiom/axiom-license.txt
new file mode 100755
index 0000000..43e91eb
--- /dev/null
+++ b/resources/axiom/axiom-license.txt
@@ -0,0 +1,203 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
diff --git a/resources/axiom/commons-logging-1.0.4.jar b/resources/axiom/commons-logging-1.0.4.jar
new file mode 100755
index 0000000..b73a80f
Binary files /dev/null and b/resources/axiom/commons-logging-1.0.4.jar differ
diff --git a/resources/axiom/stax-api-1.0.1.jar b/resources/axiom/stax-api-1.0.1.jar
new file mode 100755
index 0000000..d9a1665
Binary files /dev/null and b/resources/axiom/stax-api-1.0.1.jar differ
diff --git a/resources/axiom/wstx-asl-3.0.0.jar b/resources/axiom/wstx-asl-3.0.0.jar
new file mode 100755
index 0000000..4bbab21
Binary files /dev/null and b/resources/axiom/wstx-asl-3.0.0.jar differ
diff --git a/resources/jfreechart/batik-awt-util.jar b/resources/jfreechart/batik-awt-util.jar
new file mode 100755
index 0000000..b62ac15
Binary files /dev/null and b/resources/jfreechart/batik-awt-util.jar differ
diff --git a/resources/jfreechart/batik-dom.jar b/resources/jfreechart/batik-dom.jar
new file mode 100755
index 0000000..bf144ab
Binary files /dev/null and b/resources/jfreechart/batik-dom.jar differ
diff --git a/resources/jfreechart/batik-ext.jar b/resources/jfreechart/batik-ext.jar
new file mode 100755
index 0000000..e85a270
Binary files /dev/null and b/resources/jfreechart/batik-ext.jar differ
diff --git a/resources/jfreechart/batik-license.txt b/resources/jfreechart/batik-license.txt
new file mode 100755
index 0000000..866b638
--- /dev/null
+++ b/resources/jfreechart/batik-license.txt
@@ -0,0 +1,201 @@
+   Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
\ No newline at end of file
diff --git a/resources/jfreechart/batik-svggen.jar b/resources/jfreechart/batik-svggen.jar
new file mode 100755
index 0000000..819f2da
Binary files /dev/null and b/resources/jfreechart/batik-svggen.jar differ
diff --git a/resources/jfreechart/batik-util.jar b/resources/jfreechart/batik-util.jar
new file mode 100755
index 0000000..7550b48
Binary files /dev/null and b/resources/jfreechart/batik-util.jar differ
diff --git a/resources/jfreechart/batik-xml.jar b/resources/jfreechart/batik-xml.jar
new file mode 100755
index 0000000..1b915a0
Binary files /dev/null and b/resources/jfreechart/batik-xml.jar differ
diff --git a/resources/jfreechart/jcommon-1.0.10.jar b/resources/jfreechart/jcommon-1.0.10.jar
new file mode 100755
index 0000000..bd37fb6
Binary files /dev/null and b/resources/jfreechart/jcommon-1.0.10.jar differ
diff --git a/resources/jfreechart/jfreechart-1.0.6.jar b/resources/jfreechart/jfreechart-1.0.6.jar
new file mode 100755
index 0000000..e8f145f
Binary files /dev/null and b/resources/jfreechart/jfreechart-1.0.6.jar differ
diff --git a/resources/jfreechart/license-LGPL.txt b/resources/jfreechart/license-LGPL.txt
new file mode 100755
index 0000000..cbee875
--- /dev/null
+++ b/resources/jfreechart/license-LGPL.txt
@@ -0,0 +1,504 @@
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+

+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+

+		  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+

+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+

+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+

+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+

+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+

+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+

+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+			    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+

+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/resources/jfreechart/xml-apis.jar b/resources/jfreechart/xml-apis.jar
new file mode 100755
index 0000000..d534585
Binary files /dev/null and b/resources/jfreechart/xml-apis.jar differ
diff --git a/resources/jgrapht-0.5.3.jar b/resources/jgrapht-0.5.3.jar
new file mode 100755
index 0000000..63d0345
Binary files /dev/null and b/resources/jgrapht-0.5.3.jar differ
diff --git a/resources/jgrapht-license.txt b/resources/jgrapht-license.txt
new file mode 100755
index 0000000..cbee875
--- /dev/null
+++ b/resources/jgrapht-license.txt
@@ -0,0 +1,504 @@
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+

+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+

+		  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+

+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+

+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+

+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+

+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+

+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+

+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+			    NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+

+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/resources/jh.jar b/resources/jh.jar
new file mode 100755
index 0000000..25579c7
Binary files /dev/null and b/resources/jh.jar differ
diff --git a/resources/log4j-1.2.14.jar b/resources/log4j-1.2.14.jar
new file mode 100755
index 0000000..6251307
Binary files /dev/null and b/resources/log4j-1.2.14.jar differ
diff --git a/resources/orator.raw b/resources/orator.raw
new file mode 100755
index 0000000..b46206c
Binary files /dev/null and b/resources/orator.raw differ

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/grabix.git



More information about the debian-med-commit mailing list