Merge branch 'features/JAL-2349_matrixvis' into merge/develop_JAL-2340_matrixvis
authorJim Procter <jprocter@issues.jalview.org>
Tue, 18 Jun 2019 09:19:49 +0000 (10:19 +0100)
committerJim Procter <jprocter@issues.jalview.org>
Tue, 18 Jun 2019 10:31:12 +0000 (11:31 +0100)
 Conflicts:
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentI.java

1  2 
src/jalview/api/AlignViewportI.java
src/jalview/appletgui/AnnotationPanel.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/AlignmentI.java
src/jalview/gui/AnnotationPanel.java
src/jalview/io/AppletFormatAdapter.java
src/jalview/renderer/AnnotationRenderer.java
src/jalview/renderer/ContactMapRenderer.java
src/jalview/viewmodel/AlignmentViewport.java

@@@ -25,7 -24,9 +25,8 @@@ import jalview.analysis.TreeModel
  import jalview.datamodel.AlignmentAnnotation;
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.AlignmentView;
 -import jalview.datamodel.CigarArray;
  import jalview.datamodel.ColumnSelection;
+ import jalview.datamodel.ContactListI;
  import jalview.datamodel.ProfilesI;
  import jalview.datamodel.SearchResultsI;
  import jalview.datamodel.SequenceCollectionI;
@@@ -28,13 -28,11 +28,14 @@@ import jalview.util.LinkedIdentityHashS
  import jalview.util.MessageManager;
  
  import java.util.ArrayList;
 +import java.util.Arrays;
 +import java.util.BitSet;
  import java.util.Collections;
  import java.util.Enumeration;
+ import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Hashtable;
 +import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
@@@ -1909,124 -1917,77 +1910,156 @@@ public class Alignment implements Align
    }
  
    @Override
 -  public int[] getVisibleStartAndEndIndex(List<int[]> hiddenCols)
 +  public boolean setHiddenColumns(HiddenColumns cols)
    {
-     boolean changed = cols == null ? hiddenCols != null
-             : !cols.equals(hiddenCols);
-     hiddenCols = cols;
-     return changed;
 -    int[] alignmentStartEnd = new int[] { 0, getWidth() - 1 };
 -    int startPos = alignmentStartEnd[0];
 -    int endPos = alignmentStartEnd[1];
++  boolean changed = cols == null ? hiddenCols != null
++          : !cols.equals(hiddenCols);
++  hiddenCols = cols;
++  return changed;
 +  }
 +  @Override
 +  public void setupJPredAlignment()
 +  {
 +    SequenceI repseq = getSequenceAt(0);
 +    setSeqrep(repseq);
 +    HiddenColumns cs = new HiddenColumns();
 +    cs.hideList(repseq.getInsertions());
 +    setHiddenColumns(cs);
 +  }
  
 -    int[] lowestRange = new int[] { -1, -1 };
 -    int[] higestRange = new int[] { -1, -1 };
 +  @Override
 +  public HiddenColumns propagateInsertions(SequenceI profileseq,
 +          AlignmentView input)
 +  {
 +    int profsqpos = 0;
  
 -    for (int[] hiddenCol : hiddenCols)
 -    {
 -      lowestRange = (hiddenCol[0] <= startPos) ? hiddenCol : lowestRange;
 -      higestRange = (hiddenCol[1] >= endPos) ? hiddenCol : higestRange;
 -    }
 +    char gc = getGapCharacter();
 +    Object[] alandhidden = input.getAlignmentAndHiddenColumns(gc);
 +    HiddenColumns nview = (HiddenColumns) alandhidden[1];
 +    SequenceI origseq = ((SequenceI[]) alandhidden[0])[profsqpos];
 +    return propagateInsertions(profileseq, origseq, nview);
 +  }
  
 -    if (lowestRange[0] == -1 && lowestRange[1] == -1)
 -    {
 -      startPos = alignmentStartEnd[0];
 -    }
 -    else
 +  /**
 +   * 
 +   * @param profileseq
 +   *          sequence in al which corresponds to origseq
 +   * @param al
 +   *          alignment which is to have gaps inserted into it
 +   * @param origseq
 +   *          sequence corresponding to profileseq which defines gap map for
 +   *          modifying al
 +   */
 +  private HiddenColumns propagateInsertions(SequenceI profileseq,
 +          SequenceI origseq, HiddenColumns hc)
 +  {
 +    // take the set of hidden columns, and the set of gaps in origseq,
 +    // and remove all the hidden gaps from hiddenColumns
 +
 +    // first get the gaps as a Bitset
 +    // then calculate hidden ^ not(gap)
 +    BitSet gaps = origseq.gapBitset();
 +    hc.andNot(gaps);
 +
 +    // for each sequence in the alignment, except the profile sequence,
 +    // insert gaps corresponding to each hidden region but where each hidden
 +    // column region is shifted backwards by the number of preceding visible
 +    // gaps update hidden columns at the same time
 +    HiddenColumns newhidden = new HiddenColumns();
 +
 +    int numGapsBefore = 0;
 +    int gapPosition = 0;
 +    Iterator<int[]> it = hc.iterator();
 +    while (it.hasNext())
      {
 -      startPos = lowestRange[1] + 1;
 +      int[] region = it.next();
 +
 +      // get region coordinates accounting for gaps
 +      // we can rely on gaps not being *in* hidden regions because we already
 +      // removed those
 +      while (gapPosition < region[0])
 +      {
 +        gapPosition++;
 +        if (gaps.get(gapPosition))
 +        {
 +          numGapsBefore++;
 +        }
 +      }
 +
 +      int left = region[0] - numGapsBefore;
 +      int right = region[1] - numGapsBefore;
 +
 +      newhidden.hideColumns(left, right);
 +      padGaps(left, right, profileseq);
      }
 +    return newhidden;
 +  }
 +
 +  /**
 +   * Pad gaps in all sequences in alignment except profileseq
 +   * 
 +   * @param left
 +   *          position of first gap to insert
 +   * @param right
 +   *          position of last gap to insert
 +   * @param profileseq
 +   *          sequence not to pad
 +   */
 +  private void padGaps(int left, int right, SequenceI profileseq)
 +  {
 +    char gc = getGapCharacter();
  
 -    if (higestRange[0] == -1 && higestRange[1] == -1)
 +    // make a string with number of gaps = length of hidden region
 +    StringBuilder sb = new StringBuilder();
 +    for (int g = 0; g < right - left + 1; g++)
      {
 -      endPos = alignmentStartEnd[1];
 +      sb.append(gc);
      }
 -    else
 +
 +    // loop over the sequences and pad with gaps where required
 +    for (int s = 0, ns = getHeight(); s < ns; s++)
      {
 -      endPos = higestRange[0] - 1;
 +      SequenceI sqobj = getSequenceAt(s);
 +      if ((sqobj != profileseq) && (sqobj.getLength() >= left))
 +      {
 +        String sq = sqobj.getSequenceAsString();
 +        sqobj.setSequence(
 +                sq.substring(0, left) + sb.toString() + sq.substring(left));
 +      }
      }
 -    return new int[] { startPos, endPos };
 -  }
 -  @Override
 -  public void setHiddenColumns(HiddenColumns cols)
 -  {
 -    hiddenCols = cols;
    }
  
 -  Map<Object, ContactMatrixI> contactmaps = new HashMap<Object, ContactMatrixI>();
