Merge branch 'features/r2_11_2_alphafold/JAL-629' into features/JAL-4134_treeviewerfo...
authorJames Procter <j.procter@dundee.ac.uk>
Wed, 8 Mar 2023 13:45:11 +0000 (13:45 +0000)
committerJames Procter <j.procter@dundee.ac.uk>
Wed, 8 Mar 2023 13:45:11 +0000 (13:45 +0000)
26 files changed:
src/jalview/analysis/AlignmentSorter.java
src/jalview/analysis/AverageDistanceEngine.java [new file with mode: 0644]
src/jalview/analysis/AverageDistanceTree.java
src/jalview/analysis/NJTree.java
src/jalview/analysis/TreeBuilder.java
src/jalview/analysis/TreeEngine.java [new file with mode: 0644]
src/jalview/analysis/TreeModel.java
src/jalview/api/AlignViewportI.java
src/jalview/appletgui/TreeCanvas.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/BinaryNode.java
src/jalview/datamodel/ContactListImpl.java
src/jalview/datamodel/ContactMatrixI.java
src/jalview/datamodel/SequenceNode.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AnnotationLabels.java
src/jalview/gui/AnnotationPanel.java
src/jalview/gui/TreeCanvas.java
src/jalview/gui/TreePanel.java
src/jalview/io/NewickFile.java
src/jalview/io/vamsas/Tree.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/ws/datamodel/alphafold/PAEContactMatrix.java
src/jalview/ws/dbsources/EBIAlfaFold.java
test/jalview/analysis/AverageDistanceEngineTest.java [new file with mode: 0644]
test/jalview/io/NewickFileTests.java

index 81bddc2..0f3edfd 100755 (executable)
@@ -25,6 +25,7 @@ import jalview.analysis.scoremodels.SimilarityParams;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentOrder;
+import jalview.datamodel.BinaryNode;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -534,7 +535,7 @@ public class AlignmentSorter
    * 
    * @return DOCUMENT ME!
    */
