From: hansonr Date: Sat, 3 Aug 2019 04:26:18 +0000 (-0500) Subject: JAL-3383 JAL-3253-applet additional efficiencies; FeatureStore X-Git-Url: http://source.jalview.org/gitweb/?p=jalview.git;a=commitdiff_plain;h=3d4ead4880755ec949de0300c232544ba965e60d JAL-3383 JAL-3253-applet additional efficiencies; FeatureStore alternative to IntervalStore - implemented for JavaScript only (see SequenceFeatures): boolean useIntervalStore = /** * @j2sNative false && */ true; but can be tested in Java and JavaScript by changing either of those to true/false. - only requires storage for one sorted array in FeatureSorter: private SequenceFeature[] orderedFeatureStarts; and one additional linked-list field pointer in SequenceFeature: SequenceFeature containedBy - when running, the position is looked up in the begin-sorted feature array, and then the containedBy links are simply traversed using: SequenceFeature sf = findClosestFeature(orderedFeatureStarts, pos); while (sf != null) { if (sf.end >= pos) { result.add(sf); } sf = sf.containedBy; } - my preliminary timing tests suggest this is 2x faster than IntervalStore in JavaScript. --- diff --git a/src/jalview/api/FeatureColourI.java b/src/jalview/api/FeatureColourI.java index 7bfd8a8..c5f6727 100644 --- a/src/jalview/api/FeatureColourI.java +++ b/src/jalview/api/FeatureColourI.java @@ -64,10 +64,9 @@ public interface FeatureColourI Color getNoColour(); /** - * Answers true if the feature has a single colour, i.e. if isColourByLabel() - * and isGraduatedColour() both answer false + * Answers true if the feature has a single colour * - * @return + * @return true iff not (isColourByLabel() || isGraduatedColour()) */ boolean isSimpleColour(); diff --git a/src/jalview/datamodel/Sequence.java b/src/jalview/datamodel/Sequence.java index 7624d2c..4219c8e 100755 --- a/src/jalview/datamodel/Sequence.java +++ b/src/jalview/datamodel/Sequence.java @@ -2156,15 +2156,26 @@ public class Sequence extends ASequence implements SequenceI /** * @author Bob Hanson 2019.07.30 * - * allows passing the result ArrayList as a parameter to avoid unnecessary construction + * allows passing the result ArrayList as a parameter to avoid + * unnecessary construction + * @return result (JavaScript) or new ArrayList (Java -- see FeatureRender) * */ @Override - public void findFeatures(int column, String type, + public List findFeatures(int column, String type, List result) { - getFeatures().findFeatures(findPosition(column - 1), type, result); + return getFeatures().findFeatures(findPosition(column - 1), type, + result); } + /** + * allows early intervention for renderer if this returns false + */ + @Override + public boolean hasFeatures(String type) + { + return getFeatures().hasFeatures(type); + } } diff --git a/src/jalview/datamodel/SequenceFeature.java b/src/jalview/datamodel/SequenceFeature.java index 7052f34..bf2408c 100755 --- a/src/jalview/datamodel/SequenceFeature.java +++ b/src/jalview/datamodel/SequenceFeature.java @@ -99,6 +99,11 @@ public class SequenceFeature implements FeatureLocationI */ private String source; + // for Overview sort: + public int index; + + public SequenceFeature containedBy; + /** * Constructs a duplicate feature. Note: Uses makes a shallow copy of the * otherDetails map, so the new and original SequenceFeature may reference the diff --git a/src/jalview/datamodel/SequenceI.java b/src/jalview/datamodel/SequenceI.java index 013897d..0b26564 100755 --- a/src/jalview/datamodel/SequenceI.java +++ b/src/jalview/datamodel/SequenceI.java @@ -609,12 +609,23 @@ public interface SequenceI extends ASequenceI public void resetColors(); /** + * allows passing the result ArrayList as a parameter to avoid unnecessary + * construction + * * @author Bob Hanson 2019.07.30 * - * allows passing the result ArrayList as a parameter to avoid unnecessary construction * */ - void findFeatures(int column, String type, List result); + List findFeatures(int column, String type, + List result); + + /** + * allows early intervention for renderer if false + * + * @author Bob Hanson 2019.07.30 + * + */ + public boolean hasFeatures(String type); } diff --git a/src/jalview/datamodel/features/FeatureStore.java b/src/jalview/datamodel/features/FeatureStore.java index 686ac26..bd6bd1e 100644 --- a/src/jalview/datamodel/features/FeatureStore.java +++ b/src/jalview/datamodel/features/FeatureStore.java @@ -23,7 +23,10 @@ package jalview.datamodel.features; import jalview.datamodel.SequenceFeature; import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -32,6 +35,7 @@ import intervalstore.api.IntervalStoreI; import intervalstore.impl.BinarySearcher; import intervalstore.impl.IntervalStore; + /** * A data store for a set of sequence features that supports efficient lookup of * features overlapping a given range. Intended for (but not limited to) storage @@ -90,9 +94,7 @@ public class FeatureStore float nonPositionalMaxScore; - private SequenceFeature[] temp = new SequenceFeature[3]; - - private boolean isTainted; + private ArrayList featuresList; /** * Constructor @@ -100,6 +102,7 @@ public class FeatureStore public FeatureStore() { features = new IntervalStore<>(); + featuresList = new ArrayList<>(); positionalFeatureGroups = new HashSet<>(); nonPositionalFeatureGroups = new HashSet<>(); positionalMinScore = Float.NaN; @@ -118,6 +121,7 @@ public class FeatureStore * * @param feature */ + public boolean addFeature(SequenceFeature feature) { if (contains(feature)) @@ -186,18 +190,17 @@ public class FeatureStore { if (feature.isNonPositional()) { - return nonPositionalFeatures == null ? false : nonPositionalFeatures - .contains(feature); + return nonPositionalFeatures == null ? false + : nonPositionalFeatures.contains(feature); } if (feature.isContactFeature()) { - return contactFeatureStarts == null ? false : listContains( - contactFeatureStarts, feature); + return contactFeatureStarts == null ? false + : listContains(contactFeatureStarts, feature); } - return features == null ? false : features - .contains(feature); + return features == null ? false : features.contains(feature); } /** @@ -254,7 +257,7 @@ public class FeatureStore features = new IntervalStore<>(); } features.add(feature); - isTainted = true; + featuresList.add(feature); } /** @@ -285,7 +288,6 @@ public class FeatureStore f -> f.getBegin() >= feature.getBegin()); contactFeatureStarts.add(insertPosition, feature); - /* * insert into list sorted by end (second contact position): * binary search the sorted list to find the insertion point @@ -349,6 +351,7 @@ public class FeatureStore * end position of overlap range (inclusive) * @return */ + public List findOverlappingFeatures(long start, long end) { List result = new ArrayList<>(); @@ -408,8 +411,8 @@ public class FeatureStore SequenceFeature sf = contactFeatureEnds.get(index); if (!sf.isContactFeature()) { - System.err.println("Error! non-contact feature type " - + sf.getType() + " in contact features list"); + System.err.println("Error! non-contact feature type " + sf.getType() + + " in contact features list"); index++; continue; } @@ -488,6 +491,7 @@ public class FeatureStore * * @return */ + public List getPositionalFeatures() { List result = new ArrayList<>(); @@ -517,6 +521,7 @@ public class FeatureStore * * @return */ + public List getContactFeatures() { if (contactFeatureStarts == null) @@ -532,6 +537,7 @@ public class FeatureStore * * @return */ + public List getNonPositionalFeatures() { if (nonPositionalFeatures == null) @@ -549,6 +555,7 @@ public class FeatureStore * * @param sf */ + public synchronized boolean delete(SequenceFeature sf) { boolean removed = false; @@ -583,6 +590,7 @@ public class FeatureStore if (!removed && features != null) { removed = features.remove(sf); + featuresList.remove(sf); } if (removed) @@ -607,7 +615,6 @@ public class FeatureStore positionalMaxScore = Float.NaN; nonPositionalMinScore = Float.NaN; nonPositionalMaxScore = Float.NaN; - isTainted = true; /* * scan non-positional features for groups and scores */ @@ -677,13 +684,13 @@ public class FeatureStore * * @return */ + public boolean isEmpty() { boolean hasFeatures = (contactFeatureStarts != null - && !contactFeatureStarts - .isEmpty()) - || (nonPositionalFeatures != null && !nonPositionalFeatures - .isEmpty()) + && !contactFeatureStarts.isEmpty()) + || (nonPositionalFeatures != null + && !nonPositionalFeatures.isEmpty()) || (features != null && features.size() > 0); return !hasFeatures; @@ -697,6 +704,7 @@ public class FeatureStore * @param positionalFeatures * @return */ + public Set getFeatureGroups(boolean positionalFeatures) { if (positionalFeatures) @@ -705,9 +713,9 @@ public class FeatureStore } else { - return nonPositionalFeatureGroups == null ? Collections - . emptySet() : Collections - .unmodifiableSet(nonPositionalFeatureGroups); + return nonPositionalFeatureGroups == null + ? Collections. emptySet() + : Collections.unmodifiableSet(nonPositionalFeatureGroups); } } @@ -718,12 +726,13 @@ public class FeatureStore * @param positional * @return */ + public int getFeatureCount(boolean positional) { if (!positional) { - return nonPositionalFeatures == null ? 0 : nonPositionalFeatures - .size(); + return nonPositionalFeatures == null ? 0 + : nonPositionalFeatures.size(); } int size = 0; @@ -748,6 +757,7 @@ public class FeatureStore * * @return */ + public int getTotalFeatureLength() { return totalExtent; @@ -761,6 +771,7 @@ public class FeatureStore * @param positional * @return */ + public float getMinimumScore(boolean positional) { return positional ? positionalMinScore : nonPositionalMinScore; @@ -774,6 +785,7 @@ public class FeatureStore * @param positional * @return */ + public float getMaximumScore(boolean positional) { return positional ? positionalMaxScore : nonPositionalMaxScore; @@ -787,6 +799,7 @@ public class FeatureStore * @param group * @return */ + public List getFeaturesForGroup(boolean positional, String group) { @@ -807,8 +820,8 @@ public class FeatureStore for (SequenceFeature sf : sfs) { String featureGroup = sf.getFeatureGroup(); - if (group == null && featureGroup == null || group != null - && group.equals(featureGroup)) + if (group == null && featureGroup == null + || group != null && group.equals(featureGroup)) { result.add(sf); } @@ -825,6 +838,7 @@ public class FeatureStore * @param shiftBy * @return */ + public synchronized boolean shiftFeatures(int fromPosition, int shiftBy) { /* @@ -859,34 +873,30 @@ public class FeatureStore /** * Find all features containing this position. - * Uses isTainted field to know when to reconstruct its temporary array. * * @param pos * @return list of SequenceFeatures * @author Bob Hanson 2019.07.30 */ - public void findOverlappingFeatures(int pos, List result) + + public List findOverlappingFeatures(int pos, + List result) { + if (result == null) + { + result = new ArrayList<>(); + } if (contactFeatureStarts != null) { findContacts(contactFeatureStarts, pos, result, true); findContacts(contactFeatureEnds, pos, result, false); } - if (features != null) + if (featuresList != null) { - int n = features.size(); - if (isTainted) - { - isTainted = false; - if (temp.length < n) - { - temp = new SequenceFeature[n << 1]; - } - features.toArray(temp); - } - findOverlaps(temp, n, pos, result); + findOverlaps(featuresList, pos, result); } + return result; } /** @@ -935,28 +945,173 @@ public class FeatureStore } } + BitSet bs = new BitSet(); + /** - * Brute force point-interval overlap test + * Double binary sort with bitset correlation + * * * @param features - * @param n * @param pos * @param result */ - private static void findOverlaps(SequenceFeature[] features, int n, - int pos, + private void findOverlaps(List features, int pos, List result) { - // BH I know, brute force. We need a single-position overlap - // method for IntervalStore, I think. - for (int i = n; --i >= 0;) + int n = featuresList.size(); + if (n == 1) + { + checkOne(featuresList.get(0), pos, result); + return; + } + if (orderedFeatureStarts == null) { - SequenceFeature f = features[i]; - if (f.begin <= pos && f.end >= pos) + rebuildArrays(n); + } + SequenceFeature sf = findClosestFeature(orderedFeatureStarts, pos); + while (sf != null) { + if (sf.end >= pos) { - result.add(f); + result.add(sf); } + sf = sf.containedBy; } } + private void linkFeatures(SequenceFeature[] intervals) + { + if (intervals.length < 2) + { + return; + } + int maxEnd = intervals[0].end; + for (int i = 1, n = intervals.length; i < n; i++) + { + SequenceFeature ithis = intervals[i]; + if (ithis.begin <= maxEnd) + { + ithis.containedBy = getContainedBy(intervals[i - 1], ithis); + } + if (ithis.end > maxEnd) + { + maxEnd = ithis.end; + } + } + } + + private SequenceFeature getContainedBy(SequenceFeature sf, + SequenceFeature sf0) + { + int begin = sf0.begin; + while (sf != null) + { + if (begin <= sf.end) + { + System.out.println("\nFS found " + sf0.index + ":" + sf0 + + "\nFS in " + sf.index + ":" + sf); + return sf; + } + sf = sf.containedBy; + } + return null; + } + + private SequenceFeature findClosestFeature(SequenceFeature[] l, int pos) + { + int low = 0; + int high = l.length - 1; + while (low <= high) + { + int mid = (low + high) >>> 1; + SequenceFeature f = l[mid]; + switch (Long.signum(f.begin - pos)) + { + case -1: + low = mid + 1; + continue; + case 1: + high = mid - 1; + continue; + case 0: + + while (++mid <= high && l[mid].begin == pos) + { + ; + } + mid--; + return l[mid]; + } + } + // -1 here? + return (high < 0 || low >= l.length ? null : l[high]); + } + + private void checkOne(SequenceFeature sf, int pos, + List result) + { + if (sf.begin <= pos && sf.end >= pos) + { + result.add(sf); + } + return; + } + + /* + * contact features ordered by first contact position + */ + private SequenceFeature[] orderedFeatureStarts; + + private void rebuildArrays(int n) + { + if (startComp == null) + { + startComp = new StartComparator(); + } + orderedFeatureStarts = new SequenceFeature[n]; + + for (int i = n; --i >= 0;) + { + SequenceFeature sf = featuresList.get(i); + sf.index = i; + orderedFeatureStarts[i] = sf; + } + Arrays.sort(orderedFeatureStarts, startComp); + linkFeatures(orderedFeatureStarts); + } + + class StartComparator implements Comparator + { + + int pos; + + @Override + public int compare(SequenceFeature o1, SequenceFeature o2) + { + int p1 = o1.begin; + int p2 = o2.begin; + return (p1 < p2 ? -1 : p1 > p2 ? 1 : 0); + } + + } + + static StartComparator startComp; + + // class EndComparator implements Comparator + // { + // + // int pos; + // + // @Override + // public int compare(SequenceFeature o1, SequenceFeature o2) + // { + // int p1 = o1.end; + // int p2 = o2.end; + // int val = (p1 < p2 ? 1 : p1 > p2 ? -1 : 0); + // return val; + // } + // + // } + // + // static EndComparator endComp; + } diff --git a/src/jalview/datamodel/features/SequenceFeatures.java b/src/jalview/datamodel/features/SequenceFeatures.java index 93ee71b..0d29184 100644 --- a/src/jalview/datamodel/features/SequenceFeatures.java +++ b/src/jalview/datamodel/features/SequenceFeatures.java @@ -468,17 +468,66 @@ public class SequenceFeatures implements SequenceFeaturesI /** * Simplified find for features associated with a given position. * + * JavaScript set to not use IntervalI, but easily testable by setting false + * to true in javadoc + * + * FeatureRenderer has checked already that featureStore does contain type. + * * @author Bob Hanson 2019.07.30 */ @Override - public void findFeatures(int pos, String type, List list) + public List findFeatures(int pos, String type, + List list) { FeatureStore fs = featureStore.get(type); - if (fs != null) - { - fs.findOverlappingFeatures(pos, list); - } + boolean useIntervalStore = /** + * @j2sNative false && + */ + true; + return (useIntervalStore ? fs.findOverlappingFeatures(pos, pos) + : fs.findOverlappingFeatures(pos, list)); } + // Chrome; developer console closed + + // BH 2019.08.01 useIntervalStore true, redraw false: + // Platform: timer mark 13.848 0.367 overviewrender 16000 pixels row:14 + // Platform: timer mark 15.391 0.39 overviewrender 16000 pixels row:14 + // Platform: timer mark 16.498 0.39 overviewrender 16000 pixels row:14 + // Platform: timer mark 17.596 0.401 overviewrender 16000 pixels row:14 + // Platform: timer mark 18.738 0.363 overviewrender 16000 pixels row:14 + // Platform: timer mark 19.659 0.358 overviewrender 16000 pixels row:14 + // Platform: timer mark 20.737 0.359 overviewrender 16000 pixels row:14 + // Platform: timer mark 21.797 0.391 overviewrender 16000 pixels row:14 + // Platform: timer mark 22.851 0.361 overviewrender 16000 pixels row:14 + // Platform: timer mark 24.019 0.395 overviewrender 16000 pixels row:14 + + // BH 2019.08.01 useIntervalStore false, redraw false: + // Platform: timer mark 19.011 0.181 overviewrender 16000 pixels row:14 + // Platform: timer mark 20.311 0.183 overviewrender 16000 pixels row:14 + // Platform: timer mark 21.368 0.175 overviewrender 16000 pixels row:14 + // Platform: timer mark 22.347 0.178 overviewrender 16000 pixels row:14 + // Platform: timer mark 23.605 0.216 overviewrender 16000 pixels row:14 + // Platform: timer mark 24.836 0.191 overviewrender 16000 pixels row:14 + // Platform: timer mark 26.016 0.181 overviewrender 16000 pixels row:14 + // Platform: timer mark 27.278 0.178 overviewrender 16000 pixels row:14 + // Platform: timer mark 28.158 0.181 overviewrender 16000 pixels row:14 + // Platform: timer mark 29.227 0.196 overviewrender 16000 pixels row:14 + // Platform: timer mark 30.1 0.171 overviewrender 16000 pixels row:14 + // Platform: timer mark 31.684 0.196 overviewrender 16000 pixels row:14 + // Platform: timer mark 32.779 0.18 overviewrender 16000 pixels row:14 + // Platform: timer mark 52.355 0.185 overviewrender 16000 pixels row:14 + // Platform: timer mark 53.829 0.186 overviewrender 16000 pixels row:14 + + + + /** + * @author Bob Hanson 2019.08.01 + */ + @Override + public boolean hasFeatures(String type) + { + return featureStore.containsKey(type); + } } diff --git a/src/jalview/datamodel/features/SequenceFeaturesI.java b/src/jalview/datamodel/features/SequenceFeaturesI.java index ec4b38c..deed751 100644 --- a/src/jalview/datamodel/features/SequenceFeaturesI.java +++ b/src/jalview/datamodel/features/SequenceFeaturesI.java @@ -229,5 +229,22 @@ public interface SequenceFeaturesI */ void deleteAll(); - void findFeatures(int pos, String type, List result); + /** + * Point-specific parameter return for JavaScript + * + * @param pos + * @param type + * @param result + * @return result (JavaScript) or new ArrayList (Java -- see FeatureRender) + * @author Bob Hanson 2019.07.30 + */ + List findFeatures(int pos, String type, List result); + + /** + * @author Bob Hanson 2019.08.01 + * + * @param type + * @return true if this type is in featureStore + */ + boolean hasFeatures(String type); } diff --git a/src/jalview/renderer/OverviewRenderer.java b/src/jalview/renderer/OverviewRenderer.java index 22c75c3..36b5847 100644 --- a/src/jalview/renderer/OverviewRenderer.java +++ b/src/jalview/renderer/OverviewRenderer.java @@ -30,7 +30,6 @@ import jalview.datamodel.Annotation; import jalview.datamodel.SequenceGroup; import jalview.datamodel.SequenceI; import jalview.renderer.seqfeatures.FeatureColourFinder; -import jalview.renderer.seqfeatures.FeatureRenderer; import jalview.util.Platform; import jalview.viewmodel.OverviewDimensions; @@ -97,21 +96,32 @@ public class OverviewRenderer private AlignmentViewPanel panel; - private int sequencesHeight; + // private int sequencesHeight; - public OverviewRenderer(AlignmentViewPanel panel, FeatureRenderer fr, - OverviewDimensions od, - AlignmentI alignment, - ResidueShaderI resshader, OverviewResColourFinder colFinder) + public OverviewRenderer(AlignmentViewPanel panel, + jalview.api.FeatureRenderer fr, OverviewDimensions od, + AlignmentI alignment, ResidueShaderI resshader, + OverviewResColourFinder colFinder) { this(panel, fr, od, alignment, resshader, colFinder, true); } + /** + * @param panel + * @param fr + * @param od + * @param alignment + * @param resshader + * @param colFinder + * @param shwoProgress + * possibly not, in JavaScript and for testng + */ public OverviewRenderer(AlignmentViewPanel panel, jalview.api.FeatureRenderer fr, OverviewDimensions od, AlignmentI alignment, ResidueShaderI resshader, OverviewResColourFinder colFinder, boolean showProgress) { + { this.panel = panel; finder = new FeatureColourFinder(fr); al = alignment; @@ -129,7 +139,7 @@ public class OverviewRenderer pixelsPerSeq = od.getPixelsPerSeq(); pixelsPerCol = od.getPixelsPerCol(); colsPerPixel = Math.max(1, 1f / pixelsPerCol); - + } } final static int STATE_INIT = 0; @@ -224,9 +234,9 @@ public class OverviewRenderer miniMe = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); WritableRaster raster = miniMe.getRaster(); DataBufferInt db = (DataBufferInt) raster.getDataBuffer(); - Platform.timeCheck(null, Platform.TIME_MARK); pixels = db.getBankData()[0]; bscol = cols.getOverviewBitSet(); + Platform.timeCheck(null, Platform.TIME_MARK); } private void nextRow() @@ -327,6 +337,16 @@ public class OverviewRenderer w); } + private ActionListener listener = new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) + { + mainLoop(); + } + + }; + private boolean loop() { if (delay <= 0) @@ -335,15 +355,7 @@ public class OverviewRenderer } if (timer == null) { - timer = new Timer(delay, new ActionListener() - { - @Override - public void actionPerformed(ActionEvent e) - { - mainLoop(); - } - - }); + timer = new Timer(delay, listener); timer.setRepeats(false); timer.start(); } @@ -356,10 +368,13 @@ public class OverviewRenderer private void done() { - Platform.timeCheck( - "overviewrender " + ndone + " pixels row:" + row + " redraw:" - + redraw, - Platform.TIME_MARK); + if (!redraw) + { + Platform.timeCheck( + "overviewrender " + ndone + " pixels row:" + row + " redraw:" + + redraw, + Platform.TIME_MARK); + } overlayHiddenRegions(); if (showProgress) diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java index bed72c5..ee5ffe6 100644 --- a/src/jalview/renderer/seqfeatures/FeatureRenderer.java +++ b/src/jalview/renderer/seqfeatures/FeatureRenderer.java @@ -91,11 +91,13 @@ public class FeatureRenderer extends FeatureRendererModel int pady = (y1 + charHeight) - charHeight / 5; FontMetrics fm = g.getFontMetrics(); + char s = '\0'; for (int i = featureStart; i <= featureEnd; i++) { - char s = seq.getCharAt(i); - if (Comparison.isGap(s)) + // colourOnly is just for Overview -- no need to check this again + + if (!colourOnly && Comparison.isGap(s = seq.getCharAt(i))) { continue; } @@ -104,7 +106,12 @@ public class FeatureRenderer extends FeatureRendererModel g.fillRect((i - start) * charWidth, y1, charWidth, charHeight); - if (colourOnly || !validCharWidth) + if (colourOnly) + { + return true; + } + + if (!validCharWidth) { continue; } @@ -118,6 +125,9 @@ public class FeatureRenderer extends FeatureRendererModel } /** + * + * BH - this method is never called? + * * Renders the sequence using the given SCORE feature colour between the given * start and end columns. Returns true if at least one column is drawn, else * false (the feature range does not overlap the start and end positions). @@ -212,10 +222,12 @@ public class FeatureRenderer extends FeatureRendererModel @Override public Color findFeatureColour(SequenceI seq, int column, Graphics g) { - if (!av.isShowSequenceFeatures()) - { - return null; - } + // BH 2019.08.01 + // this is already checked in FeatureColorFinder + // if (!av.isShowSequenceFeatures()) + // { + // return null; + // } // column is 'base 1' but getCharAt is an array index (ie from 0) if (Comparison.isGap(seq.getCharAt(column - 1))) @@ -253,7 +265,8 @@ public class FeatureRenderer extends FeatureRendererModel * applies), or null if no feature is drawn in the range given. * * @param g - * the graphics context to draw on (may be null if colourOnly==true) + * the graphics context to draw on (may be null only if t == 1 from + * colourOnly==true) * @param seq * @param start * start column @@ -270,21 +283,27 @@ public class FeatureRenderer extends FeatureRendererModel final SequenceI seq, int start, int end, int y1, boolean colourOnly) { + // from SeqCanvas and OverviewRender /* * if columns are all gapped, or sequence has no features, nothing to do */ - ContiguousI visiblePositions = seq.findPositions(start + 1, end + 1); - if (visiblePositions == null || !seq.getFeatures().hasFeatures()) + ContiguousI visiblePositions; + if (!seq.getFeatures().hasFeatures() || (visiblePositions = seq + .findPositions(start + 1, end + 1)) == null) { return null; } + int vp0 = visiblePositions.getBegin(); + int vp1 = visiblePositions.getEnd(); + updateFeatures(); - if (transparency != 1f && g != null) + if (transparency != 1f) // g cannot be null here if trans == 1f - BH // && g + // != null) { - Graphics2D g2 = (Graphics2D) g; - g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, + ((Graphics2D) g).setComposite( + AlphaComposite.getInstance(AlphaComposite.SRC_OVER, transparency)); } @@ -293,25 +312,28 @@ public class FeatureRenderer extends FeatureRendererModel /* * iterate over features in ordering of their rendering (last is on top) */ - for (int renderIndex = 0; renderIndex < renderOrder.length; renderIndex++) + for (int renderIndex = 0, n = renderOrder.length; renderIndex < n; renderIndex++) { String type = renderOrder[renderIndex]; - if (!showFeatureOfType(type)) + if (!seq.hasFeatures(type) || !showFeatureOfType(type)) { continue; } FeatureColourI fc = getFeatureStyle(type); - List overlaps = seq.getFeatures().findFeatures( - visiblePositions.getBegin(), visiblePositions.getEnd(), type); + List overlaps = seq.getFeatures().findFeatures(vp0, + vp1, type); - if (fc.isSimpleColour()) + // colourOnly (i.e. Overview) can only be here if translucent, so + // there is no need to check for filtering + if (!colourOnly && fc.isSimpleColour()) { filterFeaturesForDisplay(overlaps); } - for (SequenceFeature sf : overlaps) + for (int i = overlaps.size(); --i >= 0;) { + SequenceFeature sf = overlaps.get(i); Color featureColour = getColor(sf, fc); if (featureColour == null) { @@ -326,22 +348,22 @@ public class FeatureRenderer extends FeatureRendererModel * restrict to visible positions (or if a contact feature, * to a single position) */ - int visibleStart = sf.getBegin(); - if (visibleStart < visiblePositions.getBegin()) + int sf0 = sf.getBegin(); + int sf1 = sf.getEnd(); + int visibleStart = sf0; + if (visibleStart < vp0) { - visibleStart = sf.isContactFeature() ? sf.getEnd() - : visiblePositions.getBegin(); + visibleStart = sf.isContactFeature() ? sf1 : vp0; } - int visibleEnd = sf.getEnd(); - if (visibleEnd > visiblePositions.getEnd()) + int visibleEnd = sf1; + if (visibleEnd > vp1) { - visibleEnd = sf.isContactFeature() ? sf.getBegin() - : visiblePositions.getEnd(); + visibleEnd = sf.isContactFeature() ? sf0 : vp1; } int featureStartCol = seq.findIndex(visibleStart); - int featureEndCol = sf.begin == sf.end ? featureStartCol : seq - .findIndex(visibleEnd); + int featureEndCol = (sf.begin == sf.end ? featureStartCol + : seq.findIndex(visibleEnd)); // Color featureColour = getColour(sequenceFeature); @@ -382,26 +404,24 @@ public class FeatureRenderer extends FeatureRendererModel else { */ - boolean drawn = renderFeature(g, seq, - featureStartCol - 1, - featureEndCol - 1, featureColour, - start, end, y1, colourOnly); - if (drawn) - { - drawnColour = featureColour; - } + boolean drawn = renderFeature(g, seq, featureStartCol - 1, + featureEndCol - 1, featureColour, start, end, y1, + colourOnly); + if (drawn) + { + drawnColour = featureColour; + } /*}*/ } } } - if (transparency != 1.0f && g != null) + if (transparency != 1.0f) { /* * reset transparency */ - Graphics2D g2 = (Graphics2D) g; - g2.setComposite(NO_TRANSPARENCY); + ((Graphics2D) g).setComposite(NO_TRANSPARENCY); } return drawnColour; @@ -418,7 +438,9 @@ public class FeatureRenderer extends FeatureRendererModel findAllFeatures(); } - private List overlaps = new ArrayList<>(); + @SuppressWarnings("unused") + private List overlaps = (/** @j2sNative true || */ + false ? null : new ArrayList<>()); /** * Returns the sequence feature colour rendered at the given column position, @@ -431,9 +453,10 @@ public class FeatureRenderer extends FeatureRendererModel * colour for features enclosing a gapped column. Check for gap before calling * if different behaviour is wanted. * - * BH 2019.07.30 + * BH 2019.07.30 * - * Adds a result ArrayList to parameters in order to avoid an unnecessary construction of that for every pixel checked. + * Adds a result ArrayList to parameters in order to avoid an unnecessary + * construction of that for every pixel checked. * * * @param seq @@ -452,28 +475,32 @@ public class FeatureRenderer extends FeatureRendererModel * inspect features in reverse renderOrder (the last in the array is * displayed on top) until we find one that is rendered at the position */ - for (int renderIndex = renderOrder.length - - 1; renderIndex >= 0; renderIndex--) + for (int renderIndex = renderOrder.length; --renderIndex >= 0;) { String type = renderOrder[renderIndex]; - if (!showFeatureOfType(type)) + if (!seq.hasFeatures(type) || !showFeatureOfType(type)) { continue; } - overlaps.clear(); - seq.findFeatures(column, type, overlaps); - if (overlaps.size() > 0) + if (overlaps != null) + { + overlaps.clear(); + } + List list = seq.findFeatures(column, type, overlaps); + if (list.size() > 0) { - for (SequenceFeature sequenceFeature : overlaps) + for (int i = 0, n = list.size(); i < n; i++) { - if (!featureGroupNotShown(sequenceFeature)) + SequenceFeature sf = list.get(i); + if (featureGroupNotShown(sf)) { - Color col = getColour(sequenceFeature); - if (col != null) - { - return col; - } + continue; + } + Color col = getColour(sf); + if (col != null) + { + return col; } } } diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index 6bf1f45..8576482 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -988,10 +988,8 @@ public abstract class FeatureRendererModel { return featureGroups != null && sequenceFeature.featureGroup != null - && sequenceFeature.featureGroup.length() != 0 - && featureGroups.containsKey(sequenceFeature.featureGroup) - && !featureGroups.get(sequenceFeature.featureGroup) - .booleanValue(); + && sequenceFeature.featureGroup.length() > 0 && featureGroups + .get(sequenceFeature.featureGroup) == Boolean.FALSE; } /**