JAL-1152 with sticky annotation sort order that updates as sequences are
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 30 Oct 2014 16:29:01 +0000 (16:29 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Thu, 30 Oct 2014 16:29:01 +0000 (16:29 +0000)
moved up or down

resources/lang/Messages.properties
src/jalview/analysis/AnnotationSorter.java
src/jalview/bin/Cache.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignViewport.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/jbgui/GAlignFrame.java
test/jalview/analysis/AnnotationSorterTest.java

index 24b184f..176e3e9 100644 (file)
@@ -200,8 +200,8 @@ label.average_distance_bloslum62 = Average Distance Using BLOSUM62
 label.neighbour_blosum62 = Neighbour Joining Using BLOSUM62
 label.show_annotations = Show annotations
 label.hide_annotations = Hide annotations
-label.show_all_annotations = Show all annotations
-label.hide_all_annotations = Hide all annotations
+label.show_all_annotations = Show all
+label.hide_all_annotations = Hide all
 label.hide_all = Hide all
 label.add_reference_annotations = Add reference annotations
 label.find_tip = Search alignment, selection or sequence ids for a subsequence (ignoring gaps).<br>Accepts regular expressions - search Help for 'regex' for details.
@@ -480,8 +480,8 @@ label.sort_by = Sort by
 label.sort_by_score = Sort by Score
 label.sort_by_density = Sort by Density
 label.sequence_sort_by_density = Sequence sort by Density
-label.sort_annotations_by_sequence = Sort annotations by sequence order
-label.sort_annotations_by_type = Sort annotations by type
+label.sort_annotations_by_sequence = Sort by sequence
+label.sort_annotations_by_type = Sort by type
 label.reveal = Reveal
 label.hide_columns = Hide Columns
 label.load_jalview_annotations = Load Jalview Annotations or Features File
index 4ee2b86..289544c 100644 (file)
@@ -17,6 +17,11 @@ import java.util.Comparator;
 public class AnnotationSorter
 {
 
+  public enum SortOrder
+  {
+    SEQUENCE_AND_TYPE, TYPE_AND_SEQUENCE
+  }
+  
   private final AlignmentI alignment;
 
   public AnnotationSorter(AlignmentI alignmentI)
@@ -117,39 +122,49 @@ public class AnnotationSorter
     }
   };
 
+  private final Comparator<? super AlignmentAnnotation> DEFAULT_COMPARATOR = bySequenceAndType;
+  
   /**
-   * Sort by annotation type (label), within sequence order.
-   * Non-sequence-related annotations sort to the end.
+   * Sort by the specified order.
    * 
    * @param alignmentAnnotations
+   * @param order
    */
-  public void sortBySequenceAndType(
-          AlignmentAnnotation[] alignmentAnnotations)
+  public void sort(AlignmentAnnotation[] alignmentAnnotations,
+          SortOrder order)
   {
+    Comparator<? super AlignmentAnnotation> comparator = getComparator(order);
+
     if (alignmentAnnotations != null)
     {
       synchronized (alignmentAnnotations)
       {
-        Arrays.sort(alignmentAnnotations, bySequenceAndType);
+        Arrays.sort(alignmentAnnotations, comparator);
       }
     }
   }
 
   /**
-   * Sort by sequence order within annotation type (label). Non-sequence-related
-   * annotations sort to the end.
+   * Get the comparator for the specified sort order.
    * 
-   * @param alignmentAnnotations
+   * @param order
+   * @return
    */
-  public void sortByTypeAndSequence(
-          AlignmentAnnotation[] alignmentAnnotations)
+  private Comparator<? super AlignmentAnnotation> getComparator(
+          SortOrder order)
   {
-    if (alignmentAnnotations != null)
+    if (order == null)
     {
-      synchronized (alignmentAnnotations)
-      {
-        Arrays.sort(alignmentAnnotations, byTypeAndSequence);
-      }
+      return DEFAULT_COMPARATOR;
+    }
+    switch (order)
+    {
+    case SEQUENCE_AND_TYPE:
+      return this.bySequenceAndType;
+    case TYPE_AND_SEQUENCE:
+      return this.byTypeAndSequence;
+    default:
+      throw new UnsupportedOperationException(order.toString());
     }
   }
 
