JAL-3855 auto select clusters and fix up colouring/propagation on calculation & tree...
[jalview.git] / src / jalview / gui / TreeCanvas.java
index 6f143db..6fbd422 100755 (executable)
@@ -36,6 +36,9 @@ import java.awt.print.PageFormat;
 import java.awt.print.Printable;
 import java.awt.print.PrinterException;
 import java.awt.print.PrinterJob;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
@@ -50,6 +53,13 @@ import javax.swing.ToolTipManager;
 import jalview.analysis.Conservation;
 import jalview.analysis.TreeModel;
 import jalview.api.AlignViewportI;
+import jalview.bin.Console;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.BinaryNode;
+import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.ContactMatrixI;
+import jalview.datamodel.HiddenColumns;
 import jalview.datamodel.Sequence;
 import jalview.datamodel.SequenceGroup;
 import jalview.datamodel.SequenceI;
@@ -57,8 +67,10 @@ import jalview.datamodel.SequenceNode;
 import jalview.gui.JalviewColourChooser.ColourChooserListener;
 import jalview.schemes.ColourSchemeI;
 import jalview.structure.SelectionSource;
+import jalview.util.ColorUtils;
 import jalview.util.Format;
 import jalview.util.MessageManager;
+import jalview.ws.datamodel.MappableContactMatrixI;
 
 /**
  * DOCUMENT ME!
@@ -106,9 +118,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;
 
@@ -133,9 +145,43 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
     scrollPane = scroller;
     addMouseListener(this);
     addMouseMotionListener(this);
+    
     ToolTipManager.sharedInstance().registerComponent(this);
   }
 
+  public void clearSelectedLeaves()
+  {
+    Vector<BinaryNode> leaves = tp.getTree()
+            .findLeaves(tp.getTree().getTopNode());
+    if (tp.isColumnWise())
+    {
+      markColumnsFor(getAssociatedPanels(), leaves, Color.white, true);
+    }
+    else
+    {
+      for (AlignmentPanel ap : getAssociatedPanels())
+      {
+        SequenceGroup selected = ap.av.getSelectionGroup();
+        if (selected != null)
+        {
+          {
+            for (int i = 0; i < leaves.size(); i++)
+            {
+              SequenceI seq = (SequenceI) leaves.elementAt(i).element();
+              if (selected.contains(seq))
+              {
+                selected.addOrRemove(seq, false);
+              }
+            }
+            selected.recalcConservation();
+          }
+        }
+        ap.av.sendSelection();
+      }
+    }
+    PaintRefresher.Refresh(tp, av.getSequenceSetId());
+    repaint();
+  }
   /**
    * DOCUMENT ME!
    * 
@@ -173,15 +219,22 @@ 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 = "";
 
+    AlignmentAnnotation aa = tp.getAssocAnnotation();
+    ContactMatrixI cm = (aa!=null) ? av.getContactMatrix(aa) : null;
+    if (cm!=null && cm.hasCutHeight())
+    {
+      threshold=(float) cm.getCutHeight();
+    }
+    
     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;
       }
@@ -192,6 +245,14 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
         longestName = TreeCanvas.PLACEHOLDER
                 + ((Sequence) lf.element()).getName();
       }
+      if (tp.isColumnWise() && cm!=null)
+      {
+        // get color from group colours, if they are set for the matrix
+        try {
+          Color col = cm.getGroupColorForPosition(parseColumnNode(lf));
+          setColor(lf,col.brighter());
+        } catch (NumberFormatException ex) {};
+      }
     }
 
     setMarkPlaceholders(has_placeholders);
@@ -215,7 +276,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, double chunk,
           double wscale, int width, int offx, int offy)
   {
     if (node == null)
@@ -277,9 +338,10 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
         g.drawString(nodeLabel, xstart + 2, ypos - 2);
       }
 
-      String name = (markPlaceholders && node.isPlaceholder())
-              ? (PLACEHOLDER + node.getName())
-              : node.getName();
+      String name = (markPlaceholders && ((node instanceof SequenceNode
+              && ((SequenceNode) node).isPlaceholder())))
+                      ? (PLACEHOLDER + node.getName())
+                      : node.getName();
 
       int charWidth = fm.stringWidth(name) + 3;
       int charHeight = font.getSize();
@@ -290,10 +352,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.isColumnWise())
+      {
+        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 +380,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,10 +406,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))
-              + offy;
+              : (int) (((BinaryNode) node.right()).ycount * chunk)) + offy;
 
       Rectangle pos = new Rectangle(xend - 2, ypos - 2, 5, 5);
       nodeHash.put(node, pos);
@@ -390,7 +463,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 +488,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 +521,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 +554,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 +569,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 +589,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 +776,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,15 +787,15 @@ 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;
+    double chunk = (double) (height - (offy)) / (double)top.count;
 
     drawNode(g2, tree.getTopNode(), chunk, wscale, width, offx, offy);
 
@@ -804,12 +878,18 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
     }
     else
     {
-      Vector<SequenceNode> leaves = tree.findLeaves(highlightNode);
-
-      for (int i = 0; i < leaves.size(); i++)
+      Vector<BinaryNode> leaves = tree.findLeaves(highlightNode);
+      if (tp.isColumnWise())
+      {
+        markColumnsFor(getAssociatedPanels(), leaves, Color.red,false);
+      }
+      else
       {
-        SequenceI seq = (SequenceI) leaves.elementAt(i).element();
-        treeSelectionChanged(seq);
+        for (int i = 0; i < leaves.size(); i++)
+        {
+          SequenceI seq = (SequenceI) leaves.elementAt(i).element();
+          treeSelectionChanged(seq);
+        }
       }
       av.sendSelection();
     }
@@ -847,9 +927,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 +1002,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 +1010,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();
@@ -962,7 +1042,7 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
           threshold = 0f;
         }
       }
-
+      Console.log.debug("Tree cut threshold set at:" + threshold);
       PaintRefresher.Refresh(tp,
               getAssociatedPanel().av.getSequenceSetId());
       repaint();
@@ -970,64 +1050,45 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
 
   }
 
-  void colourGroups(List<SequenceNode> groups)
+  void colourGroups(List<BinaryNode> groups)
   {
     AlignmentPanel[] aps = getAssociatedPanels();
+    List<BitSet> colGroups = new ArrayList<>();
+    Map<BitSet, Color> colors = new HashMap();
     for (int i = 0; i < groups.size(); i++)
     {
-      Color col = new Color((int) (Math.random() * 255),
-              (int) (Math.random() * 255), (int) (Math.random() * 255));
+      Color col = ColorUtils.getARandomColor();
+      
       setColor(groups.get(i), col.brighter());
 
-      Vector<SequenceNode> l = tree.findLeaves(groups.get(i));
-
-      Vector<SequenceI> sequences = new Vector<>();
-
-      for (int j = 0; j < l.size(); j++)
+      Vector<BinaryNode> l = tree.findLeaves(groups.get(i));
+      if (!tp.isColumnWise())
       {
-        SequenceI s1 = (SequenceI) l.elementAt(j).element();
-
-        if (!sequences.contains(s1))
-        {
-          sequences.addElement(s1);
-        }
+        createSeqGroupFor(aps, l, col);
       }
-
-      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++)
+      else
       {
-        SequenceGroup sg = new SequenceGroup(_sg);
-        AlignViewport viewport = aps[a].av;
+        BitSet gp = createColumnGroupFor(l, col);
 
-        // Propagate group colours in each view
-        if (viewport.getGlobalColourScheme() != null)
+        colGroups.add(gp);
+        colors.put(gp, col);
+      }
+    }
+    if (tp.isColumnWise())
+    {
+      AlignmentAnnotation aa = tp.getAssocAnnotation();
+      if (aa != null)
+      {
+        ContactMatrixI cm = av.getContactMatrix(aa);
+        if (cm != null)
         {
-          cs = viewport.getGlobalColourScheme().getInstance(viewport, sg);
-          sg.setColourScheme(cs);
-          sg.getGroupColourScheme().setThreshold(
-                  viewport.getResidueShading().getThreshold(),
-                  viewport.isIgnoreGapsConsensus());
-
-          if (viewport.getResidueShading().conservationApplied())
+          cm.updateGroups(colGroups);
+          for (BitSet gp : colors.keySet())
           {
-            Conservation c = new Conservation("Group",
-                    sg.getSequences(null), sg.getStartRes(),
-                    sg.getEndRes());
-            c.calculate();
-            c.verdict(false, viewport.getConsPercGaps());
-            sg.cs.setConservation(c);
+            cm.setColorForGroup(gp, colors.get(gp));
           }
         }
-        // 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);
+        cm.transferGroupColorsTo(aa);
       }
     }
 
@@ -1045,6 +1106,210 @@ public class TreeCanvas extends JPanel implements MouseListener, Runnable,
       }
     }
   }
+  private int parseColumnNode(BinaryNode bn) throws NumberFormatException
+  {
+    return Integer.parseInt(
+            bn.getName().substring(bn.getName().indexOf("c") + 1));
+  }
+  private boolean isColumnForNodeSelected(BinaryNode bn)
+  {
+    SequenceI rseq = tp.assocAnnotation.sequenceRef;
+    int colm = -1;
+    try
+    {
+      colm = parseColumnNode(bn);
+    } catch (Exception e)
+    {
+      return false;
+    }
+    if (av == null || av.getAlignment() == null)
+    {
+      // alignment is closed
+      return false;
+    }
+    ColumnSelection cs = av.getColumnSelection();
+    HiddenColumns hc = av.getAlignment().getHiddenColumns();
+    AlignmentAnnotation aa = tp.getAssocAnnotation();
+    int offp=-1;
+    if (aa != null)
+    {
+      ContactMatrixI cm = av.getContactMatrix(aa);
+      // generally, we assume cm has 1:1 mapping to annotation row - probably wrong
+      // but.. if
+      if (cm instanceof MappableContactMatrixI)
+      {
+        int[] pos;
+          // use the mappable's mapping - always the case for PAE Matrices so good
+        // for 2.11.3
+        MappableContactMatrixI mcm = (MappableContactMatrixI) cm;
+        pos = mcm.getMappedPositionsFor(rseq, colm + 1);
+        // finally, look up the position of the column
+        if (pos != null)
+        {
+          offp = rseq.findIndex(pos[0]);
+        }
+      } else {
+        offp = colm;
+      }
+    }
+    if (offp<=0)
+    {
+      return false;
+    }
+
+    offp-=2;
+    if (!av.hasHiddenColumns())
+    {
+      return cs.contains(offp);
+    }
+    if (hc.isVisible(offp))
+    {
+      return cs.contains(offp);
+      // return cs.contains(hc.absoluteToVisibleColumn(offp));
+    }
+    return false;
+  }
+  private BitSet createColumnGroupFor(Vector<BinaryNode> l, Color col)
+  {
+    BitSet gp = new BitSet();
+    for (BinaryNode bn : l)
+    {
+      int colm = -1;
+      if (bn.element() != null && bn.element() instanceof Integer)
+      {
+        colm = (Integer) bn.element();
+      }
+      else
+      {
+        // parse out from nodename
+        try
+        {
+          colm = parseColumnNode(bn);
+        } catch (Exception e)
+        {
+          continue;
+        }
+      }
+      gp.set(colm);
+    }
+    return gp;
+  }
+
+  private void markColumnsFor(AlignmentPanel[] aps, Vector<BinaryNode> l,
+          Color col, boolean clearSelected)
+  {
+    SequenceI rseq = tp.assocAnnotation.sequenceRef;
+    if (av == null || av.getAlignment() == null)
+    {
+      // alignment is closed
+      return;
+    }
+
+    // TODO - sort indices for faster lookup
+    ColumnSelection cs = av.getColumnSelection();
+    HiddenColumns hc = av.getAlignment().getHiddenColumns();
+    ContactMatrixI cm = av.getContactMatrix(tp.assocAnnotation);
+    MappableContactMatrixI mcm = null;
+    int offp;
+    if (cm instanceof MappableContactMatrixI)
+    {
+      mcm = (MappableContactMatrixI) cm;
+    }
+    for (BinaryNode bn : l)
+    {
+      int colm = -1;
+      try
+      {
+        colm = Integer.parseInt(
+                bn.getName().substring(bn.getName().indexOf("c") + 1));
+      } catch (Exception e)
+      {
+        continue;
+      }
+      if (mcm!=null)
+      {
+        int[] seqpos = mcm.getMappedPositionsFor(
+                rseq, colm);
+        if (seqpos == null)
+        {
+          // no mapping for this column.
+          continue;
+        }
+        // TODO: handle ranges...
+        offp = rseq.findIndex(seqpos[0])-1;
+      }
+      else
+      {
+        offp = (rseq != null) ? rseq.findIndex(rseq.getStart() + colm)
+                : colm;
+      }
+      if (!av.hasHiddenColumns() || hc.isVisible(offp))
+      {
+        if (clearSelected || cs.contains(offp))
+        {
+          cs.removeElement(offp);
+        }
+        else
+        {
+          cs.addElement(offp);
+        }
+      }
+    }
+    PaintRefresher.Refresh(tp, av.getSequenceSetId());
+  }
+
+  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);
+      }
+    }
+
+    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++)
+    {
+      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())
+        {
+          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);
+    }
+  }
 
   /**
    * DOCUMENT ME!