++  Map<Object, ContactMatrixI> contactmaps = new HashMap<>();
+   @Override
+   public
+   ContactListI getContactListFor(AlignmentAnnotation _aa, int column)
+   {
+     ContactMatrixI cm = contactmaps.get(_aa.annotationId);
+     if (cm == null)
+     {
+       return null;
+     }
+     return cm.getContactList(column);
+   }
+   @Override
+   public AlignmentAnnotation addContactList(ContactMatrixI cm)
+   {
+     Annotation _aa[] = new Annotation[getWidth()];
+     Annotation dummy = new Annotation(0.0f);
+     for (int i = 0; i < _aa.length; _aa[i++] = dummy)
+     {
+       ;
+     }
+     AlignmentAnnotation aa = new AlignmentAnnotation("Contact Matrix",
+             "Contact Matrix", _aa);
+     aa.graph = AlignmentAnnotation.CUSTOMRENDERER;
+     aa.graphMin = cm.getMin();
+     aa.graphMax = cm.getMax();
+     aa.editable = false;
+     // aa.autoCalculated = true;
+     contactmaps.put(aa.annotationId, cm);
+     addAnnotation(aa);
+     return aa;
+   }
  }
@@@ -595,33 -581,25 +595,44 @@@ public interface AlignmentI extends Ann
    AlignedCodonFrame getMapping(SequenceI mapFrom, SequenceI mapTo);
  
    /**
 -   * Calculate the visible start and end index of an alignment. The result is
 -   * returned an int array where: int[0] = startIndex, and int[1] = endIndex.
 +   * Set the hidden columns collection on the alignment. Answers true if the
 +   * hidden column selection changed, else false.
     * 
 -   * @param hiddenCols
 +   * @param cols
     * @return
     */
 -  public int[] getVisibleStartAndEndIndex(List<int[]> hiddenCols);
 +  public boolean setHiddenColumns(HiddenColumns cols);
 +
 +  /**
 +   * Set the first sequence as representative and hide its insertions. Typically
 +   * used when loading JPred files.
 +   */
 +  public void setupJPredAlignment();
 +
 +  /**
 +   * Add gaps into the sequences aligned to profileseq under the given
 +   * AlignmentView
 +   * 
 +   * @param profileseq
 +   *          sequence in al which sequences are aligned to
 +   * @param input
 +   *          alignment view where sequence corresponding to profileseq is first
 +   *          entry
 +   * @return new HiddenColumns for new alignment view, with insertions into
 +   *         profileseq marked as hidden.
 +   */
 +  public HiddenColumns propagateInsertions(SequenceI profileseq,
 +          AlignmentView input);
  