index 1214371..7aab4ae 100755 (executable)
@@ -24,12 +24,21 @@ import jalview.ws.dbsources.das.api.DasSourceRegistryI;
 import jalview.ws.dbsources.das.datamodel.DasSourceRegistry;
 
 import java.awt.Color;
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
-import java.util.*;
+import java.util.Date;
+import java.util.Properties;
 
-import org.apache.log4j.*;
+import org.apache.log4j.ConsoleAppender;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.SimpleLayout;
 
 /**
  * Stores and retrieves Jalview Application Properties Lists and fields within
@@ -63,6 +72,7 @@ import org.apache.log4j.*;
  * <li>SHOW_QUALITY show alignment quality annotation</li>
  * <li>SHOW_ANNOTATIONS show alignment annotation rows</li>
  * <li>SHOW_CONSERVATION show alignment conservation annotation</li>
+ * <li>SORT_ANNOTATIONS currently either SEQUENCE_AND_TYPE or TYPE_AND_SEQUENCE</li>
  * <li>CENTRE_COLUMN_LABELS centre the labels at each column in a displayed
  * annotation row</li>
  * <li>DEFAULT_COLOUR default colour scheme to apply for a new alignment</li>
@@ -709,15 +719,21 @@ public class Cache
         if (log != null)
         {
           if (re != null)
+          {
             log.debug("Caught runtime exception in googletracker init:", re);
+          }
           if (ex != null)
+          {
             log.warn(
                     "Failed to initialise GoogleTracker for Jalview Desktop with version "
                             + vrs, ex);
+          }
           if (err != null)
+          {
             log.error(
                     "Whilst initing GoogleTracker for Jalview Desktop version "
                             + vrs, err);
+          }
         }
         else
         {
index a048721..a50776e 100644 (file)
@@ -23,7 +23,7 @@ package jalview.gui;
 import jalview.analysis.AAFrequency;
 import jalview.analysis.AlignmentSorter;
 import jalview.analysis.AlignmentUtils;
-import jalview.analysis.AnnotationSorter;
+import jalview.analysis.AnnotationSorter.SortOrder;
 import jalview.analysis.Conservation;
 import jalview.analysis.CrossRef;
 import jalview.analysis.NJTree;
@@ -5782,23 +5782,13 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener,
     this.alignPanel.paintAlignment(true);
   }
 
+  /**
+   * Store selected annotation sort order for the view and repaint.
+   */
   @Override
-  protected void sortAnnotationsByType_actionPerformed()
-  {
-    AnnotationSorter sorter = new AnnotationSorter(
-            this.alignPanel.getAlignment());
-    sorter.sortByTypeAndSequence(this.alignPanel.getAlignment()
-            .getAlignmentAnnotation());
-    alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
-  }
-
-  @Override
-  protected void sortAnnotationsBySequence_actionPerformed()
+  protected void sortAnnotations_actionPerformed(SortOrder sortOrder)
   {
-    AnnotationSorter sorter = new AnnotationSorter(
-            this.alignPanel.getAlignment());
-    sorter.sortBySequenceAndType(this.alignPanel.getAlignment()
-            .getAlignmentAnnotation());
+    this.alignPanel.av.setSortAnnotationsBy(sortOrder);
     alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState());
   }
 }
index 6d6531f..6969513 100644 (file)
@@ -38,6 +38,7 @@
  */
 package jalview.gui;
 
+import jalview.analysis.AnnotationSorter.SortOrder;
 import jalview.analysis.NJTree;
 import jalview.api.AlignViewportI;
 import jalview.bin.Cache;