-  private static List<SequenceI> _sortByTree(SequenceNode node,
+  private static List<SequenceI> _sortByTree(BinaryNode node,
           List<SequenceI> tmp, List<SequenceI> seqset)
   {
     if (node == null)
@@ -542,12 +543,12 @@ public class AlignmentSorter
       return tmp;
     }
 
-    SequenceNode left = (SequenceNode) node.left();
-    SequenceNode right = (SequenceNode) node.right();
+    BinaryNode left = (BinaryNode) node.left();
+    BinaryNode right = (BinaryNode) node.right();
 
     if ((left == null) && (right == null))
     {
-      if (!node.isPlaceholder() && (node.element() != null))
+      if (!(node instanceof SequenceNode && ((SequenceNode)node).isPlaceholder()) && (node.element() != null))
       {
         if (node.element() instanceof SequenceI)
         {
diff --git a/src/jalview/analysis/AverageDistanceEngine.java b/src/jalview/analysis/AverageDistanceEngine.java
new file mode 100644 (file)
index 0000000..e6a763b
--- /dev/null
@@ -0,0 +1,384 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview 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 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.analysis;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Vector;
+
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.BinaryNode;
+import jalview.datamodel.ContactListI;
+import jalview.datamodel.ContactMatrixI;
+import jalview.math.Matrix;
+import jalview.viewmodel.AlignmentViewport;
+
+/**
+ * This class implements distance calculations used in constructing a Average
+ * Distance tree (also known as UPGMA)
+ */
+public class AverageDistanceEngine extends TreeEngine
+{
+  ContactMatrixI cm;
+
+  AlignmentViewport av;
+
+  AlignmentAnnotation aa;
+
+  /**
+   * compute cosine distance matrix for a given contact matrix and create a
+   * UPGMA tree
+   * 
+   * @param cm
+   */
+  public AverageDistanceEngine(AlignmentViewport av, AlignmentAnnotation aa,
+          ContactMatrixI cm)
+  {
+    this.av = av;
+    this.aa = aa;
+    this.cm = cm;
+    calculate(cm);
+
+  }
+
+  // 0 - normalised dot product
+  // 1 - L1 - ie (abs(v_1-v_2)/dim(v))
+  // L1 is more rational - since can reason about value of difference,
+  // normalised dot product might give cleaner clusters, but more difficult to
+  // understand.
+
+  int mode = 1;
+
+  public void calculate(ContactMatrixI cm)
+  {
+    this.cm = cm;
+    node = new Vector<BinaryNode>();
+    clusters = new Vector<BitSet>();
+    distances = new Matrix(new double[cm.getWidth()][cm.getWidth()]);
+    noseqs = cm.getWidth();
+    done = new BitSet();
+    double moduli[] = new double[cm.getWidth()];
+    double max;
+    if (mode == 0)
+    {
+      max = 1;
+    }
+    else
+    {
+      max = cm.getMax() * cm.getMax();
+    }
+
+    for (int i = 0; i < cm.getWidth(); i++)
+    {
+      // init the tree engine node for this column
+      BinaryNode cnode = new BinaryNode();
+      cnode.setElement(Integer.valueOf(i));
+      cnode.setName("c" + i);
+      node.addElement(cnode);
+      BitSet bs = new BitSet();
+      bs.set(i);
+      clusters.addElement(bs);
+
+      // compute distance matrix element
+      ContactListI ith = cm.getContactList(i);
+
+      for (int j = 0; j < i; j++)
+      {
+        distances.setValue(i, i, 0);
+        ContactListI jth = cm.getContactList(j);
+        double prd = 0;
+        for (int indx = 0; indx < cm.getHeight(); indx++)
+        {
+          if (mode == 0)
+          {
+            if (j == 0)
+            {
+              moduli[i] += ith.getContactAt(indx) * ith.getContactAt(indx);
+            }
+            prd += ith.getContactAt(indx) * jth.getContactAt(indx);
+          }
+          else
+          {
+            prd += Math
+                    .abs(ith.getContactAt(indx) - jth.getContactAt(indx));
+          }
+        }
+        if (mode == 0)
+        {
+          if (j == 0)
+          {
+            moduli[i] = Math.sqrt(moduli[i]);
+          }
+          prd = (moduli[i] != 0 && moduli[j] != 0)
+                  ? prd / (moduli[i] * moduli[j])
+                  : 0;
+          prd = 1 - prd;
+        }
+        else
+        {
+          prd /= cm.getHeight();
+        }
+        distances.setValue(i, j, prd);
+        distances.setValue(j, i, prd);
+      }
+    }
+
+    noClus = clusters.size();
+    cluster();
+  }
+
+  /**
+   * Calculates and saves the distance between the combination of cluster(i) and
+   * cluster(j) and all other clusters. An average of the distances from
+   * cluster(i) and cluster(j) is calculated, weighted by the sizes of each
+   * cluster.
+   * 
+   * @param i
+   * @param j
+   */
+  @Override
+  protected void findClusterDistance(int i, int j)
+  {
+    int noi = clusters.elementAt(i).cardinality();
+    int noj = clusters.elementAt(j).cardinality();
+
+    // New distances from cluster i to others
+    double[] newdist = new double[noseqs];
+
+    for (int l = 0; l < noseqs; l++)
+    {
+      if ((l != i) && (l != j))
+      {
+        newdist[l] = ((distances.getValue(i, l) * noi)
+                + (distances.getValue(j, l) * noj)) / (noi + noj);
+      }
+      else
+      {
+        newdist[l] = 0;
+      }
+    }
+
+    for (int ii = 0; ii < noseqs; ii++)
+    {
+      distances.setValue(i, ii, newdist[ii]);
+      distances.setValue(ii, i, newdist[ii]);
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected double findMinDistance()
+  {
+    double min = Double.MAX_VALUE;
+
+    for (int i = 0; i < (noseqs - 1); i++)
+    {
+      for (int j = i + 1; j < noseqs; j++)
+      {
+        if (!done.get(i) && !done.get(j))
+        {
+          if (distances.getValue(i, j) < min)
+          {
+            mini = i;
+            minj = j;
+
+            min = distances.getValue(i, j);
+          }
+        }
+      }
+    }
+    return min;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected void findNewDistances(BinaryNode nodei, BinaryNode nodej,
+          double dist)
+  {
+    double ih = 0;
+    double jh = 0;
+
+    BinaryNode sni = nodei;
+    BinaryNode snj = nodej;
+
+    while (sni != null)
+    {
+      ih = ih + sni.dist;
+      sni = (BinaryNode) sni.left();
+    }
+
+    while (snj != null)
+    {
+      jh = jh + snj.dist;
+      snj = (BinaryNode) snj.left();
+    }
+
+    nodei.dist = ((dist / 2) - ih);
+    nodej.dist = ((dist / 2) - jh);
+  }
+
+  /***
+   * not the right place - OH WELL!
+   */
+
+  /**
+   * Makes a list of groups, where each group is represented by a node whose
+   * height (distance from the root node), as a fraction of the height of the
+   * whole tree, is greater than the given threshold. This corresponds to
+   * selecting the nodes immediately to the right of a vertical line
+   * partitioning the tree (if the tree is drawn with root to the left). Each
+   * such node represents a group that contains all of the sequences linked to
+   * the child leaf nodes.
+   * 
+   * @param threshold
+   * @see #getGroups()
+   */
+  public List<BinaryNode> groupNodes(float threshold)
+  {
+    List<BinaryNode> groups = new ArrayList<BinaryNode>();
+    _groupNodes(groups, getTopNode(), threshold);
+    return groups;
+  }
+
+  protected void _groupNodes(List<BinaryNode> groups, BinaryNode nd,
+          float threshold)
+  {
+    if (nd == null)
+    {
+      return;
+    }
+
+    if ((nd.height / maxheight) > threshold)
+    {
+      groups.add(nd);
+    }
+    else
+    {
+      _groupNodes(groups, nd.left(), threshold);
+      _groupNodes(groups, nd.right(), threshold);
+    }
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  public double findHeight(BinaryNode nd)
+  {
+    if (nd == null)
+    {
+      return maxheight;
+    }
+
+    if ((nd.left() == null) && (nd.right() == null))
+    {
+      nd.height = ((BinaryNode) nd.parent()).height + nd.dist;
+
+      if (nd.height > maxheight)
+      {
+        return nd.height;
+      }
+      else
+      {
+        return maxheight;
+      }
+    }
+    else
+    {
+      if (nd.parent() != null)
+      {
+        nd.height = ((BinaryNode) nd.parent()).height + nd.dist;
+      }
+      else
+      {
+        maxheight = 0;
+        nd.height = (float) 0.0;
+      }
+
+      maxheight = findHeight((BinaryNode) (nd.left()));
+      maxheight = findHeight((BinaryNode) (nd.right()));
+    }
+
+    return maxheight;
+  }
+
+  /**
+   * Search for leaf nodes below (or at) the given node
+   * 
+   * @param top2
+   *          root node to search from
+   * 
+   * @return
+   */
+  public Vector<BinaryNode> findLeaves(BinaryNode top2)
+  {
+    Vector<BinaryNode> leaves = new Vector<BinaryNode>();
+    findLeaves(top2, leaves);
+    return leaves;
+  }
+
+  /**
+   * Search for leaf nodes.
+   * 
+   * @param nd
+   *          root node to search from
+   * @param leaves
+   *          Vector of leaves to add leaf node objects too.
+   * 
+   * @return Vector of leaf nodes on binary tree
+   */
+  Vector<BinaryNode> findLeaves(BinaryNode nd, Vector<BinaryNode> leaves)
+  {
+    if (nd == null)
+    {
+      return leaves;
+    }
+
+    if ((nd.left() == null) && (nd.right() == null)) // Interior node
+    // detection
+    {
+      leaves.addElement(nd);
+
+      return leaves;
+    }
+    else
+    {
+      /*
+       * TODO: Identify internal nodes... if (node.isSequenceLabel()) {
+       * leaves.addElement(node); }
+       */
+      findLeaves(nd.left(), leaves);
+      findLeaves(nd.right(), leaves);
+    }
+
+    return leaves;
+  }
+
+}
index c726627..760962e 100644 (file)
@@ -22,7 +22,7 @@ package jalview.analysis;
 
 import jalview.api.analysis.ScoreModelI;
 import jalview.api.analysis.SimilarityParamsI;
-import jalview.datamodel.SequenceNode;
+import jalview.datamodel.BinaryNode;
 import jalview.viewmodel.AlignmentViewport;
 
 /**
@@ -113,25 +113,25 @@ public class AverageDistanceTree extends TreeBuilder
    * {@inheritDoc}
    */
   @Override
-  protected void findNewDistances(SequenceNode nodei, SequenceNode nodej,
+  protected void findNewDistances(BinaryNode nodei, BinaryNode nodej,
           double dist)
   {
     double ih = 0;
     double jh = 0;
 
-    SequenceNode sni = nodei;
-    SequenceNode snj = nodej;
+    BinaryNode sni = nodei;
+    BinaryNode snj = nodej;
 
     while (sni != null)
     {
       ih = ih + sni.dist;
-      sni = (SequenceNode) sni.left();
+      sni = (BinaryNode) sni.left();
     }
 
     while (snj != null)
     {
       jh = jh + snj.dist;
-      snj = (SequenceNode) snj.left();
+      snj = (BinaryNode) snj.left();
     }
 
     nodei.dist = ((dist / 2) - ih);
index 522c2b1..9a39ac0 100644 (file)
@@ -22,7 +22,7 @@ package jalview.analysis;
 
 import jalview.api.analysis.ScoreModelI;
 import jalview.api.analysis.SimilarityParamsI;
-import jalview.datamodel.SequenceNode;
+import jalview.datamodel.BinaryNode;
 import jalview.viewmodel.AlignmentViewport;
 
 /**
@@ -81,7 +81,7 @@ public class NJTree extends TreeBuilder
    * {@inheritDoc}
    */
   @Override
-  protected void findNewDistances(SequenceNode nodei, SequenceNode nodej,
+  protected void findNewDistances(BinaryNode nodei, BinaryNode nodej,
           double dist)
   {
     nodei.dist = ((dist + ri) - rj) / 2;
index 0601dd9..61f65ff 100644 (file)
@@ -23,56 +23,26 @@ package jalview.analysis;
 import jalview.api.analysis.ScoreModelI;
 import jalview.api.analysis.SimilarityParamsI;
 import jalview.datamodel.AlignmentView;
+import jalview.datamodel.BinaryNode;
 import jalview.datamodel.CigarArray;
 import jalview.datamodel.SeqCigar;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.SequenceNode;
-import jalview.math.MatrixI;
 import jalview.viewmodel.AlignmentViewport;
 
 import java.util.BitSet;
 import java.util.Vector;
 
-public abstract class TreeBuilder
+public abstract class TreeBuilder extends TreeEngine
 {
   public static final String AVERAGE_DISTANCE = "AV";
 
   public static final String NEIGHBOUR_JOINING = "NJ";
 
-  protected Vector<BitSet> clusters;
-
   protected SequenceI[] sequences;
 
   public AlignmentView seqData;
 
-  protected BitSet done;
-
-  protected int noseqs;
-
-  int noClus;
-
-  protected MatrixI distances;
-
-  protected int mini;
-
-  protected int minj;
-
-  protected double ri;
-
-  protected double rj;
-
-  SequenceNode maxdist;
-
-  SequenceNode top;
-
-  double maxDistValue;
-
-  double maxheight;
-
-  int ycount;
-
-  Vector<SequenceNode> node;
-
   private AlignmentView seqStrings;
 
   /**
@@ -114,116 +84,6 @@ public abstract class TreeBuilder
   }
 
   /**
-   * DOCUMENT ME!
-   * 
-   * @param nd
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  double findHeight(SequenceNode nd)
-  {
-    if (nd == null)
-    {
-      return maxheight;
-    }
-
-    if ((nd.left() == null) && (nd.right() == null))
-    {
-      nd.height = ((SequenceNode) nd.parent()).height + nd.dist;
-
-      if (nd.height > maxheight)
-      {
-        return nd.height;
-      }
-      else
-      {
-        return maxheight;
-      }
-    }
-    else
-    {
-      if (nd.parent() != null)
-      {
-        nd.height = ((SequenceNode) nd.parent()).height + nd.dist;
-      }
-      else
-      {
-        maxheight = 0;
-        nd.height = (float) 0.0;
-      }
-
-      maxheight = findHeight((SequenceNode) (nd.left()));
-      maxheight = findHeight((SequenceNode) (nd.right()));
-    }
-
-    return maxheight;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param nd
-   *          DOCUMENT ME!
-   */
-  void reCount(SequenceNode nd)
-  {
-    ycount = 0;
-    // _lycount = 0;
-    // _lylimit = this.node.size();
-    _reCount(nd);
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @param nd
-   *          DOCUMENT ME!
-   */
-  void _reCount(SequenceNode nd)
-  {
-    // if (_lycount<_lylimit)
-    // {
-    // System.err.println("Warning: depth of _recount greater than number of
-    // nodes.");
-    // }
-    if (nd == null)
-    {
-      return;
-    }
-    // _lycount++;
-
-    if ((nd.left() != null) && (nd.right() != null))
-    {
-
-      _reCount((SequenceNode) nd.left());
-      _reCount((SequenceNode) nd.right());
-
-      SequenceNode l = (SequenceNode) nd.left();
-      SequenceNode r = (SequenceNode) nd.right();
-
-      nd.count = l.count + r.count;
-      nd.ycount = (l.ycount + r.ycount) / 2;
-    }
-    else
-    {
-      nd.count = 1;
-      nd.ycount = ycount++;
-    }
-    // _lycount--;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public SequenceNode getTopNode()
-  {
-    return top;
-  }
-
-  /**
    * 
    * @return true if tree has real distances
    */
@@ -247,40 +107,6 @@ public abstract class TreeBuilder
   }
 
   /**
-   * Form clusters by grouping sub-clusters, starting from one sequence per
-   * cluster, and finishing when only two clusters remain
-   */
-  void cluster()
-  {
-    while (noClus > 2)
-    {
-      findMinDistance();
-
-      joinClusters(mini, minj);
-
-      noClus--;
-    }
-
-    int rightChild = done.nextClearBit(0);
-    int leftChild = done.nextClearBit(rightChild + 1);
-
-    joinClusters(leftChild, rightChild);
-    top = (node.elementAt(leftChild));
-
-    reCount(top);
-    findHeight(top);
-    findMaxDist(top);
-  }
-
-  /**
-   * Returns the minimum distance between two clusters, and also sets the
-   * indices of the clusters in fields mini and minj
-   * 
-   * @return
-   */
-  protected abstract double findMinDistance();
-
-  /**
    * Calculates the tree using the given score model and parameters, and the
    * configured tree type
    * <p>
@@ -304,67 +130,9 @@ public abstract class TreeBuilder
     cluster();
   }
 
-  /**
-   * Finds the node, at or below the given node, with the maximum distance, and
-   * saves the node and the distance value
-   * 
-   * @param nd
-   */
-  void findMaxDist(SequenceNode nd)
-  {
-    if (nd == null)
-    {
-      return;
-    }
-
-    if ((nd.left() == null) && (nd.right() == null))
-    {
-      double dist = nd.dist;
-
-      if (dist > maxDistValue)
-      {
-        maxdist = nd;
-        maxDistValue = dist;
-      }
-    }
-    else
-    {
-      findMaxDist((SequenceNode) nd.left());
-      findMaxDist((SequenceNode) nd.right());
-    }
-  }
-
-  /**
-   * Calculates and returns r, whatever that is
-   * 
-   * @param i
-   * @param j
-   * 
-   * @return
-   */
-  protected double findr(int i, int j)
-  {
-    double tmp = 1;
-
-    for (int k = 0; k < noseqs; k++)
-    {
-      if ((k != i) && (k != j) && (!done.get(k)))
-      {
-        tmp = tmp + distances.getValue(i, k);
-      }
-    }
-
-    if (noClus > 2)
-    {
-      tmp = tmp / (noClus - 2);
-    }
-
-    return tmp;
-  }
-
   protected void init(AlignmentView seqView, int start, int end)
   {
-    this.node = new Vector<SequenceNode>();
+    this.node = new Vector<BinaryNode>();
     if (seqView != null)
     {
       this.seqData = seqView;
@@ -398,62 +166,6 @@ public abstract class TreeBuilder
   }
 
   /**
-   * Merges cluster(j) to cluster(i) and recalculates cluster and node distances
-   * 
-   * @param i
-   * @param j
-   */
-  void joinClusters(final int i, final int j)
-  {
-    double dist = distances.getValue(i, j);
-
-    ri = findr(i, j);
-    rj = findr(j, i);
-
-    findClusterDistance(i, j);
-
-    SequenceNode sn = new SequenceNode();
-
-    sn.setLeft((node.elementAt(i)));
-    sn.setRight((node.elementAt(j)));
-
-    SequenceNode tmpi = (node.elementAt(i));
-    SequenceNode tmpj = (node.elementAt(j));
-
-    findNewDistances(tmpi, tmpj, dist);
-
-    tmpi.setParent(sn);
-    tmpj.setParent(sn);
-
-    node.setElementAt(sn, i);
-
-    /*
-     * move the members of cluster(j) to cluster(i)
-     * and mark cluster j as out of the game
-     */
-    clusters.get(i).or(clusters.get(j));
-    clusters.get(j).clear();
-    done.set(j);
-  }
-
-  /*
-   * Computes and stores new distances for nodei and nodej, given the previous
-   * distance between them
-   */
-  protected abstract void findNewDistances(SequenceNode nodei,
-          SequenceNode nodej, double previousDistance);
-
-  /**
-   * Calculates and saves the distance between the combination of cluster(i) and
-   * cluster(j) and all other clusters. The form of the calculation depends on
-   * the tree clustering method being used.
-   * 
-   * @param i
-   * @param j
-   */
-  protected abstract void findClusterDistance(int i, int j);
-
-  /**
    * Start by making a cluster for each individual sequence
    */
   void makeLeaves()
diff --git a/src/jalview/analysis/TreeEngine.java b/src/jalview/analysis/TreeEngine.java
new file mode 100644 (file)
index 0000000..daf7836
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview 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 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.analysis;
+
+import java.util.BitSet;
+import java.util.Vector;
+
+import jalview.datamodel.BinaryNode;
+import jalview.datamodel.SequenceNode;
+import jalview.math.MatrixI;
+
+public abstract class TreeEngine
+{
+
+  protected Vector<BitSet> clusters;
+
+  protected BitSet done;
+
+  protected int noseqs;
+
+  protected int noClus;
+
+  protected MatrixI distances;
+
+  protected double ri;
+
+  protected double rj;
+
+  protected Vector<BinaryNode> node;
+
+  BinaryNode maxdist;
+
+  double maxDistValue;
+
+  protected int mini;
+
+  protected int minj;
+
+  protected BinaryNode top;
+
+  protected int ycount;
+
+  double maxheight;
+
+  /**
+   * Calculates and returns r, whatever that is
+   * 
+   * @param i
+   * @param j
+   * 
+   * @return
+   */
+  protected double findr(int i, int j)
+  {
+    double tmp = 1;
+
+    for (int k = 0; k < noseqs; k++)
+    {
+      if ((k != i) && (k != j) && (!done.get(k)))
+      {
+        tmp = tmp + distances.getValue(i, k);
+      }
+    }
+
+    if (noClus > 2)
+    {
+      tmp = tmp / (noClus - 2);
+    }
+
+    return tmp;
+  }
+
+  /**
+   * Merges cluster(j) to cluster(i) and recalculates cluster and node distances
+   * 
+   * @param i
+   * @param j
+   */
+  protected void joinClusters(final int i, final int j)
+  {
+    double dist = distances.getValue(i, j);
+
+    ri = findr(i, j);
+    rj = findr(j, i);
+
+    findClusterDistance(i, j);
+
+    BinaryNode sn = new BinaryNode();
+
+    sn.setLeft((node.elementAt(i)));
+    sn.setRight((node.elementAt(j)));
+
+    BinaryNode tmpi = (node.elementAt(i));
+    BinaryNode tmpj = (node.elementAt(j));
+
+    findNewDistances(tmpi, tmpj, dist);
+
+    tmpi.setParent(sn);
+    tmpj.setParent(sn);
+
+    node.setElementAt(sn, i);
+
+    /*
+     * move the members of cluster(j) to cluster(i)
+     * and mark cluster j as out of the game
+     */
+    clusters.get(i).or(clusters.get(j));
+    clusters.get(j).clear();
+    done.set(j);
+  }
+
+  protected abstract void findNewDistances(BinaryNode nodei,
+          BinaryNode nodej, double previousDistance);
+
+  /**
+   * Calculates and saves the distance between the combination of cluster(i) and
+   * cluster(j) and all other clusters. The form of the calculation depends on
+   * the tree clustering method being used.
+   * 
+   * @param i
+   * @param j
+   */
+  protected abstract void findClusterDistance(int i, int j);
+
+  /**
+   * Finds the node, at or below the given node, with the maximum distance, and
+   * saves the node and the distance value
+   * 
+   * @param nd
+   */
+  protected void findMaxDist(BinaryNode nd)
+  {
+    if (nd == null)
+    {
+      return;
+    }
+
+    if ((nd.left() == null) && (nd.right() == null))
+    {
+      double dist = nd.dist;
+
+      if (dist > maxDistValue)
+      {
+        maxdist = nd;
+        maxDistValue = dist;
+      }
+    }
+    else
+    {
+      findMaxDist((BinaryNode) nd.left());
+      findMaxDist((BinaryNode) nd.right());
+    }
+  }
+
+  /**
+   * Form clusters by grouping sub-clusters, starting from one sequence per
+   * cluster, and finishing when only two clusters remain
+   */
+  protected void cluster()
+  {
+    while (noClus > 2)
+    {
+      findMinDistance();
+
+      joinClusters(mini, minj);
+
+      noClus--;
+    }
+
+    int rightChild = done.nextClearBit(0);
+    int leftChild = done.nextClearBit(rightChild + 1);
+
+    joinClusters(leftChild, rightChild);
+    top = (node.elementAt(leftChild));
+
+    reCount(top);
+    findHeight(top);
+    findMaxDist(top);
+  }
+
+  /**
+   * Returns the minimum distance between two clusters, and also sets the
+   * indices of the clusters in fields mini and minj
+   * 
+   * @return
+   */
+  protected abstract double findMinDistance();
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   */
+  protected void _reCount(BinaryNode nd)
+  {
+    // if (_lycount<_lylimit)
+    // {
+    // System.err.println("Warning: depth of _recount greater than number of
+    // nodes.");
+    // }
+    if (nd == null)
+    {
+      return;
+    }
+    // _lycount++;
+
+    if ((nd.left() != null) && (nd.right() != null))
+    {
+
+      _reCount(nd.left());
+      _reCount((BinaryNode) nd.right());
+
+      BinaryNode l = nd.left();
+      BinaryNode r = nd.right();
+
+      nd.count = l.count + r.count;
+      nd.ycount = (l.ycount + r.ycount) / 2;
+    }
+    else
+    {
+      nd.count = 1;
+      nd.ycount = ycount++;
+    }
+    // _lycount--;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  double findHeight(BinaryNode nd)
+  {
+    if (nd == null)
+    {
+      return maxheight;
+    }
+
+    if ((nd.left() == null) && (nd.right() == null))
+    {
+      nd.height = ((BinaryNode) nd.parent()).height + nd.dist;
+
+      if (nd.height > maxheight)
+      {
+        return nd.height;
+      }
+      else
+      {
+        return maxheight;
+      }
+    }
+    else
+    {
+      if (nd.parent() != null)
+      {
+        nd.height = ((BinaryNode) nd.parent()).height + nd.dist;
+      }
+      else
+      {
+        maxheight = 0;
+        nd.height = (float) 0.0;
+      }
+
+      maxheight = findHeight((BinaryNode) (nd.left()));
+      maxheight = findHeight((BinaryNode) (nd.right()));
+    }
+
+    return maxheight;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param nd
+   *          DOCUMENT ME!
+   */
+  void reCount(BinaryNode nd)
+  {
+    ycount = 0;
+    // _lycount = 0;
+    // _lylimit = this.node.size();
+    _reCount(nd);
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  public BinaryNode getTopNode()
+  {
+    return top;
+  }
+
+}
index 4d5e4b2..dd56424 100644 (file)
@@ -20,7 +20,6 @@
  */
 package jalview.analysis;
 
-import jalview.bin.Cache;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.BinaryNode;
 import jalview.datamodel.NodeTransformI;
@@ -51,7 +50,7 @@ public class TreeModel
 
   int noseqs;
 
-  SequenceNode top;
+  BinaryNode top;
 
   double maxDistValue;
 
@@ -59,7 +58,7 @@ public class TreeModel
 
   int ycount;
 
-  Vector<SequenceNode> node;
+  Vector<BinaryNode> node;
 
   boolean hasDistances = true; // normal case for jalview trees
 
@@ -81,7 +80,7 @@ public class TreeModel
   public TreeModel(SequenceI[] seqs, AlignmentView odata,
           NewickFile treefile)
   {
-    this(seqs, treefile.getTree(), treefile.HasDistances(),
+    this(seqs,  treefile.getTree(), treefile.HasDistances(),
             treefile.HasBootstrap(), treefile.HasRootDistance());
     seqData = odata;
 
@@ -95,7 +94,7 @@ public class TreeModel
    */
   public TreeModel(TreeBuilder tree)
   {
-    this(tree.getSequences(), tree.getTopNode(), tree.hasDistances(),
+    this(tree.getSequences(),  tree.getTopNode(), tree.hasDistances(),
             tree.hasBootstrap(), tree.hasRootDistance());
     seqData = tree.getOriginalData();
   }
@@ -109,7 +108,7 @@ public class TreeModel
    * @param hasBoot
    * @param hasRootDist
    */
-  public TreeModel(SequenceI[] seqs, SequenceNode root, boolean hasDist,
+  public TreeModel(SequenceI[] seqs, BinaryNode root, boolean hasDist,
           boolean hasBoot, boolean hasRootDist)
   {
     this.sequences = seqs;
@@ -129,7 +128,7 @@ public class TreeModel
   {
     SequenceIdMatcher algnIds = new SequenceIdMatcher(seqs);
 
-    Vector<SequenceNode> leaves = findLeaves(top);
+    Vector<BinaryNode> leaves = findLeaves(top);
 
     int i = 0;
     int namesleft = seqs.length;
@@ -141,7 +140,8 @@ public class TreeModel
     // int countOne2Many = 0;
     while (i < leaves.size())
     {
-      j = leaves.elementAt(i++);
+      // TODO - decide if we get rid of the polymorphism here ?
+      j = (SequenceNode)leaves.elementAt(i++);
       realnam = j.getName();
       nam = null;
 
@@ -206,7 +206,7 @@ public class TreeModel
    */
   public void updatePlaceHolders(List<SequenceI> list)
   {
-    Vector<SequenceNode> leaves = findLeaves(top);
+    Vector<BinaryNode> leaves = findLeaves(top);
 
     int sz = leaves.size();
     SequenceIdMatcher seqmatcher = null;
@@ -214,7 +214,7 @@ public class TreeModel
 
     while (i < sz)
     {
-      SequenceNode leaf = leaves.elementAt(i++);
+      SequenceNode leaf = (SequenceNode) leaves.elementAt(i++);
 
       if (list.contains(leaf.element()))
       {
@@ -289,15 +289,15 @@ public class TreeModel
   /**
    * Search for leaf nodes below (or at) the given node
    * 
-   * @param nd
+   * @param top2
    *          root node to search from
    * 
    * @return
    */
-  public Vector<SequenceNode> findLeaves(SequenceNode nd)
+  public Vector<BinaryNode> findLeaves(BinaryNode top2)
   {
-    Vector<SequenceNode> leaves = new Vector<SequenceNode>();
-    findLeaves(nd, leaves);
+    Vector<BinaryNode> leaves = new Vector<BinaryNode>();
+    findLeaves(top2, leaves);
     return leaves;
   }
 
@@ -311,8 +311,8 @@ public class TreeModel
    * 
    * @return Vector of leaf nodes on binary tree
    */
-  Vector<SequenceNode> findLeaves(SequenceNode nd,
-          Vector<SequenceNode> leaves)
+  Vector<BinaryNode> findLeaves(BinaryNode nd,
+          Vector<BinaryNode> leaves)
   {
     if (nd == null)
     {
@@ -332,8 +332,8 @@ public class TreeModel
        * TODO: Identify internal nodes... if (node.isSequenceLabel()) {
        * leaves.addElement(node); }
        */
-      findLeaves((SequenceNode) nd.left(), leaves);
-      findLeaves((SequenceNode) nd.right(), leaves);
+      findLeaves(nd.left(), leaves);
+      findLeaves(nd.right(), leaves);
     }
 
     return leaves;
@@ -345,7 +345,7 @@ public class TreeModel
    * @param nd
    *          SequenceNode
    */
-  void printNode(SequenceNode nd)
+  void printNode(BinaryNode nd)
   {
     if (nd == null)
     {
@@ -361,8 +361,8 @@ public class TreeModel
     else
     {
       System.out.println("Dist " + nd.dist);
-      printNode((SequenceNode) nd.left());
-      printNode((SequenceNode) nd.right());
+      printNode((BinaryNode) nd.left());
+      printNode((BinaryNode) nd.right());
     }
   }
 
@@ -388,14 +388,14 @@ public class TreeModel
    * @param threshold
    * @see #getGroups()
    */
-  public List<SequenceNode> groupNodes(float threshold)
+  public List<BinaryNode> groupNodes(float threshold)
   {
-    List<SequenceNode> groups = new ArrayList<SequenceNode>();
+    List<BinaryNode> groups = new ArrayList<BinaryNode>();
     _groupNodes(groups, getTopNode(), threshold);
     return groups;
   }
 
-  protected void _groupNodes(List<SequenceNode> groups, SequenceNode nd,
+  protected void _groupNodes(List<BinaryNode> groups, BinaryNode nd,
           float threshold)
   {
     if (nd == null)
@@ -422,7 +422,7 @@ public class TreeModel
    * 
    * @return DOCUMENT ME!
    */
-  public double findHeight(SequenceNode nd)
+  public double findHeight(BinaryNode nd)
   {
     if (nd == null)
     {
@@ -431,7 +431,7 @@ public class TreeModel
 
     if ((nd.left() == null) && (nd.right() == null))
     {
-      nd.height = ((SequenceNode) nd.parent()).height + nd.dist;
+      nd.height = ((BinaryNode) nd.parent()).height + nd.dist;
 
       if (nd.height > maxheight)
       {
@@ -446,7 +446,7 @@ public class TreeModel
     {
       if (nd.parent() != null)
       {
-        nd.height = ((SequenceNode) nd.parent()).height + nd.dist;
+        nd.height = ((BinaryNode) nd.parent()).height + nd.dist;
       }
       else
       {
@@ -454,8 +454,8 @@ public class TreeModel
         nd.height = (float) 0.0;
       }
 
-      maxheight = findHeight((SequenceNode) (nd.left()));
-      maxheight = findHeight((SequenceNode) (nd.right()));
+      maxheight = findHeight((BinaryNode) (nd.left()));
+      maxheight = findHeight((BinaryNode) (nd.right()));
     }
 
     return maxheight;
@@ -467,7 +467,7 @@ public class TreeModel
    * @param nd
    *          DOCUMENT ME!
    */
-  void printN(SequenceNode nd)
+  void printN(BinaryNode nd)
   {
     if (nd == null)
     {
@@ -476,8 +476,8 @@ public class TreeModel
 
     if ((nd.left() != null) && (nd.right() != null))
     {
-      printN((SequenceNode) nd.left());
-      printN((SequenceNode) nd.right());
+      printN((BinaryNode) nd.left());
+      printN((BinaryNode) nd.right());
     }
     else
     {
@@ -494,7 +494,7 @@ public class TreeModel
    * @param nd
    *          DOCUMENT ME!
    */
-  public void reCount(SequenceNode nd)
+  public void reCount(BinaryNode nd)
   {
     ycount = 0;
     // _lycount = 0;
@@ -510,7 +510,7 @@ public class TreeModel
    * @param nd
    *          DOCUMENT ME!
    */
-  void _reCount(SequenceNode nd)
+  void _reCount(BinaryNode nd)
   {
     // if (_lycount<_lylimit)
     // {
@@ -526,11 +526,11 @@ public class TreeModel
     if ((nd.left() != null) && (nd.right() != null))
     {
 
-      _reCount((SequenceNode) nd.left());
-      _reCount((SequenceNode) nd.right());
+      _reCount((BinaryNode) nd.left());
+      _reCount((BinaryNode) nd.right());
 
-      SequenceNode l = (SequenceNode) nd.left();
-      SequenceNode r = (SequenceNode) nd.right();
+      BinaryNode l = (BinaryNode) nd.left();
+      BinaryNode r = (BinaryNode) nd.right();
 
       nd.count = l.count + r.count;
       nd.ycount = (l.ycount + r.ycount) / 2;
@@ -549,14 +549,14 @@ public class TreeModel
    * @param nd
    *          DOCUMENT ME!
    */
-  public void swapNodes(SequenceNode nd)
+  public void swapNodes(BinaryNode nd)
   {
     if (nd == null)
     {
       return;
     }
 
-    SequenceNode tmp = (SequenceNode) nd.left();
+    BinaryNode tmp = (BinaryNode) nd.left();
 
     nd.setLeft(nd.right());
     nd.setRight(tmp);
@@ -570,7 +570,7 @@ public class TreeModel
    * @param dir
    *          DOCUMENT ME!
    */
-  void changeDirection(SequenceNode nd, SequenceNode dir)
+  void changeDirection(BinaryNode nd, BinaryNode dir)
   {
     if (nd == null)
     {
@@ -579,9 +579,9 @@ public class TreeModel
 
     if (nd.parent() != top)
     {
-      changeDirection((SequenceNode) nd.parent(), nd);
+      changeDirection((BinaryNode) nd.parent(), nd);
 
-      SequenceNode tmp = (SequenceNode) nd.parent();
+      BinaryNode tmp = (BinaryNode) nd.parent();
 
       if (dir == nd.left())
       {
@@ -630,7 +630,7 @@ public class TreeModel
    * 
    * @return DOCUMENT ME!
    */
-  public SequenceNode getTopNode()
+  public BinaryNode getTopNode()
   {
     return top;
   }
@@ -665,7 +665,7 @@ public class TreeModel
    */
   public void applyToNodes(NodeTransformI nodeTransformI)
   {
-    for (Enumeration<SequenceNode> nodes = node.elements(); nodes
+    for (Enumeration<BinaryNode> nodes = node.elements(); nodes
             .hasMoreElements(); nodeTransformI
                     .transform(nodes.nextElement()))
     {
index b09538e..03efec5 100644 (file)
@@ -28,6 +28,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.ContactListI;
+import jalview.datamodel.ContactMatrixI;
 import jalview.datamodel.ProfilesI;
 import jalview.datamodel.SearchResultsI;
 import jalview.datamodel.SequenceCollectionI;
@@ -555,4 +556,6 @@ public interface AlignViewportI extends ViewStyleI
    * @return
    */
   Iterator<int[]> getViewAsVisibleContigs(boolean selectedRegionOnly);
+
+  ContactMatrixI getContactMatrix(AlignmentAnnotation alignmentAnnotation);
 }
index 2e5f938..8c3e39a 100755 (executable)
@@ -23,6 +23,7 @@ package jalview.appletgui;
 import jalview.analysis.Conservation;
 import jalview.analysis.TreeModel;
 import jalview.api.AlignViewportI;
+import jalview.datamodel.BinaryNode;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -86,7 +87,7 @@ public class TreeCanvas extends Panel
 
   Hashtable nodeHash = new Hashtable();
 
-  SequenceNode highlightNode;
+  BinaryNode highlightNode;
 
   AlignmentPanel ap;
 
@@ -122,15 +123,15 @@ public class TreeCanvas extends Panel
     tree2.findHeight(tree2.getTopNode());
 
     // Now have to calculate longest name based on the leaves
-    Vector<SequenceNode> leaves = tree2.findLeaves(tree2.getTopNode());
+    Vector<BinaryNode> leaves = tree2.findLeaves(tree2.getTopNode());
     boolean has_placeholders = false;
     longestName = "";
 
     for (int i = 0; i < leaves.size(); i++)
     {
-      SequenceNode lf = leaves.elementAt(i);
+      BinaryNode lf = leaves.elementAt(i);
 
-      if (lf.isPlaceholder())
+      if (lf instanceof SequenceNode && ((SequenceNode)lf).isPlaceholder())
       {
         has_placeholders = true;
       }
@@ -146,7 +147,7 @@ public class TreeCanvas extends Panel
     setMarkPlaceholders(has_placeholders);
   }
 
-  public void drawNode(Graphics g, SequenceNode node, float chunk,
+  public void drawNode(Graphics g, BinaryNode node, float chunk,
           double scale, int width, int offx, int offy)
   {
     if (node == null)
@@ -210,7 +211,7 @@ public class TreeCanvas extends Panel
         g.drawString(nodeLabel, xstart + 2, ypos - 2);
       }
 
-      String name = (markPlaceholders && node.isPlaceholder())
+      String name = (markPlaceholders && node instanceof SequenceNode && ((SequenceNode) node).isPlaceholder())
               ? (PLACEHOLDER + node.getName())
               : node.getName();
       FontMetrics fm = g.getFontMetrics(font);
@@ -237,9 +238,9 @@ public class TreeCanvas extends Panel
     }
     else
     {
-      drawNode(g, (SequenceNode) node.left(), chunk, scale, width, offx,
+      drawNode(g, (BinaryNode) node.left(), chunk, scale, width, offx,
               offy);
-      drawNode(g, (SequenceNode) node.right(), chunk, scale, width, offx,
+      drawNode(g, (BinaryNode) node.right(), chunk, scale, width, offx,
               offy);
 
       double height = node.height;
@@ -263,9 +264,9 @@ public class TreeCanvas extends Panel
       }
 
       int ystart = (int) (node.left() == null ? 0
-              : (((SequenceNode) node.left()).ycount * chunk)) + offy;
+              : (((BinaryNode) node.left()).ycount * chunk)) + offy;
       int yend = (int) (node.right() == null ? 0
-              : (((SequenceNode) node.right()).ycount * chunk)) + offy;
+              : (((BinaryNode) node.right()).ycount * chunk)) + offy;
 
       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
       nodeHash.put(node, pos);
@@ -338,20 +339,20 @@ public class TreeCanvas extends Panel
     int width = getSize().width;
     int height = getSize().height;
 
-    SequenceNode top = tree.getTopNode();
+    BinaryNode top = tree.getTopNode();
 
     double wscale = (float) (width * .8 - offx * 2) / tree.getMaxHeight();
     if (top.count == 0)
     {
-      top.count = ((SequenceNode) top.left()).count
-              + ((SequenceNode) top.right()).count;
+      top.count = ((BinaryNode) top.left()).count
+              + ((BinaryNode) top.right()).count;
     }
     float chunk = (float) (height - offy) / top.count;
 
     pickNode(pickBox, top, chunk, wscale, width, offx, offy);
   }
 
-  public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
+  public void pickNode(Rectangle pickBox, BinaryNode node, float chunk,
           double scale, int width, int offx, int offy)
   {
     if (node == null)
@@ -384,14 +385,14 @@ public class TreeCanvas extends Panel
     }
     else
     {
-      pickNode(pickBox, (SequenceNode) node.left(), chunk, scale, width,
+      pickNode(pickBox, (BinaryNode) node.left(), chunk, scale, width,
               offx, offy);
-      pickNode(pickBox, (SequenceNode) node.right(), chunk, scale, width,
+      pickNode(pickBox, (BinaryNode) node.right(), chunk, scale, width,
               offx, offy);
     }
   }
 
-  public void setColor(SequenceNode node, Color c)
+  public void setColor(BinaryNode node, Color c)
   {
     if (node == null)
     {
@@ -410,8 +411,8 @@ public class TreeCanvas extends Panel
     else
     {
       node.color = c;
-      setColor((SequenceNode) node.left(), c);
-      setColor((SequenceNode) node.right(), c);
+      setColor((BinaryNode) node.left(), c);
+      setColor((BinaryNode) node.right(), c);
     }
   }
 
@@ -469,12 +470,12 @@ public class TreeCanvas extends Panel
 
     double wscale = (width - labelLength - offx * 2) / tree.getMaxHeight();
 
-    SequenceNode top = tree.getTopNode();
+    BinaryNode top = tree.getTopNode();
 
     if (top.count == 0)
     {
-      top.count = ((SequenceNode) top.left()).count
-              + ((SequenceNode) top.right()).count;
+      top.count = ((BinaryNode) top.left()).count
+              + ((BinaryNode) top.right()).count;
     }
     float chunk = (float) (height - offy) / top.count;
 
@@ -527,7 +528,7 @@ public class TreeCanvas extends Panel
       }
       else
       {
-        Vector<SequenceNode> leaves = tree.findLeaves(highlightNode);
+        Vector<BinaryNode> leaves = tree.findLeaves(highlightNode);
 
         for (int i = 0; i < leaves.size(); i++)
         {
@@ -554,9 +555,9 @@ public class TreeCanvas extends Panel
 
     Object ob = findElement(evt.getX(), evt.getY());
 
-    if (ob instanceof SequenceNode)
+    if (ob instanceof BinaryNode)
     {
-      highlightNode = (SequenceNode) ob;
+      highlightNode = (BinaryNode) ob;
       repaint();
     }
     else
@@ -596,7 +597,7 @@ public class TreeCanvas extends Panel
         threshold = (float) (x - offx)
                 / (float) (getSize().width - labelLength - 2 * offx);
 
-        List<SequenceNode> groups = tree.groupNodes(threshold);
+        List<BinaryNode> groups = tree.groupNodes(threshold);
         setColor(tree.getTopNode(), Color.black);
 
         av.setSelectionGroup(null);
@@ -620,7 +621,7 @@ public class TreeCanvas extends Panel
 
   }
 
-  void colourGroups(List<SequenceNode> groups)
+  void colourGroups(List<BinaryNode> groups)
   {
     for (int i = 0; i < groups.size(); i++)
     {
@@ -629,7 +630,7 @@ public class TreeCanvas extends Panel
               (int) (Math.random() * 255), (int) (Math.random() * 255));
       setColor(groups.get(i), col.brighter());
 
-      Vector<SequenceNode> l = tree.findLeaves(groups.get(i));
+      Vector<BinaryNode> l = tree.findLeaves(groups.get(i));
 
       Vector<SequenceI> sequences = new Vector<>();
       for (int j = 0; j < l.size(); j++)
index 7f97f33..321eee3 100755 (executable)
@@ -2047,9 +2047,23 @@ public class Alignment implements AlignmentI, AutoCloseable
   }
 
   @Override
-  public ContactMatrixI getContactMatrixFor(AlignmentAnnotation ann)
+  public ContactMatrixI getContactMatrixFor(AlignmentAnnotation _aa)
   {
-    return cmholder.getContactMatrixFor(ann);
+    ContactMatrixI cm = cmholder.getContactMatrixFor(_aa);
+    if (cm==null && _aa.groupRef!=null)
+    {
+      cm = _aa.groupRef.getContactMatrixFor(_aa);
+    }
+    if (cm==null && _aa.sequenceRef!=null)
+    {
+      cm = _aa.sequenceRef.getContactMatrixFor(_aa);
+      if (cm==null)
+      {
+        // TODO fix up this logic and unify with getContactListFor
+        cm = _aa.sequenceRef.getDatasetSequence().getContactMatrixFor(_aa);
+      }
+    }
+    return cm;
   }
 
   @Override
index c0ff0e4..5b55594 100755 (executable)
@@ -20,6 +20,8 @@
  */
 package jalview.datamodel;
 
+import java.awt.Color;
+
 /**
  * DOCUMENT ME!
  * 
@@ -38,9 +40,27 @@ public class BinaryNode
 
   BinaryNode parent;
 
-  /** DOCUMENT ME!! */
+  /** Bootstrap value */
   public int bootstrap;
 
+  /** DOCUMENT ME!! */
+  public double dist;
+
+  /** DOCUMENT ME!! */
+  public int count;
+
+  /** DOCUMENT ME!! */
+  public double height;
+
+  /** DOCUMENT ME!! */
+  public float ycount;
+
+  /** DOCUMENT ME!! */
+  public Color color = Color.black;
+
+  /** DOCUMENT ME!! */
+  public boolean dummy = false;
+
   /**
    * Creates a new BinaryNode object.
    */
@@ -48,6 +68,7 @@ public class BinaryNode
   {
     left = right = parent = null;
     bootstrap = 0;
+    dist = 0;
   }
 
   /**
@@ -60,13 +81,28 @@ public class BinaryNode
    * @param name
    *          DOCUMENT ME!
    */
-  public BinaryNode(Object element, BinaryNode parent, String name)
+  public BinaryNode(Object element, BinaryNode parent, String name,
+          double dist)
   {
+    this();
     this.element = element;
     this.parent = parent;
     this.name = name;
+    this.dist = dist;
+  }
+
+  public BinaryNode(Object element, BinaryNode parent, String name,
+          double dist, int bootstrap)
+  {
+    this(element, parent, name, dist);
+    this.bootstrap = bootstrap;
+  }
 
-    left = right = null;
+  public BinaryNode(Object val, BinaryNode parent, String name, double dist,
+          int bootstrap, boolean dummy)
+  {
+    this(val, parent, name, dist, bootstrap);
+    this.dummy = dummy;
   }
 
   /**
@@ -300,4 +336,45 @@ public class BinaryNode
   {
     return bootstrap;
   }
+
+  /**
+   * @param dummy
+   *          true if node is created for the representation of polytomous trees
+   */
+  public boolean isDummy()
+  {
+    return dummy;
+  }
+
+  /**
+   * DOCUMENT ME!
+   * 
+   * @param newstate
+   *          DOCUMENT ME!
+   * 
+   * @return DOCUMENT ME!
+   */
+  public boolean setDummy(boolean newstate)
+  {
+    boolean oldstate = dummy;
+    dummy = newstate;
+
+    return oldstate;
+  }
+
+  /**
+   * ascends the tree but doesn't stop until a non-dummy node is discovered.
+   * 
+   */
+  public BinaryNode AscendTree()
+  {
+    BinaryNode c = this;
+
+    do
+    {
+      c = c.parent();
+    } while ((c != null) && c.dummy);
+
+    return c;
+  }
 }
index beb557f..8e806e4 100644 (file)
@@ -41,6 +41,7 @@ public class ContactListImpl implements ContactListI
   @Override
   public ContactRange getRangeFor(int from_column, int to_column)
   {
+    // TODO: consider caching ContactRange for a particular call ?
     if (clist instanceof ContactListI)
     {
       // clist may implement getRangeFor in a more efficient way, so use theirs
index 2367414..4e2076d 100644 (file)
@@ -1,5 +1,7 @@
 package jalview.datamodel;
 
+import java.util.BitSet;
+
 public interface ContactMatrixI
 {
 
@@ -25,5 +27,14 @@ public interface ContactMatrixI
 
   int getWidth();
   int getHeight();
+  
+  default boolean hasGroups() {
+    return false;
+  }
+  default BitSet getGroupsFor(int column) {
+    BitSet colbitset  = new BitSet();
+    colbitset.set(column);
+    return colbitset;
+  }
 
 }
index 0a694c2..010d4aa 100755 (executable)
@@ -20,8 +20,6 @@
  */
 package jalview.datamodel;
 
-import java.awt.Color;
-
 /**
  * DOCUMENT ME!
  * 
@@ -30,24 +28,6 @@ import java.awt.Color;
  */
 public class SequenceNode extends BinaryNode
 {
-  /** DOCUMENT ME!! */
-  public double dist;
-
-  /** DOCUMENT ME!! */
-  public int count;
-
-  /** DOCUMENT ME!! */
-  public double height;
-
-  /** DOCUMENT ME!! */
-  public float ycount;
-
-  /** DOCUMENT ME!! */
-  public Color color = Color.black;
-
-  /** DOCUMENT ME!! */
-  public boolean dummy = false;
-
   private boolean placeholder = false;
 
   /**
@@ -58,57 +38,22 @@ public class SequenceNode extends BinaryNode
     super();
   }
 
-  /**
-   * Creates a new SequenceNode object.
-   * 
-   * @param val
-   *          DOCUMENT ME!
-   * @param parent
-   *          DOCUMENT ME!
-   * @param dist
-   *          DOCUMENT ME!
-   * @param name
-   *          DOCUMENT ME!
-   */
-  public SequenceNode(Object val, SequenceNode parent, double dist,
-          String name)
+  public SequenceNode(SequenceI val, BinaryNode parent, String name,
+          double dist, int bootstrap, boolean dummy)
   {
-    super(val, parent, name);
-    this.dist = dist;
+    super(val, parent, name, dist, bootstrap, dummy);
   }
 
-  /**
-   * Creates a new SequenceNode object.
-   * 
-   * @param val
-   *          DOCUMENT ME!
-   * @param parent
-   *          DOCUMENT ME!
-   * @param name
-   *          DOCUMENT ME!
-   * @param dist
-   *          DOCUMENT ME!
-   * @param bootstrap
-   *          DOCUMENT ME!
-   * @param dummy
-   *          DOCUMENT ME!
-   */
-  public SequenceNode(Object val, SequenceNode parent, String name,
-          double dist, int bootstrap, boolean dummy)
+  public SequenceNode(SequenceI element, BinaryNode parent, String name,
+          double dist, int bootstrap)
   {
-    super(val, parent, name);
-    this.dist = dist;
-    this.bootstrap = bootstrap;
-    this.dummy = dummy;
+    super(element, parent, name, dist, bootstrap);
   }
 
-  /**
-   * @param dummy
-   *          true if node is created for the representation of polytomous trees
-   */
-  public boolean isDummy()
+  public SequenceNode(SequenceI element, BinaryNode parent, String name,
+          double dist)
   {
-    return dummy;
+    super(element, parent, name, dist);
   }
 
   /*
@@ -123,22 +68,6 @@ public class SequenceNode extends BinaryNode
   /**
    * DOCUMENT ME!
    * 
-   * @param newstate
-   *          DOCUMENT ME!
-   * 
-   * @return DOCUMENT ME!
-   */
-  public boolean setDummy(boolean newstate)
-  {
-    boolean oldstate = dummy;
-    dummy = newstate;
-
-    return oldstate;
-  }
-
-  /**
-   * DOCUMENT ME!
-   * 
    * @param Placeholder
    *          DOCUMENT ME!
    */
@@ -148,23 +77,6 @@ public class SequenceNode extends BinaryNode
   }
 
   /**
-   * ascends the tree but doesn't stop until a non-dummy node is discovered.
-   * This will probably break if the tree is a mixture of BinaryNodes and
-   * SequenceNodes.
-   */
-  public SequenceNode AscendTree()
-  {
-    SequenceNode c = this;
-
-    do
-    {
-      c = (SequenceNode) c.parent();
-    } while ((c != null) && c.dummy);
-
-    return c;
-  }
-
-  /**
    * test if this node has a name that might be a label rather than a bootstrap
    * value
    * 
index 7b310b1..82ec1d2 100644 (file)
@@ -155,6 +155,7 @@ import jalview.viewmodel.AlignmentViewport;
 import jalview.viewmodel.ViewportRanges;
 import jalview.ws.DBRefFetcher;
 import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
+import jalview.ws.datamodel.alphafold.PAEContactMatrix;
 import jalview.ws.jws1.Discoverer;
 import jalview.ws.jws2.Jws2Discoverer;
 import jalview.ws.jws2.jabaws2.Jws2Instance;
@@ -4131,6 +4132,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     return showNewickTree(nf, treeTitle, null, w, h, x, y);
   }
 
+
   /**
    * Add a treeviewer for the tree extracted from a Newick file object to the
    * current alignment view
@@ -4181,6 +4183,40 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     return tp;
   }
 
+
+  public void showContactMapTree(AlignmentAnnotation aa,
+          PAEContactMatrix cm)
+  {
+    int x = 4, y = 5;
+    int w = 400, h = 500;
+    try
+    {
+      NewickFile fin = new NewickFile(
+              new FileParse(cm.getNewickString(), DataSourceType.PASTE));
+      fin.parse();
+      if (fin.getTree() == null)
+      {
+        return;
+      }
+      String title = "PAE Matrix Tree for "
+              + cm.getReferenceSeq().getDisplayId(false);
+      TreePanel tp = new TreePanel(alignPanel, fin, aa, title);
+
+      tp.setSize(w, h);
+
+      if (x > 0 && y > 0)
+      {
+        tp.setLocation(x, y);
+      }
+
+      Desktop.addInternalFrame(tp, title, w, h);
+    } catch (Throwable xx)
+    {
+      Console.error("Unexpected exception showing tree for contact matrix",
+              xx);
+    }
+
+  }
   private boolean buildingMenu = false;
 
   /**
@@ -5930,6 +5966,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
   {
     return lastFeatureSettingsBounds;
   }
+
 }
 
 class PrintThread extends Thread
index 804ab7b..c01a6f3 100755 (executable)
@@ -36,6 +36,7 @@ import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.event.MouseMotionListener;
 import java.awt.geom.AffineTransform;
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
@@ -50,18 +51,23 @@ import javax.swing.ToolTipManager;
 
 import jalview.analysis.AlignSeq;
 import jalview.analysis.AlignmentUtils;
+import jalview.bin.Console;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Annotation;
+import jalview.datamodel.ContactMatrixI;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
+import jalview.io.DataSourceType;
 import jalview.io.FileFormat;
 import jalview.io.FormatAdapter;
+import jalview.io.NewickFile;
 import jalview.util.Comparison;
 import jalview.util.MessageManager;
 import jalview.util.Platform;
+import jalview.ws.datamodel.alphafold.PAEContactMatrix;
 
 /**
  * The panel that holds the labels for alignment annotations, providing
@@ -416,6 +422,31 @@ public class AnnotationLabels extends JPanel
         consclipbrd.addActionListener(this);
         pop.add(consclipbrd);
       }
+       if (aa[selectedRow].graph == AlignmentAnnotation.CONTACT_MAP
+              && PAEContactMatrix.PAEMATRIX
+                      .equals(aa[selectedRow].getCalcId()))
+      {
+        final PAEContactMatrix cm = (PAEContactMatrix) av
+                .getContactMatrix(aa[selectedRow]);
+        if (cm.getNewickString()!=null && cm.getNewickString().length()>0)
+        {
+          item = new JMenuItem("Show Matrix");
+          item.addActionListener(new ActionListener()
+          {
+
+            @Override
+            public void actionPerformed(ActionEvent e)
+            {
+
+                ap.alignFrame.showContactMapTree(aa[selectedRow],cm);
+
+            }
+          });
+          pop.addSeparator();
+          pop.add(item);
+        }
+
+      }
     }
     pop.show(this, evt.getX(), evt.getY());
   }
index b68ff01..b112383 100755 (executable)
@@ -41,6 +41,7 @@ import java.awt.event.MouseWheelListener;
 import java.awt.image.BufferedImage;
 import java.beans.PropertyChangeEvent;
 import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Collections;
 import java.util.List;
 
@@ -56,6 +57,7 @@ import jalview.datamodel.AlignmentI;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.ContactListI;
+import jalview.datamodel.ContactMatrixI;
 import jalview.datamodel.ContactRange;
 import jalview.datamodel.GraphLine;
 import jalview.datamodel.HiddenColumns;
@@ -602,6 +604,7 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
         else
         {
           GraphLine thr = aa[graphStretch].getThreshold();
+          
           int currentX = getColumnForXPos(evt.getX());
           ContactListI forCurrentX = av.getContactList(aa[graphStretch],
                   currentX);
@@ -611,54 +614,104 @@ public class AnnotationPanel extends JPanel implements AwtRenderPanelI,
                     aa[graphStretch].graphHeight);
             ContactGeometry.contactInterval cXci = cXcgeom.mapFor(yOffset,
                     yOffset);
+            /**
+             * start and end range corresponding to the row range under the
+             * mouse at column currentX
+             */
             int fr, to;
             fr = Math.min(cXci.cStart, cXci.cEnd);
             to = Math.max(cXci.cStart, cXci.cEnd);
-            // select corresponding range in segment under mouse
+
+            if (evt.isControlDown())
             {
-              for (int c = fr; c <= to; c++)
+              ContactMatrixI matrix = av.getContactMatrix(aa[graphStretch]);
+              
+              if (matrix != null)
               {
-                av.getColumnSelection().addElement(c);
+                // simplest approach is to select all group containing column
+                if (matrix.hasGroups())
+                {
+                  SequenceI rseq = aa[graphStretch].sequenceRef;
+                  BitSet grp = matrix.getGroupsFor(currentX);
+                  for (int c=fr;c<=to; c++)
+                  {
+                    BitSet additionalGrp = matrix.getGroupsFor(c);
+                    grp.or(additionalGrp);
+                  }
+                  HiddenColumns hc = av.getAlignment().getHiddenColumns();
+                  for (int p = grp.nextSetBit(0); p >= 0; p = grp
+                          .nextSetBit(p + 1))
+                  {
+                    int offp = (rseq != null)
+                            ? rseq.findIndex(rseq.getStart() - 1 + p)
+                            : p;
+
+                    if (!av.hasHiddenColumns() || hc.isVisible(offp))
+                    {
+                      av.getColumnSelection().addElement(offp);
+                    }
+                  }
+                }
+                // possible alternative for interactive selection - threshold
+                // gives 'ceiling' for forming a cluster
+                // when a row+column is selected, farthest common ancestor less
+                // than thr is used to compute cluster
+
               }
-              av.getColumnSelection().addElement(currentX);
             }
-            // PAE SPECIFIC
-            // and also select everything lower than the max range adjacent
-            // (kind of works)
-            if (PAEContactMatrix.PAEMATRIX.equals(aa[graphStretch].getCalcId()))
+            else
             {
-              int c = fr - 1;
-              ContactRange cr = forCurrentX.getRangeFor(fr, to);
-              double cval;
-              // TODO: could use GraphLine instead of arbitrary picking
-              // TODO: could report mean/median/variance for partitions (contiguous selected vs unselected regions and inter-contig regions)
-              // controls feathering - what other elements in row/column should we select
-              double thresh=cr.getMean()+(cr.getMax()-cr.getMean())*.15;
-              while (c > 0)
+              // select corresponding range in segment under mouse
               {
-                cval = forCurrentX.getContactAt(c);
-                if (// cr.getMin() <= cval &&
-                cval <= thresh)
-                {
-                  av.getColumnSelection().addElement(c--);
-                }
-                else
+                for (int c = fr; c <= to; c++)
                 {
-                  break;
+                  av.getColumnSelection().addElement(c);
                 }
+                av.getColumnSelection().addElement(currentX);
               }
-              c = to;
-              while (c < forCurrentX.getContactHeight())
+              // PAE SPECIFIC
+              // and also select everything lower than the max range adjacent
+              // (kind of works)
+              if (PAEContactMatrix.PAEMATRIX
+                      .equals(aa[graphStretch].getCalcId()))
               {
-                cval = forCurrentX.getContactAt(c);
-                if (// cr.getMin() <= cval &&
-                cval <= thresh)
+                int c = fr - 1;
+                ContactRange cr = forCurrentX.getRangeFor(fr, to);
+                double cval;
+                // TODO: could use GraphLine instead of arbitrary picking
+                // TODO: could report mean/median/variance for partitions
+                // (contiguous selected vs unselected regions and inter-contig
+                // regions)
+                // controls feathering - what other elements in row/column
+                // should we select
+                double thresh = cr.getMean()
+                        + (cr.getMax() - cr.getMean()) * .15;
+                while (c > 0)
                 {
-                  av.getColumnSelection().addElement(c++);
+                  cval = forCurrentX.getContactAt(c);
+                  if (// cr.getMin() <= cval &&
+                  cval <= thresh)
+                  {
+                    av.getColumnSelection().addElement(c--);
+                  }
+                  else
+                  {
+                    break;
+                  }
                 }
-                else
+                c = to;
+                while (c < forCurrentX.getContactHeight())
                 {
-                  break;
+                  cval = forCurrentX.getContactAt(c);
+                  if (// cr.getMin() <= cval &&
+                  cval <= thresh)
+                  {
+                    av.getColumnSelection().addElement(c++);
+                  }
+                  else
+                  {
+                    break;
+                  }
                 }
               }
             }
index 6f143db..354feac 100755 (executable)
@@ -36,6 +36,7 @@ import java.awt.print.PageFormat;
 import java.awt.print.Printable;
 import java.awt.print.PrinterException;
 import java.awt.print.PrinterJob;
+import java.util.BitSet;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
@@ -50,6 +51,9 @@ import javax.swing.ToolTipManager;
 import jalview.analysis.Conservation;
 import jalview.analysis.TreeModel;
 import jalview.api.AlignViewportI;
+import jalview.datamodel.BinaryNode;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -106,9 +110,9 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
 
   Map<Object, Rectangle> nameHash = new Hashtable<>();
 
-  Map<SequenceNode, Rectangle> nodeHash = new Hashtable<>();
+  Map<BinaryNode, Rectangle> nodeHash = new Hashtable<>();
 
-  SequenceNode highlightNode;
+  BinaryNode highlightNode;
 
   boolean applyToAllViews = false;
 
@@ -173,15 +177,15 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
     tree.findHeight(tree.getTopNode());
 
     // Now have to calculate longest name based on the leaves
-    Vector<SequenceNode> leaves = tree.findLeaves(tree.getTopNode());
+    Vector<BinaryNode> leaves = tree.findLeaves(tree.getTopNode());
     boolean has_placeholders = false;
     longestName = "";
 
     for (int i = 0; i < leaves.size(); i++)
     {
-      SequenceNode lf = leaves.elementAt(i);
+      BinaryNode lf = leaves.elementAt(i);
 
-      if (lf.isPlaceholder())
+      if (lf instanceof SequenceNode && ((SequenceNode)lf).isPlaceholder())
       {
         has_placeholders = true;
       }
@@ -215,7 +219,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
    * @param offy
    *          DOCUMENT ME!
    */
-  public void drawNode(Graphics g, SequenceNode node, float chunk,
+  public void drawNode(Graphics g, BinaryNode node, float chunk,
           double wscale, int width, int offx, int offy)
   {
     if (node == null)
@@ -277,7 +281,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
         g.drawString(nodeLabel, xstart + 2, ypos - 2);
       }
 
-      String name = (markPlaceholders && node.isPlaceholder())
+      String name = (markPlaceholders && ((node instanceof SequenceNode && ((SequenceNode)node).isPlaceholder())))
               ? (PLACEHOLDER + node.getName())
               : node.getName();
 
@@ -290,10 +294,22 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
       nameHash.put(node.element(), rect);
 
       // Colour selected leaves differently
-      SequenceGroup selected = av.getSelectionGroup();
+      boolean isSelected = false;
+      if (tp.getColumnWise())
+      {
+        isSelected = isColumnForNodeSelected(node);
+      }
+      else
+      {
+        SequenceGroup selected = av.getSelectionGroup();
 
-      if ((selected != null)
-              && selected.getSequences(null).contains(node.element()))
+        if ((selected != null)
+                && selected.getSequences(null).contains(node.element()))
+        {
+          isSelected = true;
+        }
+      }
+      if (isSelected)
       {
         g.setColor(Color.gray);
 
@@ -306,9 +322,9 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
     }
     else
     {
-      drawNode(g, (SequenceNode) node.left(), chunk, wscale, width, offx,
+      drawNode(g, (BinaryNode) node.left(), chunk, wscale, width, offx,
               offy);
-      drawNode(g, (SequenceNode) node.right(), chunk, wscale, width, offx,
+      drawNode(g, (BinaryNode) node.right(), chunk, wscale, width, offx,
               offy);
 
       double height = node.height;
@@ -332,9 +348,9 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
       }
 
       int ystart = (node.left() == null ? 0
-              : (int) (((SequenceNode) node.left()).ycount * chunk)) + offy;
+              : (int) (((BinaryNode) node.left()).ycount * chunk)) + offy;
       int yend = (node.right() == null ? 0
-              : (int) (((SequenceNode) node.right()).ycount * chunk))
+              : (int) (((BinaryNode) node.right()).ycount * chunk))
               + offy;
 
       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
@@ -390,7 +406,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
       }
     }
 
-    for (Entry<SequenceNode, Rectangle> entry : nodeHash.entrySet())
+    for (Entry<BinaryNode, Rectangle> entry : nodeHash.entrySet())
     {
       Rectangle rect = entry.getValue();
 
@@ -415,14 +431,14 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
     int width = getWidth();
     int height = getHeight();
 
-    SequenceNode top = tree.getTopNode();
+    BinaryNode top = tree.getTopNode();
 
     double wscale = ((width * .8) - (offx * 2)) / tree.getMaxHeight();
 
     if (top.count == 0)
     {
-      top.count = ((SequenceNode) top.left()).count
-              + ((SequenceNode) top.right()).count;
+      top.count = ((BinaryNode) top.left()).count
+              + ((BinaryNode) top.right()).count;
     }
 
     float chunk = (float) (height - (offy)) / top.count;
@@ -448,7 +464,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
    * @param offy
    *          DOCUMENT ME!
    */
-  public void pickNode(Rectangle pickBox, SequenceNode node, float chunk,
+  public void pickNode(Rectangle pickBox, BinaryNode node, float chunk,
           double wscale, int width, int offx, int offy)
   {
     if (node == null)
@@ -481,9 +497,9 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
     }
     else
     {
-      pickNode(pickBox, (SequenceNode) node.left(), chunk, wscale, width,
+      pickNode(pickBox, (BinaryNode) node.left(), chunk, wscale, width,
               offx, offy);
-      pickNode(pickBox, (SequenceNode) node.right(), chunk, wscale, width,
+      pickNode(pickBox, (BinaryNode) node.right(), chunk, wscale, width,
               offx, offy);
     }
   }
@@ -496,7 +512,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
    * @param c
    *          DOCUMENT ME!
    */
-  public void setColor(SequenceNode node, Color c)
+  public void setColor(BinaryNode node, Color c)
   {
     if (node == null)
     {
@@ -516,8 +532,8 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
         }
       }
     }
-    setColor((SequenceNode) node.left(), c);
-    setColor((SequenceNode) node.right(), c);
+    setColor((BinaryNode) node.left(), c);
+    setColor((BinaryNode) node.right(), c);
   }
 
   /**
@@ -703,6 +719,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
     if (longestName == null || tree == null)
     {
       g2.drawString("Calculating tree.", 20, 20);
+      return;
     }
     offy = font.getSize() + 10;
 
@@ -713,12 +730,12 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
     double wscale = (width - labelLength - (offx * 2))
             / tree.getMaxHeight();
 
-    SequenceNode top = tree.getTopNode();
+    BinaryNode top = tree.getTopNode();
 
     if (top.count == 0)
     {
-      top.count = ((SequenceNode) top.left()).count
-              + ((SequenceNode) top.right()).count;
+      top.count = ((BinaryNode) top.left()).count
+              + ((BinaryNode) top.right()).count;
     }
 
     float chunk = (float) (height - (offy)) / top.count;
@@ -804,13 +821,16 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
     }
     else
     {
-      Vector<SequenceNode> leaves = tree.findLeaves(highlightNode);
-
+      Vector<BinaryNode> leaves = tree.findLeaves(highlightNode);
+      if (tp.getColumnWise()) {
+        markColumnsFor(getAssociatedPanels(), leaves, Color.red);
+      } else {
       for (int i = 0; i < leaves.size(); i++)
       {
         SequenceI seq = (SequenceI) leaves.elementAt(i).element();
         treeSelectionChanged(seq);
       }
+      }
       av.sendSelection();
     }
 
@@ -847,9 +867,9 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
 
     Object ob = findElement(evt.getX(), evt.getY());
 
-    if (ob instanceof SequenceNode)
+    if (ob instanceof BinaryNode)
     {
-      highlightNode = (SequenceNode) ob;
+      highlightNode = (BinaryNode) ob;
       this.setToolTipText(
               "<html>" + MessageManager.getString("label.highlightnode"));
       repaint();
@@ -922,7 +942,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
       av.sendSelection();
       return;
     }
-    else if (!(ob instanceof SequenceNode))
+    else if (!(ob instanceof BinaryNode))
     {
       // Find threshold
       if (tree.getMaxHeight() != 0)
@@ -930,7 +950,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
         threshold = (float) (x - offx)
                 / (float) (getWidth() - labelLength - (2 * offx));
 
-        List<SequenceNode> groups = tree.groupNodes(threshold);
+        List<BinaryNode> groups = tree.groupNodes(threshold);
         setColor(tree.getTopNode(), Color.black);
 
         AlignmentPanel[] aps = getAssociatedPanels();
@@ -970,7 +990,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
 
   }
 
-  void colourGroups(List<SequenceNode> groups)
+  void colourGroups(List<BinaryNode> groups)
   {
     AlignmentPanel[] aps = getAssociatedPanels();
     for (int i = 0; i < groups.size(); i++)
@@ -979,70 +999,134 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
               (int) (Math.random() * 255), (int) (Math.random() * 255));
       setColor(groups.get(i), col.brighter());
 
-      Vector<SequenceNode> l = tree.findLeaves(groups.get(i));
-
-      Vector<SequenceI> sequences = new Vector<>();
+      Vector<BinaryNode> l = tree.findLeaves(groups.get(i));
+      if (!tp.getColumnWise()) {
+        createSeqGroupFor(aps, l, col);
+      } else {
+        markColumnsFor(aps,l,col);
+      }
+    }
 
-      for (int j = 0; j < l.size(); j++)
+    // notify the panel(s) to redo any group specific stuff
+    // also updates structure views if necessary
+    for (int a = 0; a < aps.length; a++)
+    {
+      aps[a].updateAnnotation();
+      final AlignViewportI codingComplement = aps[a].av
+              .getCodingComplement();
+      if (codingComplement != null)
       {
-        SequenceI s1 = (SequenceI) l.elementAt(j).element();
-
-        if (!sequences.contains(s1))
-        {
-          sequences.addElement(s1);
-        }
+        ((AlignViewport) codingComplement).getAlignPanel()
+                .updateAnnotation();
       }
+    }
+  }
 
-      ColourSchemeI cs = null;
-      SequenceGroup _sg = new SequenceGroup(sequences, null, cs, true, true,
-              false, 0, av.getAlignment().getWidth() - 1);
+  private boolean isColumnForNodeSelected(BinaryNode bn)
+  {
+    SequenceI rseq = tp.assocAnnotation.sequenceRef;
+    int colm = -1;
+    try
+    {
+      colm = Integer.parseInt(
+              bn.getName().substring(bn.getName().indexOf("c") + 1));
+    } catch (Exception e)
+    {
+      return false;
+    }
+    ColumnSelection cs = av.getColumnSelection();
+    HiddenColumns hc = av.getAlignment().getHiddenColumns();
+    int offp = (rseq != null) ? rseq.findIndex(rseq.getStart() - 1 + colm)
+            : colm;
 
-      _sg.setName("JTreeGroup:" + _sg.hashCode());
-      _sg.setIdColour(col);
+    if (!av.hasHiddenColumns() || hc.isVisible(offp))
+    {
+      return cs.contains(offp);
+    }
+    return false;
+  }
 
-      for (int a = 0; a < aps.length; a++)
+  private void markColumnsFor(AlignmentPanel[] aps, Vector<BinaryNode> l,
+          Color col)
+  {
+    SequenceI rseq = tp.assocAnnotation.sequenceRef;
+    for (BinaryNode bn:l)
+    {
+      int colm=-1;
+      try {
+        colm = Integer.parseInt(bn.getName().substring(bn.getName().indexOf("c")+1));
+      } catch (Exception e)
       {
-        SequenceGroup sg = new SequenceGroup(_sg);
-        AlignViewport viewport = aps[a].av;
-
-        // Propagate group colours in each view
-        if (viewport.getGlobalColourScheme() != null)
-        {
-          cs = viewport.getGlobalColourScheme().getInstance(viewport, sg);
-          sg.setColourScheme(cs);
-          sg.getGroupColourScheme().setThreshold(
-                  viewport.getResidueShading().getThreshold(),
-                  viewport.isIgnoreGapsConsensus());
-
-          if (viewport.getResidueShading().conservationApplied())
+        continue;
+      }
+      ColumnSelection cs = av.getColumnSelection();
+      HiddenColumns hc = av.getAlignment().getHiddenColumns();
+      {
+        int offp = (rseq!=null) ? rseq.findIndex(rseq.getStart()-1+colm) : colm;
+        
+        if (!av.hasHiddenColumns() || hc.isVisible(offp))
+        { 
+          if (cs.contains(offp))
           {
-            Conservation c = new Conservation("Group",
-                    sg.getSequences(null), sg.getStartRes(),
-                    sg.getEndRes());
-            c.calculate();
-            c.verdict(false, viewport.getConsPercGaps());
-            sg.cs.setConservation(c);
+            cs.removeElement(offp);
+          } else {
+            cs.addElement(offp);
           }
         }
-        // indicate that associated structure views will need an update
-        viewport.setUpdateStructures(true);
-        // propagate structure view update and sequence group to complement view
-        viewport.addSequenceGroup(sg);
+      }
+    } 
+  }
+
+  public void createSeqGroupFor(AlignmentPanel[] aps, Vector<BinaryNode> l,
+          Color col)
+  {
+
+    Vector<SequenceI> sequences = new Vector<>();
+
+    for (int j = 0; j < l.size(); j++)
+    {
+      SequenceI s1 = (SequenceI) l.elementAt(j).element();
+
+      if (!sequences.contains(s1))
+      {
+        sequences.addElement(s1);
       }
     }
 
-    // notify the panel(s) to redo any group specific stuff
-    // also updates structure views if necessary
+    ColourSchemeI cs = null;
+    SequenceGroup _sg = new SequenceGroup(sequences, null, cs, true, true,
+            false, 0, av.getAlignment().getWidth() - 1);
+
+    _sg.setName("JTreeGroup:" + _sg.hashCode());
+    _sg.setIdColour(col);
+
     for (int a = 0; a < aps.length; a++)
     {
-      aps[a].updateAnnotation();
-      final AlignViewportI codingComplement = aps[a].av
-              .getCodingComplement();
-      if (codingComplement != null)
+      SequenceGroup sg = new SequenceGroup(_sg);
+      AlignViewport viewport = aps[a].av;
+
+      // Propagate group colours in each view
+      if (viewport.getGlobalColourScheme() != null)
       {
-        ((AlignViewport) codingComplement).getAlignPanel()
-                .updateAnnotation();
+        cs = viewport.getGlobalColourScheme().getInstance(viewport, sg);
+        sg.setColourScheme(cs);
+        sg.getGroupColourScheme().setThreshold(
+                viewport.getResidueShading().getThreshold(),
+                viewport.isIgnoreGapsConsensus());
+
+        if (viewport.getResidueShading().conservationApplied())
+        {
+          Conservation c = new Conservation("Group", sg.getSequences(null),
+                  sg.getStartRes(), sg.getEndRes());
+          c.calculate();
+          c.verdict(false, viewport.getConsPercGaps());
+          sg.cs.setConservation(c);
+        }
       }
+      // indicate that associated structure views will need an update
+      viewport.setUpdateStructures(true);
+      // propagate structure view update and sequence group to complement view
+      viewport.addSequenceGroup(sg);
     }
   }
 
index 76e1884..6a07a4e 100755 (executable)
@@ -53,6 +53,7 @@ import jalview.bin.Console;
 import jalview.commands.CommandI;
 import jalview.commands.OrderCommand;
 import jalview.datamodel.Alignment;
+import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.AlignmentView;
 import jalview.datamodel.BinaryNode;
@@ -123,6 +124,30 @@ public class TreePanel extends GTreePanel
     initTreePanel(alignPanel, null, null, newtree, inputData);
   }
 
+  /**
+   * columnwise tree associated with positions in aa
+   * 
+   * @param alignPanel
+   * @param fin
+   * @param title
+   * @param aa
+   */
+  public TreePanel(AlignmentPanel alignPanel, NewickFile fin,
+          AlignmentAnnotation aa, String title)
+  {
+    this(alignPanel, fin, title, null);
+    columnWise=true;
+    assocAnnotation = aa;
+    
+
+  }
+  boolean columnWise=false;
+  AlignmentAnnotation assocAnnotation=null;
+  public boolean getColumnWise()
+  {
+    return columnWise;
+  }
+
   public AlignmentI getAlignment()
   {
     return getTreeCanvas().getViewport().getAlignment();
@@ -721,7 +746,7 @@ public class TreePanel extends GTreePanel
                 && !((SequenceNode) node).isDummy())
         {
           String newname = null;
-          SequenceI sq = (SequenceI) ((SequenceNode) node).element();
+          SequenceI sq = (SequenceI) ((BinaryNode) node).element();
           if (sq != null)
           {
             // search dbrefs, features and annotation
@@ -763,7 +788,7 @@ public class TreePanel extends GTreePanel
           {
             // String oldname = ((SequenceNode) node).getName();
             // TODO : save oldname in the undo object for this modification.
-            ((SequenceNode) node).setName(newname);
+            ((BinaryNode) node).setName(newname);
           }
         }
       }
index 027390a..269ffb3 100755 (executable)
@@ -28,6 +28,7 @@ package jalview.io;
 
 import java.util.Locale;
 
+import jalview.datamodel.BinaryNode;
 import jalview.datamodel.SequenceNode;
 import jalview.util.MessageManager;
 
@@ -78,7 +79,7 @@ import com.stevesoft.pat.Regex;
  */
 public class NewickFile extends FileParse
 {
-  SequenceNode root;
+  BinaryNode root;
 
   private boolean HasBootstrap = false;
 
@@ -145,7 +146,7 @@ public class NewickFile extends FileParse
    * @param newtree
    *          DOCUMENT ME!
    */
-  public NewickFile(SequenceNode newtree)
+  public NewickFile(BinaryNode newtree)
   {
     root = newtree;
   }
@@ -174,7 +175,7 @@ public class NewickFile extends FileParse
    * @param distances
    *          DOCUMENT ME!
    */
-  public NewickFile(SequenceNode newtree, boolean bootstrap,
+  public NewickFile(BinaryNode newtree, boolean bootstrap,
           boolean distances)
   {
     root = newtree;
@@ -194,7 +195,7 @@ public class NewickFile extends FileParse
    * @param rootdistance
    *          DOCUMENT ME!
    */
-  public NewickFile(SequenceNode newtree, boolean bootstrap,
+  public NewickFile(BinaryNode newtree, boolean bootstrap,
           boolean distances, boolean rootdistance)
   {
     root = newtree;
@@ -275,8 +276,8 @@ public class NewickFile extends FileParse
 
     root = new SequenceNode();
 
-    SequenceNode realroot = null;
-    SequenceNode c = root;
+    BinaryNode realroot = null;
+    BinaryNode c = root;
 
     int d = -1;
     int cp = 0;
@@ -323,21 +324,21 @@ public class NewickFile extends FileParse
         {
           c.setRight(new SequenceNode(null, c, null, DefDistance,
                   DefBootstrap, false));
-          c = (SequenceNode) c.right();
+          c = (BinaryNode) c.right();
         }
         else
         {
           if (c.left() != null)
           {
             // Dummy node for polytomy - keeps c.left free for new node
-            SequenceNode tmpn = new SequenceNode(null, c, null, 0, 0, true);
+            BinaryNode tmpn = new SequenceNode(null, c, null, 0, 0, true);
             tmpn.SetChildren(c.left(), c.right());
             c.setRight(tmpn);
           }
 
           c.setLeft(new SequenceNode(null, c, null, DefDistance,
                   DefBootstrap, false));
-          c = (SequenceNode) c.left();
+          c = (BinaryNode) c.left();
         }
 
         if (realroot == null)
@@ -518,7 +519,7 @@ public class NewickFile extends FileParse
         else
         {
           // Find a place to put the leaf
-          SequenceNode newnode = new SequenceNode(null, c, nodename,
+          BinaryNode newnode = new SequenceNode(null, c, nodename,
                   (HasDistances) ? distance : DefDistance,
                   (HasBootstrap) ? bootstrap : DefBootstrap, false);
           parseNHXNodeProps(c, commentString2);
@@ -538,7 +539,7 @@ public class NewickFile extends FileParse
             {
               // Insert a dummy node for polytomy
               // dummy nodes have distances
-              SequenceNode newdummy = new SequenceNode(null, c, null,
+              BinaryNode newdummy = new SequenceNode(null, c, null,
                       (HasDistances ? 0 : DefDistance), 0, true);
               newdummy.SetChildren(c.left(), newnode);
               c.setLeft(newdummy);
@@ -577,7 +578,7 @@ public class NewickFile extends FileParse
               // Just advance focus, if we need to
               if ((c.left() != null) && (!c.left().isLeaf()))
               {
-                c = (SequenceNode) c.left();
+                c = (BinaryNode) c.left();
               }
             }
           }
@@ -631,7 +632,7 @@ public class NewickFile extends FileParse
    * @param commentString
    * @param commentString2
    */
-  private void parseNHXNodeProps(SequenceNode c, String commentString)
+  private void parseNHXNodeProps(BinaryNode c, String commentString)
   {
     // TODO: store raw comment on the sequenceNode so it can be recovered when
     // tree is output
@@ -678,7 +679,7 @@ public class NewickFile extends FileParse
    * 
    * @return DOCUMENT ME!
    */
-  public SequenceNode getTree()
+  public BinaryNode getTree()
   {
     return root;
   }
@@ -832,7 +833,7 @@ public class NewickFile extends FileParse
    * 
    * @return DOCUMENT ME!
    */
-  private String printNodeField(SequenceNode c)
+  private String printNodeField(BinaryNode c)
   {
     return ((c.getName() == null) ? "" : nodeName(c.getName()))
             + ((HasBootstrap) ? ((c.getBootstrap() > -1)
@@ -849,7 +850,7 @@ public class NewickFile extends FileParse
    * 
    * @return DOCUMENT ME!
    */
-  private String printRootField(SequenceNode root)
+  private String printRootField(BinaryNode root)
   {
     return (printRootInfo)
             ? (((root.getName() == null) ? "" : nodeName(root.getName()))
@@ -864,7 +865,7 @@ public class NewickFile extends FileParse
   }
 
   // Non recursive call deals with root node properties
-  public void print(StringBuffer tf, SequenceNode root)
+  public void print(StringBuffer tf, BinaryNode root)
   {
     if (root != null)
     {
@@ -876,20 +877,20 @@ public class NewickFile extends FileParse
       {
         if (root.isDummy())
         {
-          _print(tf, (SequenceNode) root.right());
-          _print(tf, (SequenceNode) root.left());
+          _print(tf,  root.right());
+          _print(tf,  root.left());
         }
         else
         {
           tf.append("(");
-          _print(tf, (SequenceNode) root.right());
+          _print(tf,  root.right());
 
           if (root.left() != null)
           {
             tf.append(",");
           }
 
-          _print(tf, (SequenceNode) root.left());
+          _print(tf,  root.left());
           tf.append(")" + printRootField(root));
         }
       }
@@ -897,7 +898,7 @@ public class NewickFile extends FileParse
   }
 
   // Recursive call for non-root nodes
-  public void _print(StringBuffer tf, SequenceNode c)
+  public void _print(StringBuffer tf, BinaryNode c)
   {
     if (c != null)
     {
@@ -909,24 +910,24 @@ public class NewickFile extends FileParse
       {
         if (c.isDummy())
         {
-          _print(tf, (SequenceNode) c.left());
+          _print(tf,  c.left());
           if (c.left() != null)
           {
             tf.append(",");
           }
-          _print(tf, (SequenceNode) c.right());
+          _print(tf,  c.right());
         }
         else
         {
           tf.append("(");
-          _print(tf, (SequenceNode) c.right());
+          _print(tf,  c.right());
 
           if (c.left() != null)
           {
             tf.append(",");
           }
 
-          _print(tf, (SequenceNode) c.left());
+          _print(tf,  c.left());
           tf.append(")" + printNodeField(c));
         }
       }
index e8f6f6b..c0d1774 100644 (file)
@@ -322,7 +322,7 @@ public class Tree extends DatastoreItem
       Console.warn("Not updating SequenceTreeMap for " + tree.getVorbaId());
       return;
     }
-    Vector<SequenceNode> leaves = tp.getTree()
+    Vector<BinaryNode> leaves = tp.getTree()
             .findLeaves(tp.getTree().getTopNode());
     Treenode[] tn = tree.getTreenode(); // todo: select nodes for this
     // particular tree
@@ -381,7 +381,7 @@ public class Tree extends DatastoreItem
    */
   public Treenode[] makeTreeNodes(TreeModel treeModel, Newick newick)
   {
-    Vector<SequenceNode> leaves = treeModel
+    Vector<BinaryNode> leaves = treeModel
             .findLeaves(treeModel.getTopNode());
     Vector tnv = new Vector();
     Enumeration l = leaves.elements();
@@ -394,7 +394,7 @@ public class Tree extends DatastoreItem
       {
         if (!((jalview.datamodel.SequenceNode) tnode).isPlaceholder())
         {
-          Object assocseq = ((jalview.datamodel.SequenceNode) tnode)
+          Object assocseq = ((BinaryNode) tnode)
                   .element();
           if (assocseq instanceof SequenceI)
           {
index a42a2a4..36b0851 100644 (file)
@@ -51,6 +51,7 @@ import jalview.datamodel.AlignmentView;
 import jalview.datamodel.Annotation;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.ContactListI;
+import jalview.datamodel.ContactMatrixI;
 import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.HiddenSequences;
 import jalview.datamodel.ProfilesI;
@@ -2948,6 +2949,14 @@ public abstract class AlignmentViewport
     return alignment.getContactListFor(_aa, column);
   }
 
+  @Override
+  public ContactMatrixI getContactMatrix(
+          AlignmentAnnotation alignmentAnnotation)
+  {
+    return alignment.getContactMatrixFor(alignmentAnnotation);
+  }
+
+
   /**
    * get the consensus sequence as displayed under the PID consensus annotation
    * row.
index d1a2e9d..db3addc 100644 (file)
@@ -1,9 +1,14 @@
 package jalview.ws.datamodel.alphafold;
 
+import java.util.ArrayList;
+import java.util.BitSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
+import jalview.analysis.AverageDistanceEngine;
+import jalview.bin.Console;
+import jalview.datamodel.BinaryNode;
 import jalview.datamodel.ContactListI;
 import jalview.datamodel.ContactListImpl;
 import jalview.datamodel.ContactListProviderI;
@@ -248,4 +253,60 @@ public class PAEContactMatrix implements ContactMatrixI
   {
     return length;
   }
+  List<BitSet> groups=null;
+  @Override
+  public boolean hasGroups()
+  {
+    return groups!=null;
+  }
+  String newick=null;
+  public String getNewickString()
+  {
+    return newick;
+  }
+  public void makeGroups(float thresh,boolean abs)
+  {
+    AverageDistanceEngine clusterer = new AverageDistanceEngine(null, null, this);
+    double height = clusterer.findHeight(clusterer.getTopNode());
+    newick = new jalview.io.NewickFile(clusterer.getTopNode(),false,true).print();
+
+    Console.trace("Newick string\n"+newick);
+
+    List<BinaryNode> nodegroups;
+    if (abs ? height > thresh : 0 < thresh && thresh < 1)
+    {
+      float cut = abs ? (float) (thresh / height) : thresh;
+      Console.debug("Threshold "+cut+" for height="+height);
+
+      nodegroups = clusterer.groupNodes(cut);
+    }
+    else
+    {
+      nodegroups = new ArrayList<BinaryNode>();
+      nodegroups.add(clusterer.getTopNode());
+    }
+
+    groups = new ArrayList<>();
+    for (BinaryNode root:nodegroups)
+    {
+      BitSet gpset=new BitSet();
+      for (BinaryNode leaf:clusterer.findLeaves(root))
+      {
+        gpset.set((Integer)leaf.element());
+      }
+      groups.add(gpset);
+    }
+  }
+  
+  @Override
+  public BitSet getGroupsFor(int column)
+  {
+    for (BitSet gp:groups) {
+      if (gp.get(column))
+      {
+        return gp;
+      }
+    }
+    return ContactMatrixI.super.getGroupsFor(column);
+  }
 }
index 8592bd3..d9cbbd9 100644 (file)
@@ -442,6 +442,7 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
     }
     ContactMatrixI matrix = new PAEContactMatrix(sequence,
             (Map<String, Object>) paeDict);
+    ((PAEContactMatrix) matrix).makeGroups(5f, true);
 
     AlignmentAnnotation cmannot = sequence.addContactList(matrix);
     pdbAlignment.addAnnotation(cmannot);
@@ -495,7 +496,7 @@ public class EBIAlfaFold extends EbiFileRetrievedProxy
 
     ContactMatrixI matrix = new PAEContactMatrix(sm.getSequence(),
             (Map<String, Object>) pae_obj);
-
+    ((PAEContactMatrix) matrix).makeGroups(5f, true);
     AlignmentAnnotation cmannot = sm.getSequence().addContactList(matrix);
     sm.getSequence().addAlignmentAnnotation(cmannot);
 
diff --git a/test/jalview/analysis/AverageDistanceEngineTest.java b/test/jalview/analysis/AverageDistanceEngineTest.java
new file mode 100644 (file)
index 0000000..6d9ab50
--- /dev/null
@@ -0,0 +1,97 @@
+package jalview.analysis;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import jalview.bin.Cache;
+import jalview.bin.Console;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.BinaryNode;
+import jalview.datamodel.ContactMatrixI;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.gui.JvOptionPane;
+import jalview.io.DataSourceType;
+import jalview.io.FastaFile;
+import jalview.io.FileLoader;
+import jalview.io.FormatAdapter;
+import jalview.util.Platform;
+import jalview.ws.datamodel.alphafold.PAEContactMatrix;
+
+public class AverageDistanceEngineTest
+{
+
+    @BeforeClass(alwaysRun = true)
+    public void setUpJvOptionPane()
+    {
+      JvOptionPane.setInteractiveMode(false);
+      JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+    }
+
+    @BeforeMethod(alwaysRun = true)
+    public void loadProperties()
+    {
+      Cache.loadProperties("test/jalview/bin/TestProps.jvprops");
+    }
+    @Test
+    public void testUPGMAEngine() throws Exception
+    {
+      AlignFrame af = new FileLoader(false).LoadFileWaitTillLoaded("examples/test_fab41.result/sample.a3m",DataSourceType.FILE);
+      AlignmentI seqs = af.getViewport().getAlignment();
+      SequenceI target = seqs.getSequenceAt(0);
+      File testPAE = new File("examples/test_fab41.result/test_fab41_predicted_aligned_error_v1.json");
+      List<Object> pae_obj = (List<Object>) Platform.parseJSON(new FileInputStream(testPAE));
+      if (pae_obj == null)
+      {
+        Assert.fail("JSON PAE file did not parse properly.");
+      }
+      ContactMatrixI matrix = new PAEContactMatrix(target,
+              (Map<String, Object>) pae_obj.get(0));
+      AlignmentAnnotation aa = target.addContactList(matrix);
+      System.out.println("Matrix has max="+matrix.getMax()+" and min="+matrix.getMin());
+      long start = System.currentTimeMillis();
+      AverageDistanceEngine clusterer = new AverageDistanceEngine(af.getViewport(), null, matrix);
+      System.out.println("built a tree in "+(System.currentTimeMillis()-start)*0.001+" seconds.");
+      StringBuffer sb = new StringBuffer(); 
+      System.out.println("Newick string\n"+      new jalview.io.NewickFile(clusterer.getTopNode(),true,true).print());
+      
+      double height = clusterer.findHeight(clusterer.getTopNode());
+      // compute height fraction to cut 
+      // PAE matrixes are absolute measure in angstrom, so 
+      // cluster all regions within threshold (e.g. 2A) - if height above threshold. Otherwise all nodes are in one cluster
+      double thr=.2;
+      List<BinaryNode> groups;
+      if (height>thr)
+      {
+        float cut = (float) (thr/height);
+        System.out.println("Threshold "+cut+" for height="+height);
+        groups = clusterer.groupNodes(cut);
+      } else{
+        groups=new ArrayList<BinaryNode>();
+        groups.add(clusterer.getTopNode());
+      }
+      int n=1;
+      for (BinaryNode root:groups)
+      {
+        System.out.println("Cluster "+n++);
+        for (BinaryNode leaf:clusterer.findLeaves(root))
+        {
+          System.out.print(" "+leaf.getName());
+        }
+        System.out.println("\\");
+      }
+      
+    }
+
+}
index beedb11..466f218 100644 (file)
@@ -24,6 +24,7 @@ import static org.testng.ConversionUtils.wrapDataProvider;
 
 import jalview.analysis.SequenceIdMatcher;
 import jalview.analysis.TreeModel;
+import jalview.datamodel.BinaryNode;
 import jalview.datamodel.SequenceI;
 import jalview.datamodel.SequenceNode;
 import jalview.gui.JvOptionPane;
@@ -105,7 +106,7 @@ public class NewickFileTests
       AssertJUnit.assertTrue(
               stage + "Invalid Tree '" + nf.getWarningMessage() + "'",
               nf.isValid());
-      SequenceNode tree = nf.getTree();
+      BinaryNode tree = nf.getTree();
       AssertJUnit.assertTrue(stage + "Null Tree", tree != null);
       stage = "Creating newick file from testTree " + treename;
       String gentree = new NewickFile(tree).print(nf.HasBootstrap(),
@@ -119,11 +120,11 @@ public class NewickFileTests
               stage + "Newick file is invalid ('"
                       + nf_regen.getWarningMessage() + "')",
               nf_regen.isValid());
-      SequenceNode tree_regen = nf.getTree();
+      BinaryNode tree_regen = nf.getTree();
       AssertJUnit.assertTrue(stage + "Null Tree", tree_regen != null);
       stage = "Compare original and generated tree" + treename;
 
-      Vector<SequenceNode> oseqs, nseqs;
+      Vector<BinaryNode> oseqs, nseqs;
       oseqs = new TreeModel(new SequenceI[0], null, nf)
               .findLeaves(nf.getTree());
       AssertJUnit.assertTrue(stage + "No nodes in original tree.",