+   /**
+    * resolve a contact list instance (if any) associated with the annotation row
+    * and column position
+    * 
+    * @param _aa
+    * @param column
+    * @return
+    */
+   ContactListI getContactListFor(AlignmentAnnotation _aa, int column);
+   AlignmentAnnotation addContactList(ContactMatrixI cm);
 -
 -  public void setHiddenColumns(HiddenColumns cols);
  }
@@@ -544,13 -527,13 +544,13 @@@ public class AnnotationPanel extends JP
          {
            activeRow = i;
          }
-         else if (aa[i].graph > 0)
+         else if (aa[i].graph != 0)
          {
 -          // Stretch Graph
 +          /*
 +           * we have clicked on a resizable graph annotation
 +           */
            graphStretch = i;
 -          graphStretchY = y;
          }
 -
          break;
        }
      }
index 0000000,bed54e8..7413e65
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,118 +1,118 @@@
+ /**
+  * 
+  */
+ package jalview.renderer;
+ import jalview.api.AlignViewportI;
+ import jalview.datamodel.AlignmentAnnotation;
+ import jalview.datamodel.Annotation;
+ import jalview.datamodel.ColumnSelection;
+ import jalview.datamodel.ContactListI;
+ import jalview.datamodel.ContactRange;
+ import jalview.datamodel.HiddenColumns;
+ import jalview.renderer.api.AnnotationRowRendererI;
+ import java.awt.Color;
+ import java.awt.Graphics;
+ /**
+  * @author jprocter
+  *
+  */
+ public class ContactMapRenderer implements AnnotationRowRendererI
+ {
+   @Override
+   public void renderRow(Graphics g, int charWidth, int charHeight,
+           boolean hasHiddenColumns, AlignViewportI viewport, HiddenColumns hiddenColumns,
+           ColumnSelection columnSelection, AlignmentAnnotation _aa,
+           Annotation[] aa_annotations, int sRes, int eRes, float min,
+           float max, int y)
+   {
+     if (sRes > aa_annotations.length)
+     {
+       return;
+     }
+     eRes = Math.min(eRes, aa_annotations.length);
+     int x = 0, y2 = y;
+     g.setColor(Color.pink);
+     g.drawLine(x, y2, (eRes - sRes) * charWidth, y2);
+     int column;
+     int aaMax = aa_annotations.length - 1;
+     while (x < eRes - sRes)
+     {
+       column = sRes + x;
+       if (hasHiddenColumns)
+       {
 -        column = hiddenColumns.adjustForHiddenColumns(column);
++        column = hiddenColumns.visibleToAbsoluteColumn(column);
+       }
+       if (column > aaMax)
+       {
+         break;
+       }
+       if (aa_annotations[column] == null)
+       {
+         x++;
+         continue;
+       }
+       /*
+        * {profile type, #values, total count, char1, pct1, char2, pct2...}
+        */
+       ContactListI contacts = viewport.getContactList(_aa, column);
+       if (contacts == null)
+       {
+         return;
+       }
+       // cell height to render
+       double scale = (_aa.graphHeight < contacts.getContactHeight()) ? 1
+               : ((double) _aa.graphHeight)
+                       / (double) contacts.getContactHeight();
+       int cstart, cend = -1;
+       for (int ht = y2, eht = y2 - _aa.graphHeight; ht >= eht; ht -= scale)
+       {
+         cstart = cend + 1;
+         cend = Math.max(cstart + 1, contacts.getContactHeight()
+                 * ((ht - y2) / _aa.graphHeight));
+         // TODO show maximum colour for range - sort of done
+         // also need a 'getMaxPosForRange(start,end)'
+         g.setColor(getColorForRange(min, max, contacts, cstart, cend));
+         if (scale > 1)
+         {
+           g.fillRect(x * charWidth, ht, charWidth, 1 + (int) scale);
+         }
+         else
+         {
+           g.drawLine(x * charWidth, ht, (x + 1) * charWidth, ht);
+         }
+       }
+       x++;
+     }
+   }
+   Color minColor = Color.white, maxColor = Color.magenta;
+   Color shadeFor(float min, float max, float value)
+   {
+     return jalview.util.ColorUtils.getGraduatedColour(value, 0, minColor,
+             max, maxColor);
+   }
+   public Color getColorForRange(float min, float max, ContactListI cl,
+           int i, int j)
+   {
+     ContactRange cr = cl.getRangeFor(i, j);
+     // average for moment - probably more interested in maxIntProj though
+     return shadeFor(min, max, (float) cr.getMean());
+   }
+ }
@@@ -34,7 -33,9 +34,8 @@@ import jalview.datamodel.AlignmentAnnot
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.AlignmentView;
  import jalview.datamodel.Annotation;
 -import jalview.datamodel.CigarArray;
  import jalview.datamodel.ColumnSelection;
+ import jalview.datamodel.ContactListI;
  import jalview.datamodel.HiddenColumns;
  import jalview.datamodel.HiddenSequences;
  import jalview.datamodel.ProfilesI;