@@ -99,6 +100,8 @@ public class AlignViewport extends AlignmentViewport implements
 
   boolean showAnnotation = true;
 
+  SortOrder sortAnnotationsBy = null;
+
   int charHeight;
 
   int charWidth;
@@ -358,12 +361,14 @@ public class AlignViewport extends AlignmentViewport implements
       }
     }
 
-    wrapAlignment = jalview.bin.Cache.getDefault("WRAP_ALIGNMENT", false);
-    showUnconserved = jalview.bin.Cache.getDefault("SHOW_UNCONSERVED",
+    wrapAlignment = Cache.getDefault("WRAP_ALIGNMENT", false);
+    showUnconserved = Cache.getDefault("SHOW_UNCONSERVED",
             false);
-    sortByTree = jalview.bin.Cache.getDefault("SORT_BY_TREE", false);
-    followSelection = jalview.bin.Cache.getDefault("FOLLOW_SELECTIONS",
+    sortByTree = Cache.getDefault("SORT_BY_TREE", false);
+    followSelection = Cache.getDefault("FOLLOW_SELECTIONS",
             true);
+    sortAnnotationsBy = SortOrder.valueOf(Cache.getDefault(
+            "SORT_ANNOTATIONS", SortOrder.SEQUENCE_AND_TYPE.name()));
   }
 
   /**
@@ -969,8 +974,10 @@ public class AlignViewport extends AlignmentViewport implements
   {
     // TODO: JAL-1126
     if (historyList == null || redoList == null)
+    {
       return new long[]
       { -1, -1 };
+    }
     return new long[]
     { historyList.hashCode(), this.redoList.hashCode() };
   }
@@ -1207,7 +1214,9 @@ public class AlignViewport extends AlignmentViewport implements
         Vector pdbs = alignment.getSequenceAt(i).getDatasetSequence()
                 .getPDBId();
         if (pdbs == null)
+        {
           continue;
+        }
         SequenceI sq;
         for (int p = 0; p < pdbs.size(); p++)
         {
@@ -1215,7 +1224,9 @@ public class AlignViewport extends AlignmentViewport implements
           if (p1.getId().equals(pdb.getId()))
           {
             if (!seqs.contains(sq = alignment.getSequenceAt(i)))
+            {
               seqs.add(sq);
+            }
 
             continue;
           }
@@ -1264,4 +1275,14 @@ public class AlignViewport extends AlignmentViewport implements
       Cache.log.debug("trigger update for " + calcId);
     }
   }
+
+  protected SortOrder getSortAnnotationsBy()
+  {
+    return sortAnnotationsBy;
+  }
+
+  protected void setSortAnnotationsBy(SortOrder sortAnnotationsBy)
+  {
+    this.sortAnnotationsBy = sortAnnotationsBy;
+  }
 }
index cdac5b4..dd21819 100644 (file)
  */
 package jalview.gui;
 
-import java.beans.*;
-import java.io.*;
-
-import java.awt.*;
-import java.awt.event.*;
-import java.awt.print.*;
-import javax.swing.*;
-
+import jalview.analysis.AnnotationSorter;
 import jalview.api.AlignmentViewPanel;
 import jalview.bin.Cache;
-import jalview.datamodel.*;
-import jalview.jbgui.*;
-import jalview.schemes.*;
+import jalview.datamodel.AlignmentI;
+import jalview.datamodel.SearchResults;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceGroup;
+import jalview.datamodel.SequenceI;
+import jalview.jbgui.GAlignmentPanel;
+import jalview.schemes.ResidueProperties;
 import jalview.structure.StructureSelectionManager;
 import jalview.util.MessageManager;
 
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+
+import javax.swing.SwingUtilities;
+
 /**
  * DOCUMENT ME!
  * 
@@ -179,7 +196,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
 
     int afwidth = (alignFrame != null ? alignFrame.getWidth() : 300);
     int maxwidth = Math.max(20,
-            Math.min(afwidth - 200, (int) 2 * afwidth / 3));
+            Math.min(afwidth - 200, 2 * afwidth / 3));
     return calculateIdWidth(maxwidth);
   }
 
@@ -718,8 +735,15 @@ public class AlignmentPanel extends GAlignmentPanel implements
     }
   }
 
+  /**
+   * Repaint the alignment including the annotations and overview panels (if
+   * shown).
+   */
   public void paintAlignment(boolean updateOverview)
   {
+    new AnnotationSorter(getAlignment()).sort(getAlignment()
+            .getAlignmentAnnotation(),
+            av.getSortAnnotationsBy());
     repaint();
 
     if (updateOverview)
@@ -841,7 +865,7 @@ public class AlignmentPanel extends GAlignmentPanel implements
     // / How many sequences and residues can we fit on a printable page?
     int totalRes = (pwidth - idWidth) / av.getCharWidth();
 
-    int totalSeq = (int) ((pheight - scaleHeight) / av.getCharHeight()) - 1;
+    int totalSeq = (pheight - scaleHeight) / av.getCharHeight() - 1;
 
     int pagesWide = (av.getAlignment().getWidth() / totalRes) + 1;
 
@@ -954,10 +978,10 @@ public class AlignmentPanel extends GAlignmentPanel implements
       int offset = -alabels.scrollOffset;
       pg.translate(0, offset);
       pg.translate(-idWidth - 3, (endSeq - startSeq) * av.charHeight + 3);
-      alabels.drawComponent((Graphics2D) pg, idWidth);
+      alabels.drawComponent(pg, idWidth);
       pg.translate(idWidth + 3, 0);
       annotationPanel.renderer.drawComponent(annotationPanel, av,
-              (Graphics2D) pg, -1, startRes, endRes + 1);
+              pg, -1, startRes, endRes + 1);
       pg.translate(0, -offset);
     }
 
