From: Jim Procter Date: Mon, 3 Nov 2014 16:02:18 +0000 (+0000) Subject: Merge branch 'develop' into features/JAL-1152annotationSorting X-Git-Tag: Release_2_8_2b1^2~36^2~11 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=8c0f33de9bd3c211883eddcc313ebc7978f7b0b5;hp=c70b1cb43732f143e6292d9080f73230599fced2;p=jalview.git Merge branch 'develop' into features/JAL-1152annotationSorting --- diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index f23275a..fe04105 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -102,6 +102,7 @@ action.find_all = Find all action.find_next = Find next action.file = File action.view = View +action.annotations = Annotations action.change_params = Change Parameters action.apply = Apply action.apply_threshold_all_groups = Apply threshold to all groups @@ -199,8 +200,10 @@ 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_seq_annotations = Show sequence related +label.hide_all_seq_annotations = Hide sequence related +label.show_all_al_annotations = Show alignment related +label.hide_all_al_annotations = Hide alignment related 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. @@ -238,6 +241,8 @@ label.show_consensus_logo = Show Consensus Logo label.norm_consensus_logo = Normalise Consensus Logo label.apply_all_groups = Apply to all groups label.autocalculated_annotation = Autocalculated Annotation +label.show_first = Show first +label.show_last = Show last label.min_colour = Minimum Colour label.max_colour = Maximum Colour label.use_original_colours = Use Original Colours @@ -479,6 +484,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 by sequence +label.sort_annotations_by_label = Sort by label label.reveal = Reveal label.hide_columns = Hide Columns label.load_jalview_annotations = Load Jalview Annotations or Features File diff --git a/src/jalview/analysis/AlignmentAnnotationUtils.java b/src/jalview/analysis/AlignmentAnnotationUtils.java index b5f77fd..9bdbf73 100644 --- a/src/jalview/analysis/AlignmentAnnotationUtils.java +++ b/src/jalview/analysis/AlignmentAnnotationUtils.java @@ -129,21 +129,28 @@ public class AlignmentAnnotationUtils } } /* - * finally add the 'composite group labels' to the appropriate lists, - * depending on whether the group is identified as visible or hidden + * Finally add the 'composite group labels' to the appropriate lists, + * depending on whether the group is identified as visible or hidden. Don't + * add the same label more than once (there may be many graph groups that + * generate it). */ for (String calcId : groupLabels.keySet()) { for (int group : groupLabels.get(calcId).keySet()) { final List groupLabel = groupLabels.get(calcId).get(group); + // don't want to duplicate 'same types in different order' + Collections.sort(groupLabel); if (visibleGraphGroups.get(group)) { if (!shownTypes.containsKey(calcId)) { shownTypes.put(calcId, new ArrayList>()); } - shownTypes.get(calcId).add(groupLabel); + if (!shownTypes.get(calcId).contains(groupLabel)) + { + shownTypes.get(calcId).add(groupLabel); + } } else { @@ -151,7 +158,10 @@ public class AlignmentAnnotationUtils { hiddenTypes.put(calcId, new ArrayList>()); } - hiddenTypes.get(calcId).add(groupLabel); + if (!hiddenTypes.get(calcId).contains(groupLabel)) + { + hiddenTypes.get(calcId).add(groupLabel); + } } } } @@ -196,8 +206,7 @@ public class AlignmentAnnotationUtils * @param anns * @return */ - public static List asList( - AlignmentAnnotation[] anns) + public static List asList(AlignmentAnnotation[] anns) { // TODO use AlignmentAnnotationI instead when it exists return (anns == null ? Collections. emptyList() diff --git a/src/jalview/analysis/AlignmentUtils.java b/src/jalview/analysis/AlignmentUtils.java index 2feeb91..929a855 100644 --- a/src/jalview/analysis/AlignmentUtils.java +++ b/src/jalview/analysis/AlignmentUtils.java @@ -20,12 +20,12 @@ */ package jalview.analysis; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.SequenceI; + import java.util.ArrayList; import java.util.List; -import jalview.datamodel.SequenceI; -import jalview.datamodel.AlignmentI; - /** * grab bag of useful alignment manipulation operations Expect these to be * refactored elsewhere at some point. @@ -124,4 +124,28 @@ public class AlignmentUtils newAl.setDataset(core.getDataset()); return newAl; } + + /** + * Returns the index (zero-based position) of a sequence in an alignment, or + * -1 if not found. + * + * @param al + * @param seq + * @return + */ + public static int getSequenceIndex(AlignmentI al, SequenceI seq) + { + int result = -1; + int pos = 0; + for (SequenceI alSeq : al.getSequences()) + { + if (alSeq == seq) + { + result = pos; + break; + } + pos++; + } + return result; + } } diff --git a/src/jalview/analysis/AnnotationSorter.java b/src/jalview/analysis/AnnotationSorter.java new file mode 100644 index 0000000..b9e9ed5 --- /dev/null +++ b/src/jalview/analysis/AnnotationSorter.java @@ -0,0 +1,284 @@ +package jalview.analysis; + +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.SequenceI; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * A helper class to sort all annotations associated with an alignment in + * various ways. + * + * @author gmcarstairs + * + */ +public class AnnotationSorter +{ + + public enum SequenceAnnotationOrder + { + SEQUENCE_AND_LABEL, LABEL_AND_SEQUENCE, NONE + } + + private final AlignmentI alignment; + + private boolean showAutocalcAbove; + + /** + * Constructor given an alignment and the location (top or bottom) of + * Consensus and similar. + * + * @param alignmentI + * @param showAutocalculatedAbove + */ + public AnnotationSorter(AlignmentI alignmentI, + boolean showAutocalculatedAbove) + { + this.alignment = alignmentI; + this.showAutocalcAbove = showAutocalculatedAbove; + } + + /** + * Default comparator sorts as follows by annotation type within sequence + * order: + *
    + *
  • annotations with a reference to a sequence in the alignment are sorted + * on sequence ordering
  • + *
  • other annotations go 'at the end', with their mutual order unchanged
  • + *
  • within the same sequence ref, sort by label (non-case-sensitive)
  • + *
+ */ + private final Comparator bySequenceAndLabel = new Comparator() + { + @Override + public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2) + { + if (o1 == null && o2 == null) + { + return 0; + } + if (o1 == null) + { + return -1; + } + if (o2 == null) + { + return 1; + } + + /* + * Ignore label (keep existing ordering) for + * Conservation/Quality/Consensus etc + */ + if (o1.sequenceRef == null && o2.sequenceRef == null) + { + return 0; + } + int sequenceOrder = compareSequences(o1, o2); + return sequenceOrder == 0 ? compareLabels(o1, o2) : sequenceOrder; + } + }; + + /** + * This comparator sorts as follows by sequence order within annotation type + *
    + *
  • annotations with a reference to a sequence in the alignment are sorted + * on label (non-case-sensitive)
  • + *
  • other annotations go 'at the end', with their mutual order unchanged
  • + *
  • within the same label, sort by order of the related sequences
  • + *
+ */ + private final Comparator byLabelAndSequence = new Comparator() + { + @Override + public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2) + { + if (o1 == null && o2 == null) + { + return 0; + } + if (o1 == null) + { + return -1; + } + if (o2 == null) + { + return 1; + } + + /* + * Ignore label (keep existing ordering) for + * Conservation/Quality/Consensus etc + */ + if (o1.sequenceRef == null && o2.sequenceRef == null) + { + return 0; + } + + /* + * Sort non-sequence-related before or after sequence-related. + */ + if (o1.sequenceRef == null) + { + return showAutocalcAbove ? -1 : 1; + } + if (o2.sequenceRef == null) + { + return showAutocalcAbove ? 1 : -1; + } + int labelOrder = compareLabels(o1, o2); + return labelOrder == 0 ? compareSequences(o1, o2) : labelOrder; + } + }; + + /** + * noSort leaves sort order unchanged, within sequence- and + * non-sequence-related annotations, but may switch the ordering of these + * groups. Note this is guaranteed (at least in Java 7) as Arrays.sort() is + * guaranteed to be 'stable' (not change ordering of equal items). + */ + private Comparator noSort = new Comparator() + { + @Override + public int compare(AlignmentAnnotation o1, AlignmentAnnotation o2) + { + if (o1 != null && o2 != null) + { + if (o1.sequenceRef == null && o2.sequenceRef != null) + { + return showAutocalcAbove ? -1 : 1; + } + if (o1.sequenceRef != null && o2.sequenceRef == null) + { + return showAutocalcAbove ? 1 : -1; + } + } + return 0; + } + }; + + /** + * Sort by the specified ordering of sequence-specific annotations. + * + * @param alignmentAnnotations + * @param order + */ + public void sort(AlignmentAnnotation[] alignmentAnnotations, + SequenceAnnotationOrder order) + { + Comparator comparator = getComparator(order); + + if (alignmentAnnotations != null) + { + synchronized (alignmentAnnotations) + { + Arrays.sort(alignmentAnnotations, comparator); + } + } + } + + /** + * Get the comparator for the specified sort order. + * + * @param order + * @return + */ + private Comparator getComparator( + SequenceAnnotationOrder order) + { + if (order == null) + { + return noSort; + } + switch (order) + { + case NONE: + return this.noSort; + case SEQUENCE_AND_LABEL: + return this.bySequenceAndLabel; + case LABEL_AND_SEQUENCE: + return this.byLabelAndSequence; + default: + throw new UnsupportedOperationException(order.toString()); + } + } + + /** + * Non-case-sensitive comparison of annotation labels. Returns zero if either + * argument is null. + * + * @param o1 + * @param o2 + * @return + */ + private int compareLabels(AlignmentAnnotation o1, AlignmentAnnotation o2) + { + if (o1 == null || o2 == null) + { + return 0; + } + String label1 = o1.label; + String label2 = o2.label; + if (label1 == null && label2 == null) + { + return 0; + } + if (label1 == null) + { + return -1; + } + if (label2 == null) + { + return 1; + } + return label1.toUpperCase().compareTo(label2.toUpperCase()); + } + + /** + * Comparison based on position of associated sequence (if any) in the + * alignment. Returns zero if either argument is null. + * + * @param o1 + * @param o2 + * @return + */ + private int compareSequences(AlignmentAnnotation o1, + AlignmentAnnotation o2) + { + SequenceI seq1 = o1.sequenceRef; + SequenceI seq2 = o2.sequenceRef; + if (seq1 == null && seq2 == null) + { + return 0; + } + /* + * Sort non-sequence-related before or after sequence-related. + */ + if (seq1 == null) + { + return showAutocalcAbove ? -1 : 1; + } + if (seq2 == null) + { + return showAutocalcAbove ? 1 : -1; + } + // get sequence index - but note -1 means 'at end' so needs special handling + int index1 = AlignmentUtils.getSequenceIndex(alignment, seq1); + int index2 = AlignmentUtils.getSequenceIndex(alignment, seq2); + if (index1 == index2) + { + return 0; + } + if (index1 == -1) + { + return -1; + } + if (index2 == -1) + { + return 1; + } + return Integer.compare(index1, index2); + } +} diff --git a/src/jalview/bin/Cache.java b/src/jalview/bin/Cache.java index 1214371..2a9f53d 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,10 @@ 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_LABEL or + * LABEL_AND_SEQUENCE
  • + *
  • SHOW_AUTOCALC_ABOVE true to show autocalculated annotations above + * sequence annotations
  • *
  • 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 +722,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 6d8fe6d..e6d86e9 100644 --- a/src/jalview/gui/AlignFrame.java +++ b/src/jalview/gui/AlignFrame.java @@ -279,7 +279,7 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } /** - * Make a new AlignFrame from exisiting alignmentPanels + * Make a new AlignFrame from existing alignmentPanels * * @param ap * AlignmentPanel @@ -746,10 +746,12 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, scaleRight.setVisible(av.wrapAlignment); annotationPanelMenuItem.setState(av.showAnnotation); /* - * Show/hide all annotations only enabled if annotation panel is shown + * Show/hide annotations only enabled if annotation panel is shown */ - showAllAnnotations.setEnabled(annotationPanelMenuItem.getState()); - hideAllAnnotations.setEnabled(annotationPanelMenuItem.getState()); + showAllSeqAnnotations.setEnabled(annotationPanelMenuItem.getState()); + hideAllSeqAnnotations.setEnabled(annotationPanelMenuItem.getState()); + showAllAlAnnotations.setEnabled(annotationPanelMenuItem.getState()); + hideAllAlAnnotations.setEnabled(annotationPanelMenuItem.getState()); viewBoxesMenuItem.setSelected(av.showBoxes); viewTextMenuItem.setSelected(av.showText); showNonconservedMenuItem.setSelected(av.getShowUnconserved()); @@ -3100,8 +3102,10 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, final boolean setVisible = annotationPanelMenuItem.isSelected(); viewport.setShowAnnotation(setVisible); alignPanel.setAnnotationVisible(setVisible); - this.showAllAnnotations.setEnabled(setVisible); - this.hideAllAnnotations.setEnabled(setVisible); + this.showAllSeqAnnotations.setEnabled(setVisible); + this.hideAllSeqAnnotations.setEnabled(setVisible); + this.showAllAlAnnotations.setEnabled(setVisible); + this.hideAllAlAnnotations.setEnabled(setVisible); } @Override @@ -5767,20 +5771,42 @@ public class AlignFrame extends GAlignFrame implements DropTargetListener, } /** - * Action on selection of menu option to Show or Hide all annotations. + * Action on selection of menu options to Show or Hide annotations. * - * @param visibile + * @param visible + * @param forSequences + * update sequence-related annotations + * @param forAlignment + * update non-sequence-related annotations */ @Override - protected void setAllAnnotationsVisibility(boolean visible) + protected void setAnnotationsVisibility(boolean visible, + boolean forSequences, boolean forAlignment) { for (AlignmentAnnotation aa : alignPanel.getAlignment() .getAlignmentAnnotation()) { - aa.visible = visible; + boolean apply = (aa.sequenceRef == null && forAlignment) + || (aa.sequenceRef != null && forSequences); + if (apply) + { + aa.visible = visible; + } } this.alignPanel.paintAlignment(true); } + + /** + * Store selected annotation sort order for the view and repaint. + */ + @Override + protected void sortAnnotations_actionPerformed() + { + this.alignPanel.av.setSortAnnotationsBy(getAnnotationSortOrder()); + this.alignPanel.av + .setShowAutocalculatedAbove(isShowAutoCalculatedAbove()); + alignPanel.updateAnnotation(applyAutoAnnotationSettings.getState()); + } } class PrintThread extends Thread diff --git a/src/jalview/gui/AlignViewport.java b/src/jalview/gui/AlignViewport.java index 6d6531f..d331e82 100644 --- a/src/jalview/gui/AlignViewport.java +++ b/src/jalview/gui/AlignViewport.java @@ -38,6 +38,7 @@ */ package jalview.gui; +import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder; 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; + SequenceAnnotationOrder 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", - false); - sortByTree = jalview.bin.Cache.getDefault("SORT_BY_TREE", false); - followSelection = jalview.bin.Cache.getDefault("FOLLOW_SELECTIONS", - true); + wrapAlignment = Cache.getDefault("WRAP_ALIGNMENT", false); + showUnconserved = Cache.getDefault("SHOW_UNCONSERVED", false); + sortByTree = Cache.getDefault("SORT_BY_TREE", false); + followSelection = Cache.getDefault("FOLLOW_SELECTIONS", true); + sortAnnotationsBy = SequenceAnnotationOrder.valueOf(Cache.getDefault( + "SORT_ANNOTATIONS", SequenceAnnotationOrder.NONE.name())); + showAutocalculatedAbove = Cache + .getDefault("SHOW_AUTOCALC_ABOVE", false); } /** @@ -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; } @@ -1247,6 +1258,8 @@ public class AlignViewport extends AlignmentViewport implements private Hashtable calcIdParams = new Hashtable(); + private boolean showAutocalculatedAbove; + public AutoCalcSetting getCalcIdSettingsFor(String calcId) { return calcIdParams.get(calcId); @@ -1264,4 +1277,24 @@ public class AlignViewport extends AlignmentViewport implements Cache.log.debug("trigger update for " + calcId); } } + + protected SequenceAnnotationOrder getSortAnnotationsBy() + { + return sortAnnotationsBy; + } + + protected void setSortAnnotationsBy(SequenceAnnotationOrder sortAnnotationsBy) + { + this.sortAnnotationsBy = sortAnnotationsBy; + } + + protected boolean isShowAutocalculatedAbove() + { + return showAutocalculatedAbove; + } + + protected void setShowAutocalculatedAbove(boolean showAutocalculatedAbove) + { + this.showAutocalculatedAbove = showAutocalculatedAbove; + } } diff --git a/src/jalview/gui/AlignmentPanel.java b/src/jalview/gui/AlignmentPanel.java index cdac5b4..d610931 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,17 @@ public class AlignmentPanel extends GAlignmentPanel implements } } + /** + * Repaint the alignment including the annotations and overview panels (if + * shown). + */ public void paintAlignment(boolean updateOverview) { + final AnnotationSorter sorter = new AnnotationSorter(getAlignment(), + av.isShowAutocalculatedAbove()); + sorter.sort(getAlignment() + .getAlignmentAnnotation(), + av.getSortAnnotationsBy()); repaint(); if (updateOverview) @@ -841,7 +867,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 +980,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 2845122..1c7be46 100644 --- a/src/jalview/gui/PopupMenu.java +++ b/src/jalview/gui/PopupMenu.java @@ -1875,7 +1875,6 @@ public class PopupMenu extends JPopupMenu * Add annotations at the top of the annotation, in the same order as their * related sequences. */ - int insertPosition = 0; for (SequenceI seq : candidates.keySet()) { for (AlignmentAnnotation ann : candidates.get(seq)) @@ -1896,7 +1895,7 @@ public class PopupMenu extends JPopupMenu // adjust for gaps copyAnn.adjustForAlignment(); // add to the alignment and set visible - this.ap.getAlignment().addAnnotation(copyAnn, insertPosition++); + this.ap.getAlignment().addAnnotation(copyAnn); copyAnn.visible = true; } } diff --git a/src/jalview/jbgui/GAlignFrame.java b/src/jalview/jbgui/GAlignFrame.java index afb0dc1..dba77dd 100755 --- a/src/jalview/jbgui/GAlignFrame.java +++ b/src/jalview/jbgui/GAlignFrame.java @@ -20,6 +20,7 @@ */ package jalview.jbgui; +import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder; import jalview.bin.Cache; import jalview.gui.JvSwingUtils; import jalview.schemes.ColourSchemeProperty; @@ -65,6 +66,8 @@ public class GAlignFrame extends JInternalFrame protected JMenu viewMenu = new JMenu(); + protected JMenu annotationsMenu = new JMenu(); + protected JMenu colourMenu = new JMenu(); protected JMenu calculateMenu = new JMenu(); @@ -304,9 +307,17 @@ public class GAlignFrame extends JInternalFrame JMenuItem showAllhidden = new JMenuItem(); - protected JMenuItem showAllAnnotations = new JMenuItem(); + protected JMenuItem showAllSeqAnnotations = new JMenuItem(); + + protected JMenuItem hideAllSeqAnnotations = new JMenuItem(); + + protected JMenuItem showAllAlAnnotations = new JMenuItem(); + + protected JMenuItem hideAllAlAnnotations = new JMenuItem(); + + protected JCheckBoxMenuItem sortAnnBySequence = new JCheckBoxMenuItem(); - protected JMenuItem hideAllAnnotations = new JMenuItem(); + protected JCheckBoxMenuItem sortAnnByLabel = new JCheckBoxMenuItem(); protected JCheckBoxMenuItem hiddenMarkers = new JCheckBoxMenuItem(); @@ -360,8 +371,16 @@ public class GAlignFrame extends JInternalFrame protected JCheckBoxMenuItem applyAutoAnnotationSettings = new JCheckBoxMenuItem(); + protected JCheckBoxMenuItem showAutoFirst = new JCheckBoxMenuItem(); + + protected JCheckBoxMenuItem showAutoLast = new JCheckBoxMenuItem(); + private JMenuItem grpsFromSelection = new JMenuItem(); + private SequenceAnnotationOrder annotationSortOrder; + + private boolean showAutoCalculatedAbove = false; + public GAlignFrame() { try @@ -611,6 +630,7 @@ public class GAlignFrame extends JInternalFrame }); editMenu.setText(MessageManager.getString("action.edit")); viewMenu.setText(MessageManager.getString("action.view")); + annotationsMenu.setText(MessageManager.getString("action.annotations")); colourMenu.setText(MessageManager.getString("action.colour")); calculateMenu.setText(MessageManager.getString("action.calculate")); webService.setText(MessageManager.getString("action.web_service")); @@ -1073,29 +1093,78 @@ public class GAlignFrame extends JInternalFrame annotationPanelMenuItem_actionPerformed(e); } }); - /* - * Show/hide all annotations only enabled if annotation panel is shown - */ - showAllAnnotations.setText(MessageManager - .getString("label.show_all_annotations")); - showAllAnnotations.setEnabled(annotationPanelMenuItem.getState()); - showAllAnnotations.addActionListener(new ActionListener() + showAllAlAnnotations.setText(MessageManager + .getString("label.show_all_al_annotations")); + final boolean isAnnotationPanelShown = annotationPanelMenuItem + .getState(); + showAllAlAnnotations.setEnabled(isAnnotationPanelShown); + showAllAlAnnotations.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + showAllAnnotations_actionPerformed(false, true); + } + }); + hideAllAlAnnotations.setText(MessageManager + .getString("label.hide_all_al_annotations")); + hideAllAlAnnotations.setEnabled(isAnnotationPanelShown); + hideAllAlAnnotations.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + hideAllAnnotations_actionPerformed(false, true); + } + }); + showAllSeqAnnotations.setText(MessageManager + .getString("label.show_all_seq_annotations")); + showAllSeqAnnotations.setEnabled(isAnnotationPanelShown); + showAllSeqAnnotations.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + showAllAnnotations_actionPerformed(true, false); + } + }); + hideAllSeqAnnotations.setText(MessageManager + .getString("label.hide_all_seq_annotations")); + hideAllSeqAnnotations.setEnabled(isAnnotationPanelShown); + hideAllSeqAnnotations.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + hideAllAnnotations_actionPerformed(true, false); + } + }); + sortAnnBySequence.setText(MessageManager + .getString("label.sort_annotations_by_sequence")); + sortAnnBySequence.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - showAllAnnotations_actionPerformed(); + boolean newState = sortAnnBySequence.getState(); + sortAnnByLabel.setState(false); + setAnnotationSortOrder(newState ? SequenceAnnotationOrder.SEQUENCE_AND_LABEL + : SequenceAnnotationOrder.NONE); + sortAnnotations_actionPerformed(); } }); - hideAllAnnotations.setText(MessageManager - .getString("label.hide_all_annotations")); - hideAllAnnotations.setEnabled(annotationPanelMenuItem.getState()); - hideAllAnnotations.addActionListener(new ActionListener() + sortAnnByLabel.setText(MessageManager + .getString("label.sort_annotations_by_label")); + sortAnnByLabel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - hideAllAnnotations_actionPerformed(); + boolean newState = sortAnnByLabel.getState(); + sortAnnBySequence.setState(false); + setAnnotationSortOrder(newState ? SequenceAnnotationOrder.LABEL_AND_SEQUENCE + : SequenceAnnotationOrder.NONE); + sortAnnotations_actionPerformed(); } }); colourTextMenuItem.setText(MessageManager @@ -1338,13 +1407,38 @@ public class GAlignFrame extends JInternalFrame applyAutoAnnotationSettings.setVisible(true); applyAutoAnnotationSettings.addActionListener(new ActionListener() { - @Override public void actionPerformed(ActionEvent e) { applyAutoAnnotationSettings_actionPerformed(e); } + }); + showAutoFirst.setText(MessageManager.getString("label.show_first")); + showAutoFirst.setState(Cache.getDefault("SHOW_AUTOCALC_ABOVE", false)); + showAutoFirst.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + boolean sortFirst = showAutoFirst.getState(); + setShowAutoCalculatedAbove(sortFirst); + showAutoLast.setState(!sortFirst); + sortAnnotations_actionPerformed(); + } + }); + showAutoLast.setText(MessageManager.getString("label.show_last")); + showAutoLast.setState(!showAutoFirst.getState()); + showAutoLast.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + boolean sortLast = showAutoLast.getState(); + setShowAutoCalculatedAbove(!sortLast); + showAutoFirst.setState(!sortLast); + sortAnnotations_actionPerformed(); + } }); nucleotideColour.setText(MessageManager.getString("label.nucleotide")); @@ -2101,6 +2195,7 @@ public class GAlignFrame extends JInternalFrame alignFrameMenuBar.add(editMenu); alignFrameMenuBar.add(selectMenu); alignFrameMenuBar.add(viewMenu); + alignFrameMenuBar.add(annotationsMenu); alignFrameMenuBar.add(formatMenu); alignFrameMenuBar.add(colourMenu); alignFrameMenuBar.add(calculateMenu); @@ -2150,9 +2245,19 @@ public class GAlignFrame extends JInternalFrame viewMenu.add(hideMenu); viewMenu.addSeparator(); viewMenu.add(followHighlightMenuItem); - viewMenu.add(annotationPanelMenuItem); - viewMenu.add(showAllAnnotations); - viewMenu.add(hideAllAnnotations); + annotationsMenu.add(annotationPanelMenuItem); + annotationsMenu.addSeparator(); + annotationsMenu.add(showAllAlAnnotations); + annotationsMenu.add(hideAllAlAnnotations); + annotationsMenu.addSeparator(); + annotationsMenu.add(showAllSeqAnnotations); + annotationsMenu.add(hideAllSeqAnnotations); + annotationsMenu.add(sortAnnBySequence); + annotationsMenu.add(sortAnnByLabel); + annotationsMenu.addSeparator(); + autoAnnMenu.add(showAutoFirst); + autoAnnMenu.add(showAutoLast); + autoAnnMenu.addSeparator(); autoAnnMenu.add(applyAutoAnnotationSettings); autoAnnMenu.add(showConsensusHistogram); autoAnnMenu.add(showSequenceLogo); @@ -2160,7 +2265,7 @@ public class GAlignFrame extends JInternalFrame autoAnnMenu.addSeparator(); autoAnnMenu.add(showGroupConservation); autoAnnMenu.add(showGroupConsensus); - viewMenu.add(autoAnnMenu); + annotationsMenu.add(autoAnnMenu); viewMenu.addSeparator(); viewMenu.add(showSeqFeatures); // viewMenu.add(showSeqFeaturesHeight); @@ -2272,27 +2377,54 @@ public class GAlignFrame extends JInternalFrame } /** + * Action on clicking sort annotations by type. + * + * @param sortOrder + */ + protected void sortAnnotations_actionPerformed() + { + } + + /** * Action on clicking Show all annotations. + * + * @param forSequences + * update sequence-related annotations + * @param forAlignment + * update non-sequence-related annotations */ - protected void showAllAnnotations_actionPerformed() + protected void showAllAnnotations_actionPerformed(boolean forSequences, + boolean forAlignment) { - setAllAnnotationsVisibility(true); + setAnnotationsVisibility(true, forSequences, forAlignment); } /** * Action on clicking Hide all annotations. + * + * @param forSequences + * update sequence-related annotations + * @param forAlignment + * update non-sequence-related annotations */ - protected void hideAllAnnotations_actionPerformed() + protected void hideAllAnnotations_actionPerformed(boolean forSequences, + boolean forAlignment) { - setAllAnnotationsVisibility(false); + setAnnotationsVisibility(false, forSequences, forAlignment); } /** - * Set the visibility of all annotations to true or false. + * Set the visibility of annotations to true or false. Can act on + * sequence-related annotations, or alignment-related, or both. * * @param visible + * @param forSequences + * update sequence-related annotations + * @param forAlignment + * update non-sequence-related annotations */ - protected void setAllAnnotationsVisibility(boolean visible) + protected void setAnnotationsVisibility(boolean visible, + boolean forSequences, boolean forAlignment) { } @@ -2941,4 +3073,24 @@ public class GAlignFrame extends JInternalFrame // TODO Auto-generated method stub } + + protected boolean isShowAutoCalculatedAbove() + { + return showAutoCalculatedAbove; + } + + protected void setShowAutoCalculatedAbove(boolean showAutoCalculatedAbove) + { + this.showAutoCalculatedAbove = showAutoCalculatedAbove; + } + + protected SequenceAnnotationOrder getAnnotationSortOrder() + { + return annotationSortOrder; + } + + protected void setAnnotationSortOrder(SequenceAnnotationOrder annotationSortOrder) + { + this.annotationSortOrder = annotationSortOrder; + } } diff --git a/test/jalview/analysis/AlignmentAnnotationUtilsTest.java b/test/jalview/analysis/AlignmentAnnotationUtilsTest.java index 1da1939..19a5163 100644 --- a/test/jalview/analysis/AlignmentAnnotationUtilsTest.java +++ b/test/jalview/analysis/AlignmentAnnotationUtilsTest.java @@ -36,7 +36,7 @@ public class AlignmentAnnotationUtilsTest "TIETHKEEELTA-" + EOL; // @formatter:on - private static final int SEQ_ANN_COUNT = 10; + private static final int SEQ_ANN_COUNT = 12; private AlignmentI alignment; @@ -212,6 +212,7 @@ public class AlignmentAnnotationUtilsTest @Test public void testGetShownHiddenTypes_withGraphGroups() { + final int GROUP_3 = 3; final int GROUP_4 = 4; final int GROUP_5 = 5; final int GROUP_6 = 6; @@ -222,10 +223,10 @@ public class AlignmentAnnotationUtilsTest SequenceI[] seqs = alignment.getSequencesArray(); /* - * Configure annotation properties for test + * Annotations for selection group and graph group + * + * Hidden annotations Label2, Label3, in (hidden) group 5 */ - // annotations for selection group and graph group - // hidden annotations Label2, Label3, in (hidden) group 5 anns[2].sequenceRef = seqs[3]; anns[2].visible = false; anns[2].graph = AlignmentAnnotation.LINE_GRAPH; @@ -236,6 +237,19 @@ public class AlignmentAnnotationUtilsTest anns[3].graphGroup = GROUP_5; // need to ensure annotations have the same calcId as well anns[3].setCalcId("CalcId2"); + // annotations for a different hidden group generating the same group label + anns[10].sequenceRef = seqs[0]; + anns[10].visible = false; + anns[10].graph = AlignmentAnnotation.LINE_GRAPH; + anns[10].graphGroup = GROUP_3; + anns[10].label = "Label3"; + anns[10].setCalcId("CalcId2"); + anns[11].sequenceRef = seqs[3]; + anns[11].visible = false; + anns[11].graph = AlignmentAnnotation.LINE_GRAPH; + anns[11].graphGroup = GROUP_3; + anns[11].label = "Label2"; + anns[11].setCalcId("CalcId2"); // annotations Label1 (hidden), Label5 (visible) in group 6 (visible) anns[1].sequenceRef = seqs[3]; @@ -248,9 +262,29 @@ public class AlignmentAnnotationUtilsTest anns[5].graph = AlignmentAnnotation.LINE_GRAPH; anns[5].graphGroup = GROUP_6; anns[5].setCalcId("CalcId1"); + /* + * Annotations 0 and 4 are visible, for a different CalcId and graph group. + * They produce the same label as annotations 1 and 5, which should not be + * duplicated in the results. This case corresponds to (e.g.) many + * occurrences of an IUPred Short/Long annotation group, one per sequence. + */ + anns[4].sequenceRef = seqs[0]; + anns[4].visible = false; + anns[4].graph = AlignmentAnnotation.LINE_GRAPH; + anns[4].graphGroup = GROUP_4; + anns[4].label = "Label1"; + anns[4].setCalcId("CalcId1"); + anns[0].sequenceRef = seqs[0]; + anns[0].visible = true; + anns[0].graph = AlignmentAnnotation.LINE_GRAPH; + anns[0].graphGroup = GROUP_4; + anns[0].label = "Label5"; + anns[0].setCalcId("CalcId1"); - // annotations outwith selection group - should be ignored - // hidden grouped annotations + /* + * Annotations outwith selection group - should be ignored. + */ + // Hidden grouped annotations anns[6].sequenceRef = seqs[2]; anns[6].visible = false; anns[6].graph = AlignmentAnnotation.LINE_GRAPH; @@ -259,6 +293,7 @@ public class AlignmentAnnotationUtilsTest anns[8].visible = false; anns[8].graph = AlignmentAnnotation.LINE_GRAPH; anns[8].graphGroup = GROUP_4; + // visible grouped annotations Label7, Label9 anns[7].sequenceRef = seqs[2]; anns[7].visible = true; @@ -276,13 +311,16 @@ public class AlignmentAnnotationUtilsTest consoleDebug(shownTypes, hiddenTypes); - // CalcId1 / Label1, Label5 (only) should be 'shown', as a compound type + // CalcId1 / Label1, Label5 (only) should be 'shown', once, as a compound + // type + assertEquals(1, shownTypes.size()); assertEquals(1, shownTypes.get("CalcId1").size()); assertEquals(2, shownTypes.get("CalcId1").get(0).size()); assertEquals("Label1", shownTypes.get("CalcId1").get(0).get(0)); assertEquals("Label5", shownTypes.get("CalcId1").get(0).get(1)); // CalcId2 / Label2, Label3 (only) should be 'hidden' + assertEquals(1, hiddenTypes.size()); assertEquals(1, hiddenTypes.get("CalcId2").size()); assertEquals(2, hiddenTypes.get("CalcId2").get(0).size()); assertEquals("Label2", hiddenTypes.get("CalcId2").get(0).get(0)); diff --git a/test/jalview/analysis/AnnotationSorterTest.java b/test/jalview/analysis/AnnotationSorterTest.java new file mode 100644 index 0000000..ba2162d --- /dev/null +++ b/test/jalview/analysis/AnnotationSorterTest.java @@ -0,0 +1,345 @@ +package jalview.analysis; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder; +import jalview.datamodel.Alignment; +import jalview.datamodel.AlignmentAnnotation; +import jalview.datamodel.Sequence; +import jalview.datamodel.SequenceI; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import org.junit.Before; +import org.junit.Test; + +public class AnnotationSorterTest +{ + private static final int NUM_SEQS = 6; + + private static final int NUM_ANNS = 7; + + private static final String SS = "secondary structure"; + + AlignmentAnnotation[] anns = new AlignmentAnnotation[0]; + + Alignment al = null; + + /* + * Set up 6 sequences and 7 annotations. + */ + @Before + public void setUp() + { + al = buildAlignment(NUM_SEQS); + anns = buildAnnotations(NUM_ANNS); + } + + /** + * Construct an array of numAnns annotations + * + * @param numAnns + * + * @return + */ + protected AlignmentAnnotation[] buildAnnotations(int numAnns) + { + List annlist = new ArrayList(); + for (int i = 0; i < numAnns; i++) + { + AlignmentAnnotation ann = new AlignmentAnnotation(SS + i, "", 0); + annlist.add(ann); + } + return annlist.toArray(anns); + } + + /** + * Make an alignment with numSeqs sequences in it. + * + * @param numSeqs + * + * @return + */ + private Alignment buildAlignment(int numSeqs) + { + SequenceI[] seqs = new Sequence[numSeqs]; + for (int i = 0; i < numSeqs; i++) + { + seqs[i] = new Sequence("Sequence" + i, "axrdkfp"); + } + return new Alignment(seqs); + } + + /** + * Test sorting by annotation type (label) within sequence order, including + *
      + *
    • annotations with no sequence reference - sort to end keeping mutual + * ordering
    • + *
    • annotations with sequence ref = sort in sequence order
    • + *
    • multiple annotations for same sequence ref - sort by label + * non-case-specific
    • + *
    • annotations with reference to sequence not in alignment - treat like no + * sequence ref
    • + *
    + */ + @Test + public void testSortBySequenceAndType_autocalcLast() + { + // @formatter:off + anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0"; + anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure"; + anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron"; + anns[3].sequenceRef = null; anns[3].label = "Quality"; + anns[4].sequenceRef = null; anns[4].label = "Consensus"; + anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "label5"; + anns[6].sequenceRef = al.getSequenceAt(3); anns[6].label = "IRP"; + // @formatter:on + + AnnotationSorter testee = new AnnotationSorter(al, false); + testee.sort(anns, SequenceAnnotationOrder.SEQUENCE_AND_LABEL); + assertEquals("label5", anns[0].label); // for sequence 0 + assertEquals("label0", anns[1].label); // for sequence 1 + assertEquals("iron", anns[2].label); // sequence 3 /iron + assertEquals("IRP", anns[3].label); // sequence 3/IRP + assertEquals("structure", anns[4].label); // sequence 3/structure + assertEquals("Quality", anns[5].label); // non-sequence annotations + assertEquals("Consensus", anns[6].label); // retain ordering + } + + /** + * Variant with autocalculated annotations sorting to front + */ + @Test + public void testSortBySequenceAndType_autocalcFirst() + { + // @formatter:off + anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0"; + anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure"; + anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron"; + anns[3].sequenceRef = null; anns[3].label = "Quality"; + anns[4].sequenceRef = null; anns[4].label = "Consensus"; + anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "label5"; + anns[6].sequenceRef = al.getSequenceAt(3); anns[6].label = "IRP"; + // @formatter:on + + AnnotationSorter testee = new AnnotationSorter(al, true); + testee.sort(anns, SequenceAnnotationOrder.SEQUENCE_AND_LABEL); + assertEquals("Quality", anns[0].label); // non-sequence annotations + assertEquals("Consensus", anns[1].label); // retain ordering + assertEquals("label5", anns[2].label); // for sequence 0 + assertEquals("label0", anns[3].label); // for sequence 1 + assertEquals("iron", anns[4].label); // sequence 3 /iron + assertEquals("IRP", anns[5].label); // sequence 3/IRP + assertEquals("structure", anns[6].label); // sequence 3/structure + } + + /** + * Test sorting by annotation type (label) within sequence order, including + *
      + *
    • annotations with no sequence reference - sort to end keeping mutual + * ordering
    • + *
    • annotations with sequence ref = sort in sequence order
    • + *
    • multiple annotations for same sequence ref - sort by label + * non-case-specific
    • + *
    • annotations with reference to sequence not in alignment - treat like no + * sequence ref
    • + *
    + */ + @Test + public void testSortByTypeAndSequence_autocalcLast() + { + // @formatter:off + anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0"; + anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure"; + anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron"; + anns[3].sequenceRef = null; anns[3].label = "Quality"; + anns[4].sequenceRef = null; anns[4].label = "Consensus"; + anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "IRON"; + anns[6].sequenceRef = al.getSequenceAt(2); anns[6].label = "Structure"; + // @formatter:on + + AnnotationSorter testee = new AnnotationSorter(al, false); + testee.sort(anns, SequenceAnnotationOrder.LABEL_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 + assertEquals("Structure", anns[3].label); // Structure / sequence 2 + assertEquals("structure", anns[4].label); // structure / sequence 3 + assertEquals("Quality", anns[5].label); // non-sequence annotations + assertEquals("Consensus", anns[6].label); // retain ordering + } + + /** + * Variant of test with autocalculated annotations sorted to front + */ + @Test + public void testSortByTypeAndSequence_autocalcFirst() + { + // @formatter:off + anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0"; + anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure"; + anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron"; + anns[3].sequenceRef = null; anns[3].label = "Quality"; + anns[4].sequenceRef = null; anns[4].label = "Consensus"; + anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "IRON"; + anns[6].sequenceRef = al.getSequenceAt(2); anns[6].label = "Structure"; + // @formatter:on + + AnnotationSorter testee = new AnnotationSorter(al, true); + testee.sort(anns, SequenceAnnotationOrder.LABEL_AND_SEQUENCE); + assertEquals("Quality", anns[0].label); // non-sequence annotations + assertEquals("Consensus", anns[1].label); // retain ordering + assertEquals("IRON", anns[2].label); // IRON / sequence 0 + assertEquals("iron", anns[3].label); // iron / sequence 3 + assertEquals("label0", anns[4].label); // label0 / sequence 1 + assertEquals("Structure", anns[5].label); // Structure / sequence 2 + assertEquals("structure", anns[6].label); // structure / sequence 3 + } + + /** + * Variant of test with autocalculated annotations sorted to front but + * otherwise no change. + */ + @Test + public void testNoSort_autocalcFirst() + { + // @formatter:off + anns[0].sequenceRef = al.getSequenceAt(1); anns[0].label = "label0"; + anns[1].sequenceRef = al.getSequenceAt(3); anns[1].label = "structure"; + anns[2].sequenceRef = al.getSequenceAt(3); anns[2].label = "iron"; + anns[3].sequenceRef = null; anns[3].label = "Quality"; + anns[4].sequenceRef = null; anns[4].label = "Consensus"; + anns[5].sequenceRef = al.getSequenceAt(0); anns[5].label = "IRON"; + anns[6].sequenceRef = al.getSequenceAt(2); anns[6].label = "Structure"; + // @formatter:on + + AnnotationSorter testee = new AnnotationSorter(al, true); + testee.sort(anns, SequenceAnnotationOrder.NONE); + assertEquals("Quality", anns[0].label); // non-sequence annotations + assertEquals("Consensus", anns[1].label); // retain ordering + assertEquals("label0", anns[2].label); + assertEquals("structure", anns[3].label); + assertEquals("iron", anns[4].label); + assertEquals("IRON", anns[5].label); + assertEquals("Structure", anns[6].label); + } + + @Test + public void testSort_timingPresorted() + { + final long targetTime = 100; // ms + final int numSeqs = 10000; + final int numAnns = 20000; + al = buildAlignment(numSeqs); + anns = buildAnnotations(numAnns); + + /* + * Set the annotations presorted by label + */ + 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, false); + testee.sort(anns, SequenceAnnotationOrder.LABEL_AND_SEQUENCE); + long endTime = System.currentTimeMillis(); + final long elapsed = endTime - startTime; + 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, false); + testee.sort(anns, SequenceAnnotationOrder.SEQUENCE_AND_LABEL); + 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, false); + testee.sort(anns, SequenceAnnotationOrder.LABEL_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, SequenceAnnotationOrder.SEQUENCE_AND_LABEL); + 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, SequenceAnnotationOrder.LABEL_AND_SEQUENCE); + endTime = System.currentTimeMillis(); + elapsed = endTime - startTime; + System.out.println("Resort by type for semisorted " + numSeqs + + " sequences and " + numAnns + " annotations took " + elapsed + + "ms"); + } +}