From 9962480f980106936de9350235509116746a79a2 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Sun, 17 Nov 2019 13:38:27 +0000 Subject: [PATCH] JAL-3081 sort annotations by order read from project after reloading --- src/jalview/analysis/AnnotationSorter.java | 88 ++++++++++++++ src/jalview/project/Jalview2XML.java | 143 +++++------------------ test/jalview/analysis/AnnotationSorterTest.java | 47 ++++++++ 3 files changed, 161 insertions(+), 117 deletions(-) diff --git a/src/jalview/analysis/AnnotationSorter.java b/src/jalview/analysis/AnnotationSorter.java index 83f3adf..2e04cb1 100644 --- a/src/jalview/analysis/AnnotationSorter.java +++ b/src/jalview/analysis/AnnotationSorter.java @@ -28,6 +28,7 @@ import jalview.datamodel.SequenceI; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -40,6 +41,70 @@ import java.util.Map; public class AnnotationSorter { /** + * A special comparator for use when reloading from project, that supports + * reordering annotations to match their order in the project. It incorporates + * special handling of autocalculated annotations, that are recreated on load: + * + */ + private final class ReloadComparator + implements Comparator + { + private List order; + + ReloadComparator(List theOrder) + { + order = theOrder; + } + + @Override + public int compare(AlignmentAnnotation o1, + AlignmentAnnotation o2) + { + int i1 = findPosition(o1); + int i2 = findPosition(o2); + return Integer.compare(i1, i2); + } + + /** + * A helper method that returns the position of the annotation in the + * {@code order} list, by object identity, or failing that, by matched + * autocalc label ("Consensus" etc). Returns -1 if not found. This can + * happen if, for example, Consensus was saved but since turned off in + * Preferences. + * + * @param aa + * @return + */ + private int findPosition(AlignmentAnnotation aa) + { + int i = order.indexOf(aa); + if (i == -1 && aa.autoCalculated && aa.label != null) + { + for (int j = 0; j < order.size(); j++) + { + AlignmentAnnotation ann = order.get(j); + if (aa.label.equals(ann.label)) + { + i = j; + aa.visible = ann.visible; + if (ann.graphHeight >= 0) + { + aa.graphHeight = ann.graphHeight; + } + break; + } + } + } + return i; + } + } + + /** * enum for annotation sort options. The text description is used in the * Preferences drop-down options. The enum name is saved in the preferences * file. @@ -452,4 +517,27 @@ public class AnnotationSorter } return Integer.compare(index1.intValue(), index2.intValue()); } + + /** + * Sort annotations to match the order of the provided list. This is intended + * only for use when reloading a project, in order to set the saved order + * after constructing a viewport (which might sort differently based on user + * preferences for sort order). + *

+ * There is some special handling specific to auto-calculated annotations. + * These are saved in the project, to preserve their visibility and height + * properties, but are recalculated when the viewport is reconstructed. + *

    + *
  • Autocalculated annotations are matched to the list by label + * "Consensus/Quality/Conservation/Occupancy"
  • + *
  • those that are matched have their visibility and height set
  • + *
+ * + * @param addedAnnotation + */ + public void sort(List annotations) + { + Arrays.sort(alignment.getAlignmentAnnotation(), + new ReloadComparator(annotations)); + } } diff --git a/src/jalview/project/Jalview2XML.java b/src/jalview/project/Jalview2XML.java index c017d11..eaabd47 100644 --- a/src/jalview/project/Jalview2XML.java +++ b/src/jalview/project/Jalview2XML.java @@ -24,6 +24,7 @@ import static jalview.math.RotatableMatrix.Axis.X; import static jalview.math.RotatableMatrix.Axis.Y; import static jalview.math.RotatableMatrix.Axis.Z; +import jalview.analysis.AnnotationSorter; import jalview.analysis.AnnotationSorter.SequenceAnnotationOrder; import jalview.analysis.Conservation; import jalview.analysis.PCA; @@ -3622,7 +3623,7 @@ public class Jalview2XML // //////////////////////////////// // LOAD ANNOTATIONS - List autoAlan = new ArrayList<>(); + List addedAnnotation = new ArrayList<>(); /* * store any annotations which forward reference a group's ID @@ -3713,7 +3714,11 @@ public class Jalview2XML } } } - jalview.datamodel.AlignmentAnnotation jaa = null; + + /* + * construct the Jalview AlignmentAnnotation, add to alignment + */ + AlignmentAnnotation jaa = null; if (annotation.isGraph()) { @@ -3811,9 +3816,10 @@ public class Jalview2XML jaa.autoCalculated = true; // means annotation will be marked for // update at end of load. } - if (annotation.getGraphHeight() != null) + Integer graphHeight = annotation.getGraphHeight(); + if (graphHeight != null) { - jaa.graphHeight = annotation.getGraphHeight().intValue(); + jaa.graphHeight = graphHeight.intValue(); } jaa.belowAlignment = annotation.isBelowAlignment(); jaa.setCalcId(annotation.getCalcId()); @@ -3825,17 +3831,16 @@ public class Jalview2XML jaa.setProperty(prop.getName(), prop.getValue()); } } - if (jaa.autoCalculated) - { - autoAlan.add(new JvAnnotRow(i, jaa)); - } - else - // if (!autoForView) + if (!jaa.autoCalculated) { - // add autocalculated group annotation and any user created annotation - // for the view + // TODO ensure Consensus etc is enabled if found in project? + /* + * add autocalculated group annotation and any user created annotation + * for the view + */ al.addAnnotation(jaa); } + addedAnnotation.add(jaa); } } // /////////////////////// @@ -4028,10 +4033,16 @@ public class Jalview2XML if (isnewview) { af = loadViewport(file, jseqs, hiddenSeqs, al, jalviewModel, view, - uniqueSeqSetId, viewId, autoAlan); - // TODO resort annotations here to their order in the project? + uniqueSeqSetId, viewId); + // TODO restore autocalc preferences if overridden earlier? + /* + * resort annotations to their order in the project + * (also sets height and visibility for autocalc'd annotation) + */ av = af.getViewport(); + new AnnotationSorter(av).sort(addedAnnotation); ap = af.alignPanel; + ap.adjustAnnotationHeight(); } /* @@ -4839,7 +4850,7 @@ public class Jalview2XML AlignFrame loadViewport(String file, List JSEQ, List hiddenSeqs, AlignmentI al, JalviewModel jm, Viewport view, String uniqueSeqSetId, - String viewId, List autoAlan) + String viewId) { AlignFrame af = null; af = new AlignFrame(al, safeInt(view.getWidth()), @@ -5196,8 +5207,6 @@ public class Jalview2XML safeInt(view.getWidth()), safeInt(view.getHeight())); // recompute any autoannotation af.alignPanel.updateAnnotation(false, true); - reorderAutoannotation(af, al, autoAlan); - af.sortAnnotations(false); af.alignPanel.alignmentChanged(); } else @@ -5324,106 +5333,6 @@ public class Jalview2XML return cs; } - private void reorderAutoannotation(AlignFrame af, AlignmentI al, - List autoAlan) - { - // copy over visualization settings for autocalculated annotation in the - // view - if (al.getAlignmentAnnotation() != null) - { - /** - * Kludge for magic autoannotation names (see JAL-811) - */ - String[] magicNames = new String[] { "Consensus", "Quality", - "Conservation" }; - JvAnnotRow nullAnnot = new JvAnnotRow(-1, null); - Hashtable visan = new Hashtable<>(); - for (String nm : magicNames) - { - visan.put(nm, nullAnnot); - } - for (JvAnnotRow auan : autoAlan) - { - visan.put(auan.template.label - + (auan.template.getCalcId() == null ? "" - : "\t" + auan.template.getCalcId()), - auan); - } - int hSize = al.getAlignmentAnnotation().length; - List reorder = new ArrayList<>(); - // work through any autoCalculated annotation already on the view - // removing it if it should be placed in a different location on the - // annotation panel. - List remains = new ArrayList<>(visan.keySet()); - for (int h = 0; h < hSize; h++) - { - jalview.datamodel.AlignmentAnnotation jalan = al - .getAlignmentAnnotation()[h]; - if (jalan.autoCalculated) - { - String k; - JvAnnotRow valan = visan.get(k = jalan.label); - if (jalan.getCalcId() != null) - { - valan = visan.get(k = jalan.label + "\t" + jalan.getCalcId()); - } - - if (valan != null) - { - // delete the auto calculated row from the alignment - al.deleteAnnotation(jalan, false); - remains.remove(k); - hSize--; - h--; - if (valan != nullAnnot) - { - if (jalan != valan.template) - { - // newly created autoannotation row instance - // so keep a reference to the visible annotation row - // and copy over all relevant attributes - if (valan.template.graphHeight >= 0) - - { - jalan.graphHeight = valan.template.graphHeight; - } - jalan.visible = valan.template.visible; - } - reorder.add(new JvAnnotRow(valan.order, jalan)); - } - } - } - } - // Add any (possibly stale) autocalculated rows that were not appended to - // the view during construction - for (String other : remains) - { - JvAnnotRow othera = visan.get(other); - if (othera != nullAnnot && othera.template.getCalcId() != null - && othera.template.getCalcId().length() > 0) - { - reorder.add(othera); - } - } - // now put the automatic annotation in its correct place - int s = 0, srt[] = new int[reorder.size()]; - JvAnnotRow[] rws = new JvAnnotRow[reorder.size()]; - for (JvAnnotRow jvar : reorder) - { - rws[s] = jvar; - srt[s++] = jvar.order; - } - reorder.clear(); - jalview.util.QuickSort.sort(srt, rws); - // and re-insert the annotation at its correct position - for (JvAnnotRow jvar : rws) - { - al.addAnnotation(jvar.template, jvar.order); - } - af.alignPanel.adjustAnnotationHeight(); - } - } - Hashtable skipList = null; /** diff --git a/test/jalview/analysis/AnnotationSorterTest.java b/test/jalview/analysis/AnnotationSorterTest.java index de57b1b..fa8af3d 100644 --- a/test/jalview/analysis/AnnotationSorterTest.java +++ b/test/jalview/analysis/AnnotationSorterTest.java @@ -33,6 +33,8 @@ import jalview.datamodel.SequenceI; import jalview.gui.AlignViewport; import jalview.gui.JvOptionPane; +import java.util.Arrays; +import java.util.List; import java.util.Random; import org.testng.annotations.BeforeClass; @@ -138,6 +140,7 @@ public class AnnotationSorterTest av.setShowAutocalculatedAbove(false); AnnotationSorter testee = new AnnotationSorter(av); testee.sort(SequenceAnnotationOrder.SEQUENCE_AND_LABEL, false); + anns = al.getAlignmentAnnotation(); assertEquals("label5", anns[0].label); // for sequence 0 assertEquals("label0", anns[1].label); // for sequence 1 assertEquals("iron", anns[2].label); // sequence 3 /iron @@ -169,6 +172,7 @@ public class AnnotationSorterTest av.setShowAutocalculatedAbove(true); AnnotationSorter testee = new AnnotationSorter(av); testee.sort(SequenceAnnotationOrder.SEQUENCE_AND_LABEL, false); + anns = al.getAlignmentAnnotation(); assertEquals("Quality", anns[0].label); // autocalc annotations assertEquals("Consensus", anns[1].label); // retain ordering assertEquals("label5", anns[2].label); // for sequence 0 @@ -209,6 +213,7 @@ public class AnnotationSorterTest av.setShowAutocalculatedAbove(false); AnnotationSorter testee = new AnnotationSorter(av); testee.sort(SequenceAnnotationOrder.LABEL_AND_SEQUENCE, false); + anns = al.getAlignmentAnnotation(); assertEquals("IRON", anns[0].label); // IRON / sequence 0 assertEquals("iron", anns[1].label); // iron / sequence 3 assertEquals("label0", anns[2].label); // label0 / sequence 1 @@ -240,6 +245,7 @@ public class AnnotationSorterTest av.setShowAutocalculatedAbove(true); AnnotationSorter testee = new AnnotationSorter(av); testee.sort(SequenceAnnotationOrder.LABEL_AND_SEQUENCE, false); + anns = al.getAlignmentAnnotation(); assertEquals("Quality", anns[0].label); // autocalc annotations assertEquals("Consensus", anns[1].label); // retain ordering assertEquals("IRON", anns[2].label); // IRON / sequence 0 @@ -272,6 +278,7 @@ public class AnnotationSorterTest av.setShowAutocalculatedAbove(true); AnnotationSorter testee = new AnnotationSorter(av); testee.sort(SequenceAnnotationOrder.NONE, false); + anns = al.getAlignmentAnnotation(); assertEquals("Quality", anns[0].label); // autocalc annotations assertEquals("Consensus", anns[1].label); // retain ordering assertEquals("label0", anns[2].label); @@ -468,6 +475,7 @@ public class AnnotationSorterTest av.setShowAutocalculatedAbove(true); AnnotationSorter testee = new AnnotationSorter(av); testee.sort(SequenceAnnotationOrder.CUSTOM, false); + anns = al.getAlignmentAnnotation(); assertEquals("label0", anns[0].label); // all unchanged assertEquals("structure", anns[1].label); assertEquals("iron", anns[2].label); @@ -502,6 +510,7 @@ public class AnnotationSorterTest av.setShowAutocalculatedAbove(true); AnnotationSorter testee = new AnnotationSorter(av); testee.sort(SequenceAnnotationOrder.LABEL_AND_SEQUENCE, true); + anns = al.getAlignmentAnnotation(); assertEquals("Quality", anns[0].label); // moved to top assertEquals("Consensus", anns[1].label); // moved to top assertEquals("label0", anns[2].label); // the rest unchanged @@ -524,4 +533,42 @@ public class AnnotationSorterTest assertEquals("Quality", anns[5].label); // moved to bottom assertEquals("Consensus", anns[6].label); // moved to bottom } + + /** + * Test sorting by annotation order + */ + @Test(groups = { "Functional" }) + public void testSortByAnnotation() + { + AlignmentI al = av.getAlignment(); + AlignmentAnnotation[] anns = al.getAlignmentAnnotation(); + + // @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].autoCalculated = true; anns[3].label = "Quality"; + anns[4].autoCalculated = true; 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 + + av.setShowAutocalculatedAbove(false); + AnnotationSorter testee = new AnnotationSorter(av); + List reorder = Arrays.asList(anns[2], anns[6], + anns[3], anns[0], anns[5], anns[1], anns[4]); + testee.sort(reorder); + + /* + * should now be ordered as specified by the list + */ + anns = al.getAlignmentAnnotation(); + assertEquals("iron", anns[0].label); + assertEquals("IRP", anns[1].label); + assertEquals("Quality", anns[2].label); + assertEquals("label0", anns[3].label); + assertEquals("label5", anns[4].label); + assertEquals("structure", anns[5].label); + assertEquals("Consensus", anns[6].label); + } } -- 1.7.10.2