index 8e0e6e3..175adea 100644 (file)
@@ -23,6 +23,7 @@ package jalview.gui;
 import jalview.analysis.AAFrequency;
 import jalview.analysis.AlignmentAnnotationUtils;
 import jalview.analysis.AnnotationSorter;
+import jalview.analysis.AnnotationSorter.SortOrder;
 import jalview.analysis.Conservation;
 import jalview.commands.ChangeCaseCommand;
 import jalview.commands.EditCommand;
@@ -1902,9 +1903,9 @@ public class PopupMenu extends JPopupMenu
     }
     // TODO: save annotation sort order on AlignViewport
     // do sorting from AlignmentPanel.updateAnnotation()
-    new AnnotationSorter(this.ap.getAlignment())
-            .sortBySequenceAndType(this.ap.getAlignment()
-                    .getAlignmentAnnotation());
+    new AnnotationSorter(this.ap.getAlignment()).sort(this.ap
+            .getAlignment().getAlignmentAnnotation(),
+            SortOrder.SEQUENCE_AND_TYPE);
     refresh();
   }
 
index 5e60a85..3591056 100755 (executable)
@@ -20,6 +20,7 @@
  */
 package jalview.jbgui;
 
+import jalview.analysis.AnnotationSorter.SortOrder;
 import jalview.bin.Cache;
 import jalview.gui.JvSwingUtils;
 import jalview.schemes.ColourSchemeProperty;
@@ -310,9 +311,9 @@ public class GAlignFrame extends JInternalFrame
 
   protected JMenuItem hideAllAnnotations = new JMenuItem();
 
-  protected JMenuItem sortAnnBySequence = new JMenuItem();
+  protected JCheckBoxMenuItem sortAnnBySequence = new JCheckBoxMenuItem();
 
-  protected JMenuItem sortAnnByType = new JMenuItem();
+  protected JCheckBoxMenuItem sortAnnByType = new JCheckBoxMenuItem();
 
   protected JCheckBoxMenuItem hiddenMarkers = new JCheckBoxMenuItem();
 
