From d305e02d8b425bb501141ad32142aeb8572adc57 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Thu, 30 Oct 2014 16:29:01 +0000 Subject: [PATCH] JAL-1152 with sticky annotation sort order that updates as sequences are moved up or down --- resources/lang/Messages.properties | 8 +- src/jalview/analysis/AnnotationSorter.java | 45 ++++++---- src/jalview/bin/Cache.java | 22 ++++- src/jalview/gui/AlignFrame.java | 22 ++--- src/jalview/gui/AlignViewport.java | 29 ++++++- src/jalview/gui/AlignmentPanel.java | 54 ++++++++---- src/jalview/gui/PopupMenu.java | 7 +- src/jalview/jbgui/GAlignFrame.java | 28 ++++--- test/jalview/analysis/AnnotationSorterTest.java | 102 +++++++++++++++++++++-- 9 files changed, 238 insertions(+), 79 deletions(-) diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 24b184f..176e3e9 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -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).
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 diff --git a/src/jalview/analysis/AnnotationSorter.java b/src/jalview/analysis/AnnotationSorter.java index 4ee2b86..289544c 100644 --- a/src/jalview/analysis/AnnotationSorter.java +++ b/src/jalview/analysis/AnnotationSorter.java @@ -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 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 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 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()); } } diff --git a/src/jalview/bin/Cache.java b/src/jalview/bin/Cache.java index 1214371..7aab4ae 100755 --- a/src/jalview/bin/Cache.java +++ b/src/jalview/bin/Cache.java @@ -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.*; *
  • SHOW_QUALITY show alignment quality annotation
  • *
  • SHOW_ANNOTATIONS show alignment annotation rows
  • *
  • SHOW_CONSERVATION show alignment conservation annotation
  • + *
  • SORT_ANNOTATIONS currently either SEQUENCE_AND_TYPE or TYPE_AND_SEQUENCE
  • *
  • CENTRE_COLUMN_LABELS centre the labels at each column in a displayed * annotation row
  • *
  • DEFAULT_COLOUR default colour scheme to apply for a new alignment
  • @@ -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 { diff --git a/src/jalview/gui/AlignFrame.java b/src/jalview/gui/AlignFrame.java index a048721..a50776e 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -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()); } } diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java index 6d6531f..6969513 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -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; + } } diff --git a/src/jalview/gui/AlignmentPanel.java b/src/jalview/gui/AlignmentPanel.java index cdac5b4..dd21819 100644 --- a/src/jalview/gui/AlignmentPanel.java +++ b/src/jalview/gui/AlignmentPanel.java @@ -20,22 +20,39 @@ */ 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); } diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java index 8e0e6e3..175adea 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -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(); } diff --git a/src/jalview/jbgui/GAlignFrame.java b/src/jalview/jbgui/GAlignFrame.java index 5e60a85..3591056 100755 --- a/src/jalview/jbgui/GAlignFrame.java +++ b/src/jalview/jbgui/GAlignFrame.java @@ -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) { } diff --git a/test/jalview/analysis/AnnotationSorterTest.java b/test/jalview/analysis/AnnotationSorterTest.java index 879aa9b..97dabbc 100644 --- a/test/jalview/analysis/AnnotationSorterTest.java +++ b/test/jalview/analysis/AnnotationSorterTest.java @@ -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"); + } } -- 1.7.10.2