From d8084ce0043bb2454cfc48fe9c87f9aef12c0cf8 Mon Sep 17 00:00:00 2001 From: gmungoc Date: Tue, 26 Apr 2016 14:45:13 +0100 Subject: [PATCH] JAL-2055 prototype alternate feature colouring based on SequenceFeature.featureNumber property --- resources/lang/Messages.properties | 4 +- src/jalview/analysis/AlignmentUtils.java | 7 ++ src/jalview/api/FeatureRenderer.java | 8 ++ src/jalview/datamodel/SequenceFeature.java | 31 ++++++ src/jalview/gui/FeatureColourChooser.java | 109 ++++++++++++-------- src/jalview/gui/FeatureSettings.java | 31 ++++-- src/jalview/schemes/GraduatedColor.java | 24 +++++ .../seqfeatures/FeatureRendererModel.java | 91 +++++++++------- 8 files changed, 214 insertions(+), 91 deletions(-) diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties index 3c79b58..10dcdd8 100644 --- a/resources/lang/Messages.properties +++ b/resources/lang/Messages.properties @@ -1295,4 +1295,6 @@ info.error_creating_file = Error creating {0} file. label.run_groovy = Run Groovy console script label.run_groovy_tip = Run the script in the Groovy console over this alignment label.couldnt_run_groovy_script = Failed to run Groovy script -label.uniprot_sequence_fetcher = UniProt Sequence Fetcher \ No newline at end of file +label.uniprot_sequence_fetcher = UniProt Sequence Fetcher +label.colour_alternately = Alternately +label.colour_alternately_tip = Colour features in two alternating colours diff --git a/src/jalview/analysis/AlignmentUtils.java b/src/jalview/analysis/AlignmentUtils.java index 42a1201..2090207 100644 --- a/src/jalview/analysis/AlignmentUtils.java +++ b/src/jalview/analysis/AlignmentUtils.java @@ -1529,6 +1529,8 @@ public class AlignmentUtils public static int transferFeatures(SequenceI fromSeq, SequenceI toSeq, MapList mapping, String select, String... omitting) { + Map featureCounts = new HashMap(); + SequenceI copyTo = toSeq; while (copyTo.getDatasetSequence() != null) { @@ -1601,6 +1603,11 @@ public class AlignmentUtils copy.setBegin(Math.min(mappedTo[0], mappedTo[1])); copy.setEnd(Math.max(mappedTo[0], mappedTo[1])); copyTo.addSequenceFeature(copy); + int featureNumber = featureCounts.containsKey(type) ? featureCounts + .get(type) : -1; + featureNumber++; + featureCounts.put(type, featureNumber); + copy.setFeatureNumber(featureNumber); count++; } } diff --git a/src/jalview/api/FeatureRenderer.java b/src/jalview/api/FeatureRenderer.java index 5b15cad..6e0cf9f 100644 --- a/src/jalview/api/FeatureRenderer.java +++ b/src/jalview/api/FeatureRenderer.java @@ -177,4 +177,12 @@ public interface FeatureRenderer */ void setVisible(String featureType); + /** + * Answers true if the feature has 'featureNumber' properties set + * + * @param featureType + * @return + */ + boolean isOrdinal(String featureType); + } diff --git a/src/jalview/datamodel/SequenceFeature.java b/src/jalview/datamodel/SequenceFeature.java index f2eb8ac..54ff6df 100755 --- a/src/jalview/datamodel/SequenceFeature.java +++ b/src/jalview/datamodel/SequenceFeature.java @@ -39,6 +39,9 @@ public class SequenceFeature // private key for Phase designed not to conflict with real GFF data private static final String PHASE = "!Phase"; + // private key for feature number designed not to conflict with real GFF data + private static final String NUMBER = "!Num"; + /* * ATTRIBUTES is reserved for the GFF 'column 9' data, formatted as * name1=value1;name2=value2,value3;...etc @@ -480,6 +483,34 @@ public class SequenceFeature } /** + * Set the ordinal number of this feature on the sequence + * + * @param num + */ + public void setFeatureNumber(int num) + { + setValue(NUMBER, String.valueOf(num)); + } + + /** + * Returns the feature number if set, else 0. Intended for use as the ordinal + * position of the feature on the sequence for features of the same type. + * + * @return + */ + public int getFeatureNumber() + { + try + { + return Integer.parseInt((String) getValue(NUMBER)); + } catch (Exception e) + { + // property absent or not numeric + return 0; + } + } + + /** * Readable representation, for debug only, not guaranteed not to change * between versions */ diff --git a/src/jalview/gui/FeatureColourChooser.java b/src/jalview/gui/FeatureColourChooser.java index 064d58b..d4e37f7 100644 --- a/src/jalview/gui/FeatureColourChooser.java +++ b/src/jalview/gui/FeatureColourChooser.java @@ -84,6 +84,31 @@ public class FeatureColourChooser extends JalviewDialog String type = null; + JPanel minColour = new JPanel(); + + JPanel maxColour = new JPanel(); + + JComboBox threshold = new JComboBox(); + + JSlider slider = new JSlider(); + + JTextField thresholdValue = new JTextField(20); + + // TODO implement GUI for tolower flag + // JCheckBox toLower = new JCheckBox(); + + JCheckBox thresholdIsMin = new JCheckBox(); + + JCheckBox colourAlternately = new JCheckBox(); + + JCheckBox colourByLabel = new JCheckBox(); + + private GraphLine threshline; + + private Color oldmaxColour; + + private Color oldminColour; + public FeatureColourChooser(FeatureRenderer frender, String type) { this(frender, false, type); @@ -127,7 +152,7 @@ public class FeatureColourChooser extends JalviewDialog } }); - float mm[] = ((float[][]) fr.getMinMax().get(type))[0]; + float mm[] = fr.getMinMax().get(type)[0]; min = mm[0]; max = mm[1]; @@ -176,6 +201,7 @@ public class FeatureColourChooser extends JalviewDialog // update the gui from threshold state thresholdIsMin.setSelected(!cs.isAutoScale()); colourByLabel.setSelected(cs.isColourByLabel()); + colourAlternately.setSelected(cs.isColourAlternately()); if (cs.getThreshType() != AnnotationColourGradient.NO_THRESHOLD) { // initialise threshold slider and selector @@ -229,12 +255,16 @@ public class FeatureColourChooser extends JalviewDialog } }); maxColour.setBorder(new LineBorder(Color.black)); + JLabel minText = new JLabel(); minText.setText(MessageManager.getString("label.min")); minText.setFont(JvSwingUtils.getLabelFont()); + JLabel maxText = new JLabel(); maxText.setText(MessageManager.getString("label.max")); maxText.setFont(JvSwingUtils.getLabelFont()); - this.setLayout(borderLayout1); - jPanel2.setLayout(flowLayout1); + this.setLayout(new BorderLayout()); + JPanel jPanel1 = new JPanel(); + JPanel jPanel2 = new JPanel(); + jPanel2.setLayout(new FlowLayout()); jPanel1.setBackground(Color.white); jPanel2.setBackground(Color.white); threshold.addActionListener(new ActionListener() @@ -253,7 +283,8 @@ public class FeatureColourChooser extends JalviewDialog .getString("label.threshold_feature_above_thereshold")); // index 1 threshold.addItem(MessageManager .getString("label.threshold_feature_below_thereshold")); // index 2 - jPanel3.setLayout(flowLayout2); + JPanel jPanel3 = new JPanel(); + jPanel3.setLayout(new FlowLayout()); thresholdValue.addActionListener(new ActionListener() { @Override @@ -286,6 +317,19 @@ public class FeatureColourChooser extends JalviewDialog thresholdIsMin_actionPerformed(actionEvent); } }); + colourAlternately.setBackground(Color.white); + colourAlternately.setText(MessageManager + .getString("label.colour_alternately")); + colourAlternately.setToolTipText(MessageManager + .getString("label.colour_alternately_tip")); + colourAlternately.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent actionEvent) + { + colourAlternately_actionPerformed(actionEvent); + } + }); colourByLabel.setBackground(Color.white); colourByLabel .setText(MessageManager.getString("label.colour_by_label")); @@ -300,10 +344,13 @@ public class FeatureColourChooser extends JalviewDialog colourByLabel_actionPerformed(actionEvent); } }); + + JPanel colourPanel = new JPanel(); colourPanel.setBackground(Color.white); jPanel1.add(ok); jPanel1.add(cancel); - jPanel2.add(colourByLabel, java.awt.BorderLayout.WEST); + jPanel2.add(colourAlternately, java.awt.BorderLayout.WEST); + jPanel2.add(colourByLabel, java.awt.BorderLayout.CENTER); jPanel2.add(colourPanel, java.awt.BorderLayout.EAST); colourPanel.add(minText); colourPanel.add(minColour); @@ -318,46 +365,10 @@ public class FeatureColourChooser extends JalviewDialog this.add(jPanel2, java.awt.BorderLayout.NORTH); } - JLabel minText = new JLabel(); - - JLabel maxText = new JLabel(); - - JPanel minColour = new JPanel(); - - JPanel maxColour = new JPanel(); - - JPanel colourPanel = new JPanel(); - - JPanel jPanel1 = new JPanel(); - - JPanel jPanel2 = new JPanel(); - - BorderLayout borderLayout1 = new BorderLayout(); - - JComboBox threshold = new JComboBox(); - - FlowLayout flowLayout1 = new FlowLayout(); - - JPanel jPanel3 = new JPanel(); - - FlowLayout flowLayout2 = new FlowLayout(); - - JSlider slider = new JSlider(); - - JTextField thresholdValue = new JTextField(20); - - // TODO implement GUI for tolower flag - // JCheckBox toLower = new JCheckBox(); - - JCheckBox thresholdIsMin = new JCheckBox(); - - JCheckBox colourByLabel = new JCheckBox(); - - private GraphLine threshline; - - private Color oldmaxColour; - - private Color oldminColour; + protected void colourAlternately_actionPerformed(ActionEvent actionEvent) + { + changeColour(); + } public void minColour_actionPerformed() { @@ -472,6 +483,14 @@ public class FeatureColourChooser extends JalviewDialog acg.setAutoScaled(true); } acg.setColourByLabel(colourByLabel.isSelected()); + acg.setColourAlternately(colourAlternately.isSelected()); + colourByLabel.setEnabled(!colourAlternately.isSelected()); + colourAlternately.setEnabled(!colourByLabel.isSelected()); + if (!fr.isOrdinal(type)) + { + // don't allow colour alternately if no feature numbering on features + colourAlternately.setEnabled(false); + } if (acg.isColourByLabel()) { maxColour.setEnabled(false); diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java index 6633156..9072ce6 100644 --- a/src/jalview/gui/FeatureSettings.java +++ b/src/jalview/gui/FeatureSettings.java @@ -1587,17 +1587,22 @@ public class FeatureSettings extends JPanel implements tx += "Label"; comp.setIcon(null); } + // else if (gcol.isColourAlternately()) + // { + // tt = "Coloured alternately " + tt; + // if (thr) + // { + // tx += " "; + // } + // tx += "Alternately"; + // comp.setIcon(null); + // } else { Color newColor = gcol.getMaxColor(); comp.setBackground(newColor); - // System.err.println("Width is " + w / 2); Icon ficon = new FeatureIcon(gcol, comp.getBackground(), w, h, thr); comp.setIcon(ficon); - // tt+="RGB value: Max (" + newColor.getRed() + ", " - // + newColor.getGreen() + ", " + newColor.getBlue() - // + ")\nMin (" + minCol.getRed() + ", " + minCol.getGreen() - // + ", " + minCol.getBlue() + ")"); } comp.setHorizontalAlignment(SwingConstants.CENTER); comp.setText(tx); @@ -1636,7 +1641,7 @@ class FeatureIcon implements Icon width = w; height = h; midspace = mspace; - if (midspace) + if (midspace || gcol.isColourAlternately()) { s1 = width / 3; e1 = s1 * 2; @@ -1681,6 +1686,16 @@ class FeatureIcon implements Icon g.drawString(MessageManager.getString("label.label"), 0, 0); } + else if (gcol.isColourAlternately()) + { + Color minCol = gcol.getMinColor(); + g.setColor(minCol); + g.fillRect(0, 0, s1, height); + g.setColor(gcol.getMaxColor()); + g.fillRect(s1, 0, e1 - s1, height); + g.setColor(minCol); + g.fillRect(e1, 0, width - e1, height); + } else { Color minCol = gcol.getMinColor(); @@ -1692,7 +1707,9 @@ class FeatureIcon implements Icon g.fillRect(s1, 0, e1 - s1, height); } g.setColor(gcol.getMaxColor()); - g.fillRect(0, e1, width - e1, height); + g.fillRect(e1, 0, width - e1, height); + // this is wrong but works - why?? + // g.fillRect(0, e1, width - e1, height); } } } diff --git a/src/jalview/schemes/GraduatedColor.java b/src/jalview/schemes/GraduatedColor.java index 2d1c572..f629d09 100644 --- a/src/jalview/schemes/GraduatedColor.java +++ b/src/jalview/schemes/GraduatedColor.java @@ -34,6 +34,11 @@ import java.awt.Color; */ public class GraduatedColor { + private static final Color[] colours = new Color[] { Color.blue, + Color.red }; + + private static int colourModulus = 0; + int thresholdState = AnnotationColourGradient.NO_THRESHOLD; // or // ABOVE_THRESHOLD // or @@ -104,6 +109,7 @@ public class GraduatedColor thrsh = oldcs.thrsh; autoScale = oldcs.autoScale; colourByLabel = oldcs.colourByLabel; + colourAlternately = oldcs.colourAlternately; } /** @@ -182,6 +188,8 @@ public class GraduatedColor private boolean colourByLabel = false; + private boolean colourAlternately = false; + /** * * @return true if colourByLabel style is set @@ -212,6 +220,12 @@ public class GraduatedColor } return ucs.createColourFromName(feature.getDescription()); } + if (colourAlternately) + { + int minOrMax = feature.getFeatureNumber() % 2; + return minOrMax == 0 ? new Color(lr, lg, lb) : new Color(lr + dr, lg + + dg, lb + db); + } if (range == 0.0) { return getMaxColor(); @@ -301,4 +315,14 @@ public class GraduatedColor tolow = false; } } + + public boolean isColourAlternately() + { + return colourAlternately; + } + + public void setColourAlternately(boolean colourAlternately) + { + this.colourAlternately = colourAlternately; + } } diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index 848f565..7e28c7f 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -74,6 +74,12 @@ public abstract class FeatureRendererModel implements */ private Map minmax = new Hashtable(); + /* + * List of feature types where getFeatureNumber() > 0 is found + * - a heuristic for 'features have an explicit ordering' + */ + private List ordinalFeatures = new ArrayList(); + @Override public AlignViewportI getViewport() { @@ -309,7 +315,7 @@ public abstract class FeatureRendererModel implements * Searches alignment for all features and updates colours * * @param newMadeVisible - * if true newly added feature types will be rendered immediatly + * if true newly added feature types will be rendered immediately * TODO: check to see if this method should actually be proxied so * repaint events can be propagated by the renderer code */ @@ -325,14 +331,15 @@ public abstract class FeatureRendererModel implements } findingFeatures = true; + ordinalFeatures.clear(); if (av.getFeaturesDisplayed() == null) { av.setFeaturesDisplayed(new FeaturesDisplayed()); } FeaturesDisplayedI featuresDisplayed = av.getFeaturesDisplayed(); - ArrayList allfeatures = new ArrayList(); - ArrayList oldfeatures = new ArrayList(); + List allfeatures = new ArrayList(); + List oldfeatures = new ArrayList(); if (renderOrder != null) { for (int i = 0; i < renderOrder.length; i++) @@ -348,9 +355,8 @@ public abstract class FeatureRendererModel implements minmax = new Hashtable(); } AlignmentI alignment = av.getAlignment(); - for (int i = 0; i < alignment.getHeight(); i++) + for (SequenceI asq : alignment.getSequences()) { - SequenceI asq = alignment.getSequenceAt(i); SequenceFeature[] features = asq.getSequenceFeatures(); if (features == null) @@ -358,12 +364,12 @@ public abstract class FeatureRendererModel implements continue; } - int index = 0; - while (index < features.length) + for (SequenceFeature feature : features) { - if (!featuresDisplayed.isRegistered(features[index].getType())) + String type = feature.getType(); + if (!featuresDisplayed.isRegistered(type)) { - String fgrp = features[index].getFeatureGroup(); + String fgrp = feature.getFeatureGroup(); if (fgrp != null) { Boolean groupDisplayed = featureGroups.get(fgrp); @@ -374,57 +380,60 @@ public abstract class FeatureRendererModel implements } if (!groupDisplayed.booleanValue()) { - index++; continue; } } - if (!(features[index].begin == 0 && features[index].end == 0)) + if (!(feature.begin == 0 && feature.end == 0)) { // If beginning and end are 0, the feature is for the whole sequence // and we don't want to render the feature in the normal way if (newMadeVisible - && !oldfeatures.contains(features[index].getType())) + && !oldfeatures.contains(type)) { // this is a new feature type on the alignment. Mark it for // display. - featuresDisplayed.setVisible(features[index].getType()); - setOrder(features[index].getType(), 0); + featuresDisplayed.setVisible(type); + setOrder(type, 0); } } } - if (!allfeatures.contains(features[index].getType())) + if (!allfeatures.contains(type)) { - allfeatures.add(features[index].getType()); + allfeatures.add(type); } - if (!Float.isNaN(features[index].score)) + float score = feature.score; + if (!Float.isNaN(score)) { - int nonpos = features[index].getBegin() >= 1 ? 0 : 1; - float[][] mm = minmax.get(features[index].getType()); + int nonpos = feature.getBegin() >= 1 ? 0 : 1; + float[][] mm = minmax.get(type); if (mm == null) { mm = new float[][] { null, null }; - minmax.put(features[index].getType(), mm); + minmax.put(type, mm); } if (mm[nonpos] == null) { - mm[nonpos] = new float[] { features[index].score, - features[index].score }; + mm[nonpos] = new float[] { score, score }; } else { - if (mm[nonpos][0] > features[index].score) - { - mm[nonpos][0] = features[index].score; - } - if (mm[nonpos][1] < features[index].score) - { - mm[nonpos][1] = features[index].score; - } + mm[nonpos][0] = Math.min(mm[nonpos][0], score); + mm[nonpos][1] = Math.max(mm[nonpos][1], score); + } + } + + /* + * add to 'ordinal feature types' if it has featureNumber > 0 + */ + if (!ordinalFeatures.contains(type)) + { + if (feature.getFeatureNumber() > 0) + { + ordinalFeatures.add(type); } } - index++; } } updateRenderOrder(allfeatures); @@ -449,7 +458,8 @@ public abstract class FeatureRendererModel implements List allfeatures = new ArrayList(allFeatures); String[] oldRender = renderOrder; renderOrder = new String[allfeatures.size()]; - Object mmrange, fc = null; + float[][] mmrange; + Object fc; boolean initOrders = (featureOrder == null); int opos = 0; if (oldRender != null && oldRender.length > 0) @@ -464,8 +474,8 @@ public abstract class FeatureRendererModel implements } if (allfeatures.contains(oldRender[j])) { - renderOrder[opos++] = oldRender[j]; // existing features always - // appear below new features + renderOrder[opos++] = oldRender[j]; + // existing features always appear below new features allfeatures.remove(oldRender[j]); if (minmax != null) { @@ -477,8 +487,8 @@ public abstract class FeatureRendererModel implements && ((GraduatedColor) fc).isAutoScale()) { ((GraduatedColor) fc).updateBounds( - ((float[][]) mmrange)[0][0], - ((float[][]) mmrange)[0][1]); + mmrange[0][0], + mmrange[0][1]); } } } @@ -509,8 +519,8 @@ public abstract class FeatureRendererModel implements if (fc != null && fc instanceof GraduatedColor && ((GraduatedColor) fc).isAutoScale()) { - ((GraduatedColor) fc).updateBounds(((float[][]) mmrange)[0][0], - ((float[][]) mmrange)[0][1]); + ((GraduatedColor) fc).updateBounds(mmrange[0][0], + mmrange[0][1]); } } } @@ -1009,4 +1019,9 @@ public abstract class FeatureRendererModel implements return _gps; } + @Override + public boolean isOrdinal(String featureType) + { + return ordinalFeatures.contains(featureType); + } } -- 1.7.10.2