@@ -1112,7 +1113,11 @@ public class GAlignFrame extends JInternalFrame
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        sortAnnotationsBySequence_actionPerformed();
+        sortAnnBySequence.setEnabled(false);
+        sortAnnBySequence.setState(true);
+        sortAnnByType.setEnabled(true);
+        sortAnnByType.setState(false);
+        sortAnnotations_actionPerformed(SortOrder.SEQUENCE_AND_TYPE);
       }
     });
     sortAnnByType.setText(MessageManager
@@ -1122,7 +1127,11 @@ public class GAlignFrame extends JInternalFrame
       @Override
       public void actionPerformed(ActionEvent e)
       {
-        sortAnnotationsByType_actionPerformed();
+        sortAnnByType.setEnabled(false);
+        sortAnnByType.setState(true);
+        sortAnnBySequence.setEnabled(true);
+        sortAnnBySequence.setState(false);
+        sortAnnotations_actionPerformed(SortOrder.TYPE_AND_SEQUENCE);
       }
     });
     colourTextMenuItem.setText(MessageManager
@@ -2303,15 +2312,10 @@ public class GAlignFrame extends JInternalFrame
 
   /**
    * Action on clicking sort annotations by type.
+   * 
+   * @param sortOrder
    */
-  protected void sortAnnotationsByType_actionPerformed()
-  {
-  }
-
-  /**
-   * Action on clicking sort annotations by sequence
-   */
-  protected void sortAnnotationsBySequence_actionPerformed()
+  protected void sortAnnotations_actionPerformed(SortOrder sortOrder)
   {
   }
 
index 879aa9b..97dabbc 100644 (file)
@@ -2,6 +2,7 @@ package jalview.analysis;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import jalview.analysis.AnnotationSorter.SortOrder;
 import jalview.datamodel.Alignment;
 import jalview.datamodel.AlignmentAnnotation;
 import jalview.datamodel.Sequence;
@@ -97,7 +98,7 @@ public class AnnotationSorterTest
     // @formatter:on
 
     AnnotationSorter testee = new AnnotationSorter(al);
-    testee.sortBySequenceAndType(anns);
+    testee.sort(anns, SortOrder.SEQUENCE_AND_TYPE);
     assertEquals("label5", anns[0].label); // for sequence 0
     assertEquals("label0", anns[1].label); // for sequence 1
     assertEquals("iron", anns[2].label); // sequence 3 /iron
@@ -133,7 +134,7 @@ public class AnnotationSorterTest
     // @formatter:on
 
     AnnotationSorter testee = new AnnotationSorter(al);
-    testee.sortByTypeAndSequence(anns);
+    testee.sort(anns, SortOrder.TYPE_AND_SEQUENCE);
     assertEquals("IRON", anns[0].label); // IRON / sequence 0
     assertEquals("iron", anns[1].label); // iron / sequence 3
     assertEquals("label0", anns[2].label); // label0 / sequence 1
@@ -144,16 +145,16 @@ public class AnnotationSorterTest
   }
 
   @Test
-  public void testSortBySequenceAndType_timing()
+  public void testSort_timingPresorted()
   {
-    final long targetTime = 300;        // ms
+    final long targetTime = 100; // ms
     final int numSeqs = 10000;
     final int numAnns = 20000;
     al = buildAlignment(numSeqs);
     anns = buildAnnotations(numAnns);
 
     /*
-     * Set the annotations in random order with respect to the sequences
+     * Set the annotations presorted by label
      */
     Random r = new Random();
     final SequenceI[] sequences = al.getSequencesArray();
@@ -161,15 +162,102 @@ public class AnnotationSorterTest
     {
       SequenceI randomSequenceRef = sequences[r.nextInt(sequences.length)];
       anns[i].sequenceRef = randomSequenceRef;
+      anns[i].label = "label" + i;
     }
     long startTime = System.currentTimeMillis();
     AnnotationSorter testee = new AnnotationSorter(al);
-    testee.sortByTypeAndSequence(anns);
+    testee.sort(anns, SortOrder.TYPE_AND_SEQUENCE);
     long endTime = System.currentTimeMillis();
     final long elapsed = endTime - startTime;
-    System.out.println("Timing test for " + numSeqs + " sequences and "
+    System.out.println("Timing test for presorted " + numSeqs
+            + " sequences and "
             + numAnns + " annotations took " + elapsed + "ms");
     assertTrue("Sort took more than " + targetTime + "ms",
             elapsed <= targetTime);
   }
+
+  /**
+   * Timing test for sorting randomly sorted annotations
+   */
+  @Test
+  public void testSort_timingUnsorted()
+  {
+    final int numSeqs = 2000;
+    final int numAnns = 4000;
+    al = buildAlignment(numSeqs);
+    anns = buildAnnotations(numAnns);
+
+    /*
+     * Set the annotations in random order with respect to the sequences
+     */
+    Random r = new Random();
+    final SequenceI[] sequences = al.getSequencesArray();
+    for (int i = 0; i < anns.length; i++)
+    {
+      SequenceI randomSequenceRef = sequences[r.nextInt(sequences.length)];
+      anns[i].sequenceRef = randomSequenceRef;
+      anns[i].label = "label" + i;
+    }
+    long startTime = System.currentTimeMillis();
+    AnnotationSorter testee = new AnnotationSorter(al);
+    testee.sort(anns, SortOrder.SEQUENCE_AND_TYPE);
+    long endTime = System.currentTimeMillis();
+    final long elapsed = endTime - startTime;
+    System.out.println("Timing test for unsorted " + numSeqs
+            + " sequences and "
+            + numAnns + " annotations took " + elapsed + "ms");
+  }
+
+  /**
+   * Timing test for sorting annotations with a limited range of types (labels).
+   */
+  @Test
+  public void testSort_timingSemisorted()
+  {
+    final int numSeqs = 2000;
+    final int numAnns = 4000;
+    al = buildAlignment(numSeqs);
+    anns = buildAnnotations(numAnns);
+
+    String[] labels = new String[]
+    { "label1", "label2", "label3", "label4", "label5", "label6" };
+
+    /*
+     * Set the annotations in sequence order with randomly assigned labels.
+     */
+    Random r = new Random();
+    final SequenceI[] sequences = al.getSequencesArray();
+    for (int i = 0; i < anns.length; i++)
+    {
+      SequenceI sequenceRef = sequences[i % sequences.length];
+      anns[i].sequenceRef = sequenceRef;
+      anns[i].label = labels[r.nextInt(labels.length)];
+    }
+    long startTime = System.currentTimeMillis();
+    AnnotationSorter testee = new AnnotationSorter(al);
+    testee.sort(anns, SortOrder.TYPE_AND_SEQUENCE);
+    long endTime = System.currentTimeMillis();
+    long elapsed = endTime - startTime;
+    System.out.println("Sort by type for semisorted " + numSeqs
+            + " sequences and "
+            + numAnns + " annotations took " + elapsed + "ms");
+
+    // now resort by sequence
+    startTime = System.currentTimeMillis();
+    testee.sort(anns, SortOrder.SEQUENCE_AND_TYPE);
+    endTime = System.currentTimeMillis();
+    elapsed = endTime - startTime;
+    System.out.println("Resort by sequence for semisorted " + numSeqs
+            + " sequences and " + numAnns + " annotations took " + elapsed
+            + "ms");
+
+    // now resort by type
+    startTime = System.currentTimeMillis();
+    testee.sort(anns, SortOrder.TYPE_AND_SEQUENCE);
+    endTime = System.currentTimeMillis();
+    elapsed = endTime - startTime;
+    System.out.println("Resort by type for semisorted " + numSeqs
+            + " sequences and " + numAnns + " annotations took " + elapsed
+            + "ms");
+  }
 }