From: gmungoc
Date: Tue, 7 Nov 2017 11:50:54 +0000 (+0000)
Subject: JAL-2069 update spike branch with latest
X-Git-Tag: Release_2_11_0~62^2~17
X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=14307f5cfcbc90c419c892434613a500ca550ecc;p=jalview.git
JAL-2069 update spike branch with latest
---
diff --git a/README b/README
index cbc93b1..eaf226b 100755
--- a/README
+++ b/README
@@ -25,7 +25,10 @@ To run application:
java -Djava.ext.dirs=JALVIEW_HOME/lib -cp JALVIEW_HOME/jalview.jar jalview.bin.Jalview
-Replace JALVIEW_HOME with the full path to Jalview Installation Directory.
+Replace JALVIEW_HOME with the full path to Jalview Installation Directory. If building from source:
+
+java -Djava.ext.dirs=JALVIEW_BUILD/dist -cp JALVIEW_BUILD/dist/jalview.jar jalview.bin.Jalview
+
##################
diff --git a/help/html/releases.html b/help/html/releases.html
index 92af377..7be088e 100755
--- a/help/html/releases.html
+++ b/help/html/releases.html
@@ -131,7 +131,8 @@ li:before {
Scale mark not shown when close to right hand end of alignment
Pairwise alignment only aligns selected regions of each selected sequence
Alignment ruler height set incorrectly after canceling the Alignment Window's Font dialog
- Show cross-references not enabled after restoring project until a new view is created
+ Show cross-references not enabled after restoring project until a new view is created
+ Warning popup about use of SEQUENCE_ID in URL links appears when only default EMBL-EBI link is configured (since 2.10.2b2)
Applet
diff --git a/resources/lang/Messages.properties b/resources/lang/Messages.properties
index 851585a..35ead5e 100644
--- a/resources/lang/Messages.properties
+++ b/resources/lang/Messages.properties
@@ -283,7 +283,6 @@ label.sequence = Sequence
label.view_pdb_structure = View PDB Structure
label.min = Min:
label.max = Max:
-label.colour_by_label = Colour by label
label.new_feature = New Feature
label.match_case = Match Case
label.view_alignment_editor = View in alignment editor
@@ -532,7 +531,7 @@ label.threshold_feature_above_threshold = Above Threshold
label.threshold_feature_below_threshold = Below Threshold
label.adjust_threshold = Adjust threshold
label.toggle_absolute_relative_display_threshold = Toggle between absolute and relative display threshold.
-label.display_features_same_type_different_label_using_different_colour = Display features of the same type with a different label using a different colour. (e.g. domain features)
+label.colour_by_label_tip = Display features of the same type with a different label using a different colour. (e.g. domain features)
label.select_colour_minimum_value = Select Colour for Minimum Value
label.select_colour_maximum_value = Select Colour for Maximum Value
label.open_url_param = Open URL {0}
@@ -1335,8 +1334,18 @@ label.matchCondition_le = <=
label.matchCondition_gt = >
label.matchCondition_ge = >=
label.numeric_required = The value should be numeric
-label.no_attributes_known = No attributes known
+label.no_attributes = No attributes known
+label.no_numeric_attributes = No numeric attributes known
label.filters = Filters
label.match_condition = Match condition
label.join_conditions = Join conditions with
label.feature_to_filter = Feature to filter
+label.colour_by_value = Colour by value
+label.colour_by_text = Colour by text
+label.score = Score
+label.attribute = Attribute
+label.colour_by_label = Colour by label
+label.variable_colour = Variable colour
+label.no_colour = No colour:
+label.select_no_value_colour = Select colour when no value
+label.select_new_colour = Select new colour
diff --git a/src/jalview/api/FeatureColourI.java b/src/jalview/api/FeatureColourI.java
index 0ded079..3eebf6c 100644
--- a/src/jalview/api/FeatureColourI.java
+++ b/src/jalview/api/FeatureColourI.java
@@ -56,6 +56,14 @@ public interface FeatureColourI
Color getMaxColour();
/**
+ * Returns the 'no value' colour (used when a feature lacks score, or the
+ * attribute, being used for colouring)
+ *
+ * @return
+ */
+ Color getNoColour();
+
+ /**
* Answers true if the feature has a single colour, i.e. if isColourByLabel()
* and isGraduatedColour() both answer false
*
@@ -156,7 +164,10 @@ public interface FeatureColourI
Color getColor(SequenceFeature feature);
/**
- * Update the min-max range for a graduated colour scheme
+ * Update the min-max range for a graduated colour scheme. Note that the
+ * colour scheme may be configured to colour by feature score, or a
+ * (numeric-valued) attribute - the caller should ensure that the correct
+ * range is being set.
*
* @param min
* @param max
@@ -169,4 +180,26 @@ public interface FeatureColourI
* @return
*/
String toJalviewFormat(String featureType);
+
+ /**
+ * Answers true if colour is by attribute text or numerical value
+ *
+ * @return
+ */
+ boolean isColourByAttribute();
+
+ /**
+ * Answers the name of the attribute used for colouring if any, or null
+ *
+ * @return
+ */
+ String getAttributeName();
+
+ /**
+ * Sets the name of the attribute used for colouring if any, or null to remove
+ * this property
+ *
+ * @return
+ */
+ void setAttributeName(String name);
}
diff --git a/src/jalview/appletgui/APopupMenu.java b/src/jalview/appletgui/APopupMenu.java
index 46bd4fd..76f2705 100644
--- a/src/jalview/appletgui/APopupMenu.java
+++ b/src/jalview/appletgui/APopupMenu.java
@@ -901,10 +901,7 @@ public class APopupMenu extends java.awt.PopupMenu
.formatMessage("label.annotation_for_displayid", new Object[]
{ seq.getDisplayId(true) }));
new SequenceAnnotationReport(null).createSequenceAnnotationReport(
- contents, seq, true, true,
- (ap.seqPanel.seqCanvas.fr != null)
- ? ap.seqPanel.seqCanvas.fr.getMinMax()
- : null);
+ contents, seq, true, true, ap.seqPanel.seqCanvas.fr);
contents.append("
");
}
Frame frame = new Frame();
diff --git a/src/jalview/datamodel/SequenceFeature.java b/src/jalview/datamodel/SequenceFeature.java
index ffbd497..2110632 100755
--- a/src/jalview/datamodel/SequenceFeature.java
+++ b/src/jalview/datamodel/SequenceFeature.java
@@ -442,10 +442,31 @@ public class SequenceFeature implements FeatureLocationI
}
otherDetails.put(key, value);
- FeatureAttributes.getInstance().addAttribute(this.type, key);
+ recordAttribute(key, value);
}
}
+ /**
+ * Notifies the addition of a feature attribute. This lets us keep track of
+ * which attributes are present on each feature type, and also the range of
+ * numerical-valued attributes.
+ *
+ * @param key
+ * @param value
+ */
+ protected void recordAttribute(String key, Object value)
+ {
+ String attDesc = null;
+ if (source != null)
+ {
+ attDesc = FeatureSources.getInstance().getSource(source)
+ .getAttributeName(key);
+ }
+
+ FeatureAttributes.getInstance().addAttribute(this.type, key, attDesc,
+ value.toString());
+ }
+
/*
* The following methods are added to maintain the castor Uniprot mapping file
* for the moment.
diff --git a/src/jalview/datamodel/features/FeatureAttributes.java b/src/jalview/datamodel/features/FeatureAttributes.java
index d4e9fb0..3dc4f19 100644
--- a/src/jalview/datamodel/features/FeatureAttributes.java
+++ b/src/jalview/datamodel/features/FeatureAttributes.java
@@ -5,8 +5,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
+import java.util.TreeMap;
/**
* A singleton class to hold the set of attributes known for each feature type
@@ -15,7 +14,91 @@ public class FeatureAttributes
{
private static FeatureAttributes instance = new FeatureAttributes();
- private Map> attributes;
+ private Map> attributes;
+
+ private class AttributeData
+ {
+ /*
+ * description(s) for this attribute, if known
+ * (different feature source might have differing descriptions)
+ */
+ List description;
+
+ /*
+ * minimum value (of any numeric values recorded)
+ */
+ float min = 0f;
+
+ /*
+ * maximum value (of any numeric values recorded)
+ */
+ float max = 0f;
+
+ /*
+ * flag is set true if any numeric value is detected for this attribute
+ */
+ boolean hasValue = false;
+
+ /**
+ * Note one instance of this attribute, recording unique, non-null names,
+ * and the min/max of any numerical values
+ *
+ * @param desc
+ * @param value
+ */
+ void addInstance(String desc, String value)
+ {
+ addDescription(desc);
+
+ if (value != null)
+ {
+ try
+ {
+ float f = Float.valueOf(value);
+ min = Float.min(min, f);
+ max = Float.max(max, f);
+ hasValue = true;
+ } catch (NumberFormatException e)
+ {
+ // ok, wasn't a number, ignore for min-max purposes
+ }
+ }
+ }
+
+ /**
+ * Answers the description of the attribute, if recorded and unique, or null if either no, or more than description is recorded
+ * @return
+ */
+ public String getDescription()
+ {
+ if (description != null && description.size() == 1)
+ {
+ return description.get(0);
+ }
+ return null;
+ }
+
+ /**
+ * Adds the given description to the list of known descriptions (without
+ * duplication)
+ *
+ * @param desc
+ */
+ public void addDescription(String desc)
+ {
+ if (desc != null)
+ {
+ if (description == null)
+ {
+ description = new ArrayList<>();
+ }
+ if (!description.contains(desc))
+ {
+ description.add(desc);
+ }
+ }
+ }
+ }
/**
* Answers the singleton instance of this class
@@ -46,7 +129,7 @@ public class FeatureAttributes
return Collections. emptyList();
}
- return new ArrayList<>(attributes.get(featureType));
+ return new ArrayList<>(attributes.get(featureType).keySet());
}
/**
@@ -58,7 +141,6 @@ public class FeatureAttributes
*/
public boolean hasAttributes(String featureType)
{
-
if (attributes.containsKey(featureType))
{
if (!attributes.get(featureType).isEmpty())
@@ -70,24 +152,113 @@ public class FeatureAttributes
}
/**
- * Records the given attribute name for the given feature type
+ * Records the given attribute name and description for the given feature
+ * type, and updates the min-max for any numeric value
*
* @param featureType
* @param attName
+ * @param description
+ * @param value
*/
- public void addAttribute(String featureType, String attName)
+ public void addAttribute(String featureType, String attName,
+ String description, String value)
{
if (featureType == null || attName == null)
{
return;
}
- if (!attributes.containsKey(featureType))
+ Map atts = attributes.get(featureType);
+ if (atts == null)
+ {
+ atts = new TreeMap(
+ String.CASE_INSENSITIVE_ORDER);
+ attributes.put(featureType, atts);
+ }
+ AttributeData attData = atts.get(attName);
+ if (attData == null)
{
- attributes.put(featureType, new TreeSet(
- String.CASE_INSENSITIVE_ORDER));
+ attData = new AttributeData();
+ atts.put(attName, attData);
}
+ attData.addInstance(description, value);
+ }
- attributes.get(featureType).add(attName);
+ /**
+ * Answers the description of the given attribute for the given feature type,
+ * if known and unique, else null
+ *
+ * @param featureType
+ * @param attName
+ * @return
+ */
+ public String getDescription(String featureType, String attName)
+ {
+ String desc = null;
+ Map atts = attributes.get(featureType);
+ if (atts != null)
+ {
+ AttributeData attData = atts.get(attName);
+ if (attData != null)
+ {
+ desc = attData.getDescription();
+ }
+ }
+ return desc;
+ }
+
+ /**
+ * Answers the [min, max] value range of the given attribute for the given
+ * feature type, if known, else null. Attributes which only have text values
+ * would normally return null, however text values which happen to be numeric
+ * could result in a 'min-max' range.
+ *
+ * @param featureType
+ * @param attName
+ * @return
+ */
+ public float[] getMinMax(String featureType, String attName)
+ {
+ Map atts = attributes.get(featureType);
+ if (atts != null)
+ {
+ AttributeData attData = atts.get(attName);
+ if (attData != null && attData.hasValue)
+ {
+ return new float[] { attData.min, attData.max };
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Records the given attribute description for the given feature type
+ *
+ * @param featureType
+ * @param attName
+ * @param description
+ */
+ public void addDescription(String featureType, String attName,
+ String description)
+ {
+ if (featureType == null || attName == null)
+ {
+ return;
+ }
+
+ Map atts = attributes.get(featureType);
+ if (atts == null)
+ {
+ atts = new TreeMap(
+ String.CASE_INSENSITIVE_ORDER);
+ attributes.put(featureType, atts);
+ }
+ AttributeData attData = atts.get(attName);
+ if (attData == null)
+ {
+ attData = new AttributeData();
+ atts.put(attName, attData);
+ }
+ attData.addDescription(description);
}
}
diff --git a/src/jalview/gui/Desktop.java b/src/jalview/gui/Desktop.java
index 2d1ba12..128481c 100644
--- a/src/jalview/gui/Desktop.java
+++ b/src/jalview/gui/Desktop.java
@@ -2338,7 +2338,7 @@ public class Desktop extends jalview.jbgui.GDesktop
{
String link = li.next();
if (link.contains(SEQUENCE_ID)
- && !link.equals(UrlConstants.DEFAULT_STRING))
+ && !UrlConstants.isDefaultString(link))
{
check = true;
int barPos = link.indexOf("|");
diff --git a/src/jalview/gui/FeatureColourChooser.java b/src/jalview/gui/FeatureColourChooser.java
index 89b64a7..3fc3116 100644
--- a/src/jalview/gui/FeatureColourChooser.java
+++ b/src/jalview/gui/FeatureColourChooser.java
@@ -22,27 +22,35 @@ package jalview.gui;
import jalview.api.FeatureColourI;
import jalview.datamodel.GraphLine;
+import jalview.datamodel.features.FeatureAttributes;
import jalview.schemes.FeatureColour;
import jalview.util.MessageManager;
-import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
-import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.List;
import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JLabel;
+import javax.swing.JMenuItem;
import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.border.LineBorder;
@@ -51,7 +59,8 @@ import javax.swing.event.ChangeListener;
public class FeatureColourChooser extends JalviewDialog
{
- // FeatureSettings fs;
+ private static final int MAX_TOOLTIP_LENGTH = 50;
+
private FeatureRenderer fr;
private FeatureColourI cs;
@@ -62,11 +71,11 @@ public class FeatureColourChooser extends JalviewDialog
private boolean adjusting = false;
- final private float min;
+ private float min;
- final private float max;
+ private float max;
- final private float scaleFactor;
+ private float scaleFactor;
private String type = null;
@@ -74,27 +83,50 @@ public class FeatureColourChooser extends JalviewDialog
private JPanel maxColour = new JPanel();
+ private JPanel noColour = new JPanel();
+
private JComboBox threshold = new JComboBox<>();
private JSlider slider = new JSlider();
private JTextField thresholdValue = new JTextField(20);
- // TODO implement GUI for tolower flag
- // JCheckBox toLower = new JCheckBox();
-
private JCheckBox thresholdIsMin = new JCheckBox();
- private JCheckBox colourByLabel = new JCheckBox();
-
private GraphLine threshline;
private Color oldmaxColour;
private Color oldminColour;
+ private Color oldNoColour;
+
private ActionListener colourEditor = null;
+ /*
+ * radio buttons to select what to colour by
+ * label, attribute text, score, attribute value
+ */
+ private JRadioButton byDescription = new JRadioButton();
+
+ private JRadioButton byAttributeText = new JRadioButton();
+
+ private JRadioButton byScore = new JRadioButton();
+
+ private JRadioButton byAttributeValue = new JRadioButton();
+
+ private ActionListener changeColourAction;
+
+ /*
+ * choice of attribute (if any) for 'colour by text'
+ */
+ private JComboBox textAttributeCombo;
+
+ /*
+ * choice of attribute (if any) for 'colour by value'
+ */
+ private JComboBox valueAttributeCombo;
+
/**
* Constructor
*
@@ -123,7 +155,7 @@ public class FeatureColourChooser extends JalviewDialog
String title = MessageManager
.formatMessage("label.graduated_color_for_params", new String[]
{ theType });
- initDialogFrame(this, true, blocking, title, 480, 185);
+ initDialogFrame(this, true, blocking, title, 450, 300);
slider.addChangeListener(new ChangeListener()
{
@@ -179,7 +211,10 @@ public class FeatureColourChooser extends JalviewDialog
}
else
{
- // promote original color to a graduated color
+ /*
+ * promote original simple color to a graduated color
+ * - by score if there is a score range, else by label
+ */
Color bl = oldcs.getColour();
if (bl == null)
{
@@ -187,10 +222,11 @@ public class FeatureColourChooser extends JalviewDialog
}
// original colour becomes the maximum colour
cs = new FeatureColour(Color.white, bl, mm[0], mm[1]);
- cs.setColourByLabel(false);
+ cs.setColourByLabel(mm[0] == mm[1]);
}
minColour.setBackground(oldminColour = cs.getMinColour());
maxColour.setBackground(oldmaxColour = cs.getMaxColour());
+ noColour.setBackground(oldNoColour = cs.getNoColour());
adjusting = true;
try
@@ -198,10 +234,46 @@ public class FeatureColourChooser extends JalviewDialog
jbInit();
} catch (Exception ex)
{
+ ex.printStackTrace();
+ return;
}
- // update the gui from threshold state
+
+ /*
+ * set the initial state of options on screen
+ */
thresholdIsMin.setSelected(!cs.isAutoScaled());
- colourByLabel.setSelected(cs.isColourByLabel());
+
+ if (cs.isColourByLabel())
+ {
+ if (cs.isColourByAttribute())
+ {
+ byAttributeText.setSelected(true);
+ textAttributeCombo.setEnabled(true);
+ textAttributeCombo.setSelectedItem(cs.getAttributeName());
+ }
+ else
+ {
+ byDescription.setSelected(true);
+ textAttributeCombo.setEnabled(false);
+ }
+ }
+ else
+ {
+ if (cs.isColourByAttribute())
+ {
+ byAttributeValue.setSelected(true);
+ String attributeName = cs.getAttributeName();
+ valueAttributeCombo.setSelectedItem(attributeName);
+ valueAttributeCombo.setEnabled(true);
+ setAttributeMinMax(attributeName);
+ }
+ else
+ {
+ byScore.setSelected(true);
+ valueAttributeCombo.setEnabled(false);
+ }
+ }
+
if (cs.hasThreshold())
{
// initialise threshold slider and selector
@@ -220,39 +292,193 @@ public class FeatureColourChooser extends JalviewDialog
waitForInput();
}
- private void jbInit() throws Exception
+ /**
+ * Configures the initial layout
+ */
+ private void jbInit()
{
- this.setLayout(new GridLayout(4, 1));
+ this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+ this.setBackground(Color.white);
+
+ changeColourAction = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ changeColour(true);
+ }
+ };
+
+ /*
+ * this panel
+ * detailsPanel
+ * colourByTextPanel
+ * colourByScorePanel
+ * okCancelPanel
+ */
+ JPanel detailsPanel = new JPanel();
+ detailsPanel.setLayout(new BoxLayout(detailsPanel, BoxLayout.Y_AXIS));
- JPanel colourByPanel = initColoursPanel();
+ JPanel colourByTextPanel = initColourByTextPanel();
+ detailsPanel.add(colourByTextPanel);
- JPanel thresholdPanel = initThresholdPanel();
+ JPanel colourByValuePanel = initColourByValuePanel();
+ detailsPanel.add(colourByValuePanel);
- JPanel okCancelPanel = initOkCancelPanel();
+ /*
+ * 4 radio buttons select between colour by description, by
+ * attribute text, by score, or by attribute value
+ */
+ ButtonGroup bg = new ButtonGroup();
+ bg.add(byDescription);
+ bg.add(byAttributeText);
+ bg.add(byScore);
+ bg.add(byAttributeValue);
- this.add(colourByPanel);
- this.add(thresholdPanel);
+ JPanel okCancelPanel = initOkCancelPanel();
+ this.add(detailsPanel);
this.add(okCancelPanel);
}
/**
- * Lay out fields for threshold options
+ * Lay out fields for graduated colour by value
*
* @return
*/
- protected JPanel initThresholdPanel()
+ protected JPanel initColourByValuePanel()
{
- JPanel thresholdPanel = new JPanel();
- thresholdPanel.setLayout(new FlowLayout());
- threshold.addActionListener(new ActionListener()
+ JPanel byValuePanel = new JPanel();
+ byValuePanel.setLayout(new BoxLayout(byValuePanel, BoxLayout.Y_AXIS));
+ byValuePanel.setBorder(BorderFactory.createTitledBorder(MessageManager
+ .getString("label.colour_by_value")));
+ byValuePanel.setBackground(Color.white);
+
+ /*
+ * first row - choose colour by score or by attribute, choose attribute
+ */
+ JPanel byWhatPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ byWhatPanel.setBackground(Color.white);
+ byValuePanel.add(byWhatPanel);
+
+ byScore.setText(MessageManager.getString("label.score"));
+ byWhatPanel.add(byScore);
+ byScore.addActionListener(changeColourAction);
+
+ byAttributeValue.setText(MessageManager
+.getString("label.attribute"));
+ byAttributeValue.addActionListener(changeColourAction);
+ byWhatPanel.add(byAttributeValue);
+
+ List attNames = FeatureAttributes.getInstance().getAttributes(
+ type);
+ valueAttributeCombo = populateAttributesDropdown(type, attNames,
+ true);
+
+ /*
+ * if no numeric atttibutes found, disable colour by attribute value
+ */
+ if (valueAttributeCombo.getItemCount() == 0)
+ {
+ byAttributeValue.setEnabled(false);
+ }
+
+ byWhatPanel.add(valueAttributeCombo);
+
+ /*
+ * second row - min/max/no colours
+ */
+ JPanel colourRangePanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ colourRangePanel.setBackground(Color.white);
+ byValuePanel.add(colourRangePanel);
+
+ minColour.setFont(JvSwingUtils.getLabelFont());
+ minColour.setBorder(BorderFactory.createLineBorder(Color.black));
+ minColour.setPreferredSize(new Dimension(40, 20));
+ minColour.setToolTipText(MessageManager.getString("label.min_colour"));
+ minColour.addMouseListener(new MouseAdapter()
{
@Override
- public void actionPerformed(ActionEvent e)
+ public void mousePressed(MouseEvent e)
{
- changeColour(true);
+ if (minColour.isEnabled())
+ {
+ minColour_actionPerformed();
+ }
+ }
+ });
+
+ maxColour.setFont(JvSwingUtils.getLabelFont());
+ maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
+ maxColour.setPreferredSize(new Dimension(40, 20));
+ maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
+ maxColour.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mousePressed(MouseEvent e)
+ {
+ if (maxColour.isEnabled())
+ {
+ maxColour_actionPerformed();
+ }
+ }
+ });
+ maxColour.setBorder(new LineBorder(Color.black));
+
+ noColour.setFont(JvSwingUtils.getLabelFont());
+ noColour.setBorder(BorderFactory.createLineBorder(Color.black));
+ noColour.setPreferredSize(new Dimension(40, 20));
+ noColour.setToolTipText("Colour if feature has no attribute value");
+ noColour.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mousePressed(MouseEvent e)
+ {
+ if (e.isPopupTrigger()) // Mac: mouseReleased
+ {
+ showNoColourPopup(e);
+ return;
+ }
+ if (noColour.isEnabled())
+ {
+ noColour_actionPerformed();
+ }
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e)
+ {
+ if (e.isPopupTrigger()) // Windows: mouseReleased
+ {
+ showNoColourPopup(e);
+ e.consume();
+ return;
+ }
}
});
+ noColour.setBorder(new LineBorder(Color.black));
+
+ JLabel minText = new JLabel(MessageManager.getString("label.min"));
+ minText.setFont(JvSwingUtils.getLabelFont());
+ JLabel maxText = new JLabel(MessageManager.getString("label.max"));
+ maxText.setFont(JvSwingUtils.getLabelFont());
+ JLabel noText = new JLabel(MessageManager.getString("label.no_colour"));
+ noText.setFont(JvSwingUtils.getLabelFont());
+
+ colourRangePanel.add(minText);
+ colourRangePanel.add(minColour);
+ colourRangePanel.add(maxText);
+ colourRangePanel.add(maxColour);
+ colourRangePanel.add(noText);
+ colourRangePanel.add(noColour);
+
+ /*
+ * third row - threshold options and value
+ */
+ JPanel thresholdPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ thresholdPanel.setBackground(Color.white);
+ byValuePanel.add(thresholdPanel);
+
+ threshold.addActionListener(changeColourAction);
threshold.setToolTipText(MessageManager
.getString("label.threshold_feature_display_by_score"));
threshold.addItem(MessageManager
@@ -288,25 +514,65 @@ public class FeatureColourChooser extends JalviewDialog
MessageManager.getString("label.adjust_threshold"));
thresholdValue.setEnabled(false);
thresholdValue.setColumns(7);
- thresholdPanel.setBackground(Color.white);
+
+ thresholdPanel.add(threshold);
+ thresholdPanel.add(slider);
+ thresholdPanel.add(thresholdValue);
+
+ /*
+ * 4th row - threshold is min / max
+ */
+ JPanel isMinMaxPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ isMinMaxPanel.setBackground(Color.white);
+ byValuePanel.add(isMinMaxPanel);
thresholdIsMin.setBackground(Color.white);
thresholdIsMin
.setText(MessageManager.getString("label.threshold_minmax"));
thresholdIsMin.setToolTipText(MessageManager
.getString("label.toggle_absolute_relative_display_threshold"));
- thresholdIsMin.addActionListener(new ActionListener()
+ thresholdIsMin.addActionListener(changeColourAction);
+ isMinMaxPanel.add(thresholdIsMin);
+
+ return byValuePanel;
+ }
+
+ /**
+ * Show a popup menu with options to make 'no value colour' the same as Min
+ * Colour or Max Colour
+ *
+ * @param evt
+ */
+ protected void showNoColourPopup(MouseEvent evt)
+ {
+ JPopupMenu pop = new JPopupMenu();
+
+ JMenuItem copyMin = new JMenuItem(
+ MessageManager.getString("label.min_colour"));
+ copyMin.addActionListener((new ActionListener()
+ {
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ noColour.setBackground(minColour.getBackground());
+ changeColour(true);
+ }
+ }));
+ pop.add(copyMin);
+
+ JMenuItem copyMax = new JMenuItem(
+ MessageManager.getString("label.max_colour"));
+ copyMax.addActionListener((new ActionListener()
{
@Override
- public void actionPerformed(ActionEvent actionEvent)
+ public void actionPerformed(ActionEvent e)
{
+ noColour.setBackground(maxColour.getBackground());
changeColour(true);
}
- });
- thresholdPanel.add(threshold);
- thresholdPanel.add(slider);
- thresholdPanel.add(thresholdValue);
- thresholdPanel.add(thresholdIsMin);
- return thresholdPanel;
+ }));
+ pop.add(copyMax);
+
+ pop.show(noColour, evt.getX(), evt.getY());
}
/**
@@ -324,76 +590,41 @@ public class FeatureColourChooser extends JalviewDialog
}
/**
- * Lay out Colour by Label and min/max colour widgets
+ * Lay out Colour by Label and attribute choice elements
*
* @return
*/
- protected JPanel initColoursPanel()
+ protected JPanel initColourByTextPanel()
{
- JPanel colourByPanel = new JPanel();
- colourByPanel.setLayout(new FlowLayout());
- colourByPanel.setBackground(Color.white);
- minColour.setFont(JvSwingUtils.getLabelFont());
- minColour.setBorder(BorderFactory.createLineBorder(Color.black));
- minColour.setPreferredSize(new Dimension(40, 20));
- minColour.setToolTipText(MessageManager.getString("label.min_colour"));
- minColour.addMouseListener(new MouseAdapter()
- {
- @Override
- public void mousePressed(MouseEvent e)
- {
- if (minColour.isEnabled())
- {
- minColour_actionPerformed();
- }
- }
- });
- maxColour.setFont(JvSwingUtils.getLabelFont());
- maxColour.setBorder(BorderFactory.createLineBorder(Color.black));
- maxColour.setPreferredSize(new Dimension(40, 20));
- maxColour.setToolTipText(MessageManager.getString("label.max_colour"));
- maxColour.addMouseListener(new MouseAdapter()
- {
- @Override
- public void mousePressed(MouseEvent e)
- {
- if (maxColour.isEnabled())
- {
- maxColour_actionPerformed();
- }
- }
- });
- maxColour.setBorder(new LineBorder(Color.black));
- JLabel minText = new JLabel(MessageManager.getString("label.min"));
- minText.setFont(JvSwingUtils.getLabelFont());
- JLabel maxText = new JLabel(MessageManager.getString("label.max"));
- maxText.setFont(JvSwingUtils.getLabelFont());
+ JPanel byTextPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ byTextPanel.setBackground(Color.white);
+ byTextPanel.setBorder(BorderFactory.createTitledBorder(MessageManager
+ .getString("label.colour_by_text")));
+
+ byDescription.setText(MessageManager.getString("label.label"));
+ byDescription.setToolTipText(MessageManager
+ .getString("label.colour_by_label_tip"));
+ byDescription.addActionListener(changeColourAction);
+ byTextPanel.add(byDescription);
+
+ byAttributeText.setText(MessageManager.getString("label.attribute"));
+ byAttributeText.addActionListener(changeColourAction);
+ byTextPanel.add(byAttributeText);
+
+ List attNames = FeatureAttributes.getInstance().getAttributes(
+ type);
+ textAttributeCombo = populateAttributesDropdown(type, attNames, false);
+ byTextPanel.add(textAttributeCombo);
- JPanel colourPanel = new JPanel();
- colourPanel.setBackground(Color.white);
- colourPanel.add(minText);
- colourPanel.add(minColour);
- colourPanel.add(maxText);
- colourPanel.add(maxColour);
- colourByPanel.add(colourByLabel, BorderLayout.WEST);
- colourByPanel.add(colourPanel, BorderLayout.EAST);
-
- colourByLabel.setBackground(Color.white);
- colourByLabel
- .setText(MessageManager.getString("label.colour_by_label"));
- colourByLabel
- .setToolTipText(MessageManager
- .getString("label.display_features_same_type_different_label_using_different_colour"));
- colourByLabel.addActionListener(new ActionListener()
+ /*
+ * disable colour by attribute if no attributes
+ */
+ if (attNames.isEmpty())
{
- @Override
- public void actionPerformed(ActionEvent actionEvent)
- {
- changeColour(true);
- }
- });
+ byAttributeText.setEnabled(false);
+ }
- return colourByPanel;
+ return byTextPanel;
}
/**
@@ -433,6 +664,24 @@ public class FeatureColourChooser extends JalviewDialog
}
/**
+ * Action on clicking the 'no colour' - open a colour chooser dialog, and set
+ * the selected colour (if the user does not cancel out of the dialog)
+ */
+ protected void noColour_actionPerformed()
+ {
+ Color col = JColorChooser.showDialog(this,
+ MessageManager.getString("label.select_no_value_colour"),
+ noColour.getBackground());
+ if (col != null)
+ {
+ noColour.setBackground(col);
+ noColour.setForeground(col);
+ }
+ noColour.repaint();
+ changeColour(true);
+ }
+
+ /**
* Constructs and sets the selected colour options as the colour for the
* feature type, and repaints the alignment, and optionally the Overview
* and/or structure viewer if open
@@ -462,6 +711,9 @@ public class FeatureColourChooser extends JalviewDialog
slider.setEnabled(true);
thresholdValue.setEnabled(true);
+ /*
+ * make the feature colour
+ */
FeatureColourI acg;
if (cs.isColourByLabel())
{
@@ -470,8 +722,23 @@ public class FeatureColourChooser extends JalviewDialog
else
{
acg = new FeatureColour(oldminColour = minColour.getBackground(),
- oldmaxColour = maxColour.getBackground(), min, max);
+ oldmaxColour = maxColour.getBackground(),
+ oldNoColour = noColour.getBackground(), min, max);
+ }
+ String attribute = null;
+ textAttributeCombo.setEnabled(false);
+ valueAttributeCombo.setEnabled(false);
+ if (byAttributeText.isSelected())
+ {
+ attribute = (String) textAttributeCombo.getSelectedItem();
+ textAttributeCombo.setEnabled(true);
+ }
+ else if (byAttributeValue.isSelected())
+ {
+ attribute = (String) valueAttributeCombo.getSelectedItem();
+ valueAttributeCombo.setEnabled(true);
}
+ acg.setAttributeName(attribute);
if (!hasThreshold)
{
@@ -504,7 +771,7 @@ public class FeatureColourChooser extends JalviewDialog
slider.setMajorTickSpacing((int) (range / 10f));
slider.setEnabled(true);
thresholdValue.setEnabled(true);
- thresholdIsMin.setEnabled(!colourByLabel.isSelected());
+ thresholdIsMin.setEnabled(!byDescription.isSelected());
adjusting = false;
}
@@ -526,27 +793,37 @@ public class FeatureColourChooser extends JalviewDialog
{
acg.setAutoScaled(true);
}
- acg.setColourByLabel(colourByLabel.isSelected());
+ acg.setColourByLabel(byDescription.isSelected()
+ || byAttributeText.isSelected());
+
if (acg.isColourByLabel())
{
maxColour.setEnabled(false);
minColour.setEnabled(false);
+ noColour.setEnabled(false);
maxColour.setBackground(this.getBackground());
maxColour.setForeground(this.getBackground());
minColour.setBackground(this.getBackground());
minColour.setForeground(this.getBackground());
-
+ noColour.setBackground(this.getBackground());
+ noColour.setForeground(this.getBackground());
}
else
{
maxColour.setEnabled(true);
minColour.setEnabled(true);
+ noColour.setEnabled(true);
maxColour.setBackground(oldmaxColour);
- minColour.setBackground(oldminColour);
maxColour.setForeground(oldmaxColour);
+ minColour.setBackground(oldminColour);
minColour.setForeground(oldminColour);
+ noColour.setBackground(oldNoColour);
+ noColour.setForeground(oldNoColour);
}
+ /*
+ * save the colour, and repaint stuff
+ */
fr.setColour(type, acg);
cs = acg;
ap.paintAlignment(updateStructsAndOverview, updateStructsAndOverview);
@@ -653,4 +930,82 @@ public class FeatureColourChooser extends JalviewDialog
return cs;
}
+ /**
+ * A helper method to build the drop-down choice of attributes for a feature.
+ * Where metadata is available with a description for an attribute, that is
+ * added as a tooltip. The list may be restricted to attributes for which we
+ * hold a range of numerical values (so suitable candidates for a graduated
+ * colour scheme).
+ *
+ * @param featureType
+ * @param attNames
+ * @param withNumericRange
+ */
+ protected JComboBox populateAttributesDropdown(
+ String featureType, List attNames,
+ boolean withNumericRange)
+ {
+ List validAtts = new ArrayList<>();
+ List tooltips = new ArrayList<>();
+
+ FeatureAttributes fa = FeatureAttributes.getInstance();
+ for (String attName : attNames)
+ {
+ if (withNumericRange)
+ {
+ float[] minMax = fa.getMinMax(featureType, attName);
+ if (minMax == null)
+ {
+ continue;
+ }
+ }
+ validAtts.add(attName);
+ String desc = fa.getDescription(featureType, attName);
+ if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
+ {
+ desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "...";
+ }
+ tooltips.add(desc == null ? "" : desc);
+ }
+
+ JComboBox attCombo = JvSwingUtils.buildComboWithTooltips(
+ validAtts, tooltips);
+
+ attCombo.addItemListener(new ItemListener()
+ {
+ @Override
+ public void itemStateChanged(ItemEvent e)
+ {
+ setAttributeMinMax(attCombo.getSelectedItem().toString());
+ changeColour(true);
+ }
+ });
+
+ if (validAtts.isEmpty())
+ {
+ attCombo.setToolTipText(MessageManager
+ .getString(withNumericRange ? "label.no_numeric_attributes"
+ : "label.no_attributes"));
+ }
+
+ return attCombo;
+ }
+
+ /**
+ * Updates the min-max range and scale to be that for the given attribute name
+ *
+ * @param attributeName
+ */
+ protected void setAttributeMinMax(String attributeName)
+ {
+ float[] minMax = FeatureAttributes.getInstance().getMinMax(type,
+ attributeName);
+ if (minMax != null)
+ {
+ min = minMax[0];
+ max = minMax[1];
+ scaleFactor = (max == min) ? 1f : 100f / (max - min);
+ }
+ }
+
}
diff --git a/src/jalview/gui/FeatureSettings.java b/src/jalview/gui/FeatureSettings.java
index d724b8c..3be597c 100644
--- a/src/jalview/gui/FeatureSettings.java
+++ b/src/jalview/gui/FeatureSettings.java
@@ -84,6 +84,7 @@ import java.util.Vector;
import javax.help.HelpSetException;
import javax.swing.AbstractCellEditor;
import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.JButton;
@@ -122,6 +123,8 @@ public class FeatureSettings extends JPanel
private static final int MIN_HEIGHT = 400;
+ private static final int MAX_TOOLTIP_LENGTH = 50;
+
DasSourceBrowser dassourceBrowser;
DasSequenceFeatureFetcher dasFeatureFetcher;
@@ -414,84 +417,63 @@ public class FeatureSettings extends JPanel
});
men.add(dens);
- if (minmax != null)
+
+ /*
+ * variable colour options include colour by label, by score,
+ * by selected attribute text, or attribute value
+ */
+ final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
+ MessageManager.getString("label.variable_colour"));
+ mxcol.setSelected(!featureColour.isSimpleColour());
+ men.add(mxcol);
+ mxcol.addActionListener(new ActionListener()
{
- final float[][] typeMinMax = minmax.get(type);
- /*
- * final JCheckBoxMenuItem chb = new JCheckBoxMenuItem("Vary Height"); //
- * this is broken at the moment and isn't that useful anyway!
- * chb.setSelected(minmax.get(type) != null); chb.addActionListener(new
- * ActionListener() {
- *
- * public void actionPerformed(ActionEvent e) {
- * chb.setState(chb.getState()); if (chb.getState()) { minmax.put(type,
- * null); } else { minmax.put(type, typeMinMax); } }
- *
- * });
- *
- * men.add(chb);
- */
- if (typeMinMax != null && typeMinMax[0] != null)
- {
- // if (table.getValueAt(row, column));
- // graduated colourschemes for those where minmax exists for the
- // positional features
- final JCheckBoxMenuItem mxcol = new JCheckBoxMenuItem(
- "Graduated Colour");
- mxcol.setSelected(!featureColour.isSimpleColour());
- men.add(mxcol);
- mxcol.addActionListener(new ActionListener()
- {
- JColorChooser colorChooser;
+ JColorChooser colorChooser;
- @Override
- public void actionPerformed(ActionEvent e)
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ if (e.getSource() == mxcol)
+ {
+ if (featureColour.isSimpleColour())
{
- if (e.getSource() == mxcol)
- {
- if (featureColour.isSimpleColour())
- {
- FeatureColourChooser fc = new FeatureColourChooser(me.fr,
- type);
- fc.addActionListener(this);
- }
- else
- {
- // bring up simple color chooser
- colorChooser = new JColorChooser();
- JDialog dialog = JColorChooser.createDialog(me,
- "Select new Colour", true, // modal
- colorChooser, this, // OK button handler
- null); // no CANCEL button handler
- colorChooser.setColor(featureColour.getMaxColour());
- dialog.setVisible(true);
- }
- }
- else
- {
- if (e.getSource() instanceof FeatureColourChooser)
- {
- FeatureColourChooser fc = (FeatureColourChooser) e
- .getSource();
- table.setValueAt(fc.getLastColour(), selectedRow, 1);
- table.validate();
- }
- else
- {
- // probably the color chooser!
- table.setValueAt(new FeatureColour(colorChooser.getColor()),
- selectedRow, 1);
- table.validate();
- me.updateFeatureRenderer(
- ((FeatureTableModel) table.getModel()).getData(),
- false);
- }
- }
+ FeatureColourChooser fc = new FeatureColourChooser(me.fr, type);
+ fc.addActionListener(this);
}
-
- });
+ else
+ {
+ // bring up simple color chooser
+ colorChooser = new JColorChooser();
+ JDialog dialog = JColorChooser.createDialog(me,
+ "Select new Colour", true, // modal
+ colorChooser, this, // OK button handler
+ null); // no CANCEL button handler
+ colorChooser.setColor(featureColour.getMaxColour());
+ dialog.setVisible(true);
+ }
+ }
+ else
+ {
+ if (e.getSource() instanceof FeatureColourChooser)
+ {
+ FeatureColourChooser fc = (FeatureColourChooser) e.getSource();
+ table.setValueAt(fc.getLastColour(), selectedRow, 1);
+ table.validate();
+ }
+ else
+ {
+ // probably the color chooser!
+ table.setValueAt(new FeatureColour(colorChooser.getColor()),
+ selectedRow, 1);
+ table.validate();
+ me.updateFeatureRenderer(
+ ((FeatureTableModel) table.getModel()).getData(), false);
+ }
+ }
}
- }
+
+ });
+
JMenuItem selCols = new JMenuItem(
MessageManager.getString("label.select_columns_containing"));
selCols.addActionListener(new ActionListener()
@@ -1379,7 +1361,8 @@ public class FeatureSettings extends JPanel
/*
* the panel with the filters for the selected feature type
*/
- JPanel filtersPanel = new JPanel(new GridLayout(0, 1));
+ JPanel filtersPanel = new JPanel();
+ filtersPanel.setLayout(new BoxLayout(filtersPanel, BoxLayout.Y_AXIS));
filtersPanel.setBackground(Color.white);
filtersPanel.setBorder(BorderFactory
.createTitledBorder(MessageManager.getString("label.filters")));
@@ -1414,7 +1397,9 @@ public class FeatureSettings extends JPanel
/*
* panel with filters - populated by refreshFiltersDisplay
*/
- chooseFiltersPanel = new JPanel(new GridLayout(0, 1));
+ chooseFiltersPanel = new JPanel();
+ chooseFiltersPanel.setLayout(new BoxLayout(chooseFiltersPanel,
+ BoxLayout.Y_AXIS));
filtersPanel.add(chooseFiltersPanel);
/*
@@ -1471,7 +1456,7 @@ public class FeatureSettings extends JPanel
}
if (!found)
{
- filteredFeatureChoice
+ filteredFeatureChoice // todo i18n
.addItem("No filterable feature attributes known");
}
@@ -1491,14 +1476,12 @@ public class FeatureSettings extends JPanel
* clear the panel and list of filter conditions
*/
chooseFiltersPanel.removeAll();
-
- String selectedType = (String) filteredFeatureChoice.getSelectedItem();
-
filters.clear();
/*
* look up attributes known for feature type
*/
+ String selectedType = (String) filteredFeatureChoice.getSelectedItem();
List attNames = FeatureAttributes.getInstance().getAttributes(
selectedType);
@@ -1514,11 +1497,7 @@ public class FeatureSettings extends JPanel
{
orFilters.setSelected(true);
}
- Iterator matchers = featureFilters.getMatchers();
- while (matchers.hasNext())
- {
- filters.add(matchers.next());
- }
+ featureFilters.getMatchers().forEach(matcher -> filters.add(matcher));
}
/*
@@ -1530,16 +1509,16 @@ public class FeatureSettings extends JPanel
/*
* render the conditions in rows, each in its own JPanel
*/
- int i = 0;
+ int filterIndex = 0;
for (KeyedMatcherI filter : filters)
{
String key = filter.getKey();
Condition condition = filter.getMatcher()
.getCondition();
String pattern = filter.getMatcher().getPattern();
- JPanel row = addFilter(key, attNames, condition, pattern, i);
+ JPanel row = addFilter(key, attNames, condition, pattern, filterIndex);
chooseFiltersPanel.add(row);
- i++;
+ filterIndex++;
}
filtersPane.validate();
@@ -1562,11 +1541,11 @@ public class FeatureSettings extends JPanel
* @param attNames
* @param cond
* @param pattern
- * @param i
+ * @param filterIndex
* @return
*/
protected JPanel addFilter(String attribute, List attNames,
- Condition cond, String pattern, int i)
+ Condition cond, String pattern, int filterIndex)
{
JPanel filterRow = new JPanel(new FlowLayout(FlowLayout.LEFT));
filterRow.setBackground(Color.white);
@@ -1574,7 +1553,13 @@ public class FeatureSettings extends JPanel
/*
* inputs for attribute, condition, pattern
*/
- JComboBox attCombo = new JComboBox<>();
+ /*
+ * drop-down choice of attribute, with description as a tooltip
+ * if we can obtain it
+ */
+ String featureType = (String) filteredFeatureChoice.getSelectedItem();
+ final JComboBox attCombo = populateAttributesDropdown(
+ featureType, attNames);
JComboBox condCombo = new JComboBox<>();
JTextField patternField = new JTextField(8);
@@ -1590,7 +1575,7 @@ public class FeatureSettings extends JPanel
{
if (validateFilter(patternField, condCombo))
{
- updateFilter(attCombo, condCombo, patternField, i);
+ updateFilter(attCombo, condCombo, patternField, filterIndex);
filtersChanged();
}
}
@@ -1605,32 +1590,16 @@ public class FeatureSettings extends JPanel
}
};
- /*
- * drop-down choice of attribute
- */
- if (attNames.isEmpty())
+ if ("".equals(attribute))
{
- attCombo.addItem("---");
- attCombo.setToolTipText(MessageManager
- .getString("label.no_attributes_known"));
+ attCombo.setSelectedItem(null);
}
else
{
- attCombo.setToolTipText("");
- for (String attName : attNames)
- {
- attCombo.addItem(attName);
- }
- if ("".equals(attribute))
- {
- attCombo.setSelectedItem(null);
- }
- else
- {
- attCombo.setSelectedItem(attribute);
- }
- attCombo.addItemListener(itemListener);
+ attCombo.setSelectedItem(attribute);
}
+ attCombo.addItemListener(itemListener);
+
filterRow.add(attCombo);
/*
@@ -1676,7 +1645,7 @@ public class FeatureSettings extends JPanel
@Override
public void actionPerformed(ActionEvent e)
{
- filters.remove(i);
+ filters.remove(filterIndex);
filtersChanged();
}
});
@@ -1687,6 +1656,39 @@ public class FeatureSettings extends JPanel
}
/**
+ * A helper method to build the drop-down choice of attributes for a feature.
+ * Where metadata is available with a description for an attribute, that is
+ * added as a tooltip.
+ *
+ * @param featureType
+ * @param attNames
+ */
+ protected JComboBox populateAttributesDropdown(
+ String featureType, List attNames)
+ {
+ List tooltips = new ArrayList<>();
+ FeatureAttributes fa = FeatureAttributes.getInstance();
+ for (String attName : attNames)
+ {
+ String desc = fa.getDescription(featureType, attName);
+ if (desc != null && desc.length() > MAX_TOOLTIP_LENGTH)
+ {
+ desc = desc.substring(0, MAX_TOOLTIP_LENGTH) + "...";
+ }
+ tooltips.add(desc == null ? "" : desc);
+ }
+
+ JComboBox attCombo = JvSwingUtils.buildComboWithTooltips(
+ attNames, tooltips);
+ if (attNames.isEmpty())
+ {
+ attCombo.setToolTipText(MessageManager
+ .getString("label.no_attributes"));
+ }
+ return attCombo;
+ }
+
+ /**
* Action on any change to feature filtering, namely
*
* - change of selected attribute
@@ -2060,12 +2062,7 @@ public class FeatureSettings extends JPanel
boolean isSelected, boolean hasFocus, int row, int column)
{
FeatureColourI cellColour = (FeatureColourI) color;
- // JLabel comp = new JLabel();
- // comp.
setOpaque(true);
- // comp.
- // setBounds(getBounds());
- Color newColor;
setToolTipText(baseTT);
setBackground(tbl.getBackground());
if (!cellColour.isSimpleColour())
@@ -2073,14 +2070,12 @@ public class FeatureSettings extends JPanel
Rectangle cr = tbl.getCellRect(row, column, false);
FeatureSettings.renderGraduatedColor(this, cellColour,
(int) cr.getWidth(), (int) cr.getHeight());
-
}
else
{
this.setText("");
this.setIcon(null);
- newColor = cellColour.getColour();
- setBackground(newColor);
+ setBackground(cellColour.getColour());
}
if (isSelected)
{
@@ -2131,28 +2126,43 @@ public class FeatureSettings extends JPanel
int w, int h)
{
boolean thr = false;
- String tt = "";
- String tx = "";
+ StringBuilder tt = new StringBuilder();
+ StringBuilder tx = new StringBuilder();
+
+ if (gcol.isColourByAttribute())
+ {
+ tx.append(gcol.getAttributeName());
+ }
+ else if (!gcol.isColourByLabel())
+ {
+ tx.append(MessageManager.getString("label.score"));
+ }
+ tx.append(" ");
if (gcol.isAboveThreshold())
{
thr = true;
- tx += ">";
- tt += "Thresholded (Above " + gcol.getThreshold() + ") ";
+ tx.append(">");
+ tt.append("Thresholded (Above ").append(gcol.getThreshold())
+ .append(") ");
}
if (gcol.isBelowThreshold())
{
thr = true;
- tx += "<";
- tt += "Thresholded (Below " + gcol.getThreshold() + ") ";
+ tx.append("<");
+ tt.append("Thresholded (Below ").append(gcol.getThreshold())
+ .append(") ");
}
if (gcol.isColourByLabel())
{
- tt = "Coloured by label text. " + tt;
+ tt.append("Coloured by label text. ").append(tt);
if (thr)
{
- tx += " ";
+ tx.append(" ");
+ }
+ if (!gcol.isColourByAttribute())
+ {
+ tx.append("Label");
}
- tx += "Label";
comp.setIcon(null);
}
else
@@ -2168,16 +2178,17 @@ public class FeatureSettings extends JPanel
// + ", " + minCol.getBlue() + ")");
}
comp.setHorizontalAlignment(SwingConstants.CENTER);
- comp.setText(tx);
+ comp.setText(tx.toString());
if (tt.length() > 0)
{
if (comp.getToolTipText() == null)
{
- comp.setToolTipText(tt);
+ comp.setToolTipText(tt.toString());
}
else
{
- comp.setToolTipText(tt + " " + comp.getToolTipText());
+ comp.setToolTipText(tt.append(" ").append(comp.getToolTipText())
+ .toString());
}
}
}
@@ -2299,7 +2310,8 @@ class ColorEditor extends AbstractCellEditor
button.setBorderPainted(false);
// Set up the dialog that the button brings up.
colorChooser = new JColorChooser();
- dialog = JColorChooser.createDialog(button, "Select new Colour", true, // modal
+ dialog = JColorChooser.createDialog(button,
+ MessageManager.getString("label.select_new_colour"), true, // modal
colorChooser, this, // OK button handler
null); // no CANCEL button handler
}
diff --git a/src/jalview/gui/IdPanel.java b/src/jalview/gui/IdPanel.java
index a4f79c2..a1726f1 100755
--- a/src/jalview/gui/IdPanel.java
+++ b/src/jalview/gui/IdPanel.java
@@ -108,8 +108,7 @@ public class IdPanel extends JPanel
SequenceI sequence = av.getAlignment().getSequenceAt(seq);
StringBuilder tip = new StringBuilder(64);
seqAnnotReport.createTooltipAnnotationReport(tip, sequence,
- av.isShowDBRefs(), av.isShowNPFeats(),
- sp.seqCanvas.fr.getMinMax());
+ av.isShowDBRefs(), av.isShowNPFeats(), sp.seqCanvas.fr);
setToolTipText(JvSwingUtils.wrapTooltip(true,
sequence.getDisplayId(true) + " " + tip.toString()));
}
diff --git a/src/jalview/gui/JvSwingUtils.java b/src/jalview/gui/JvSwingUtils.java
index 0a765cb..ef96fa6 100644
--- a/src/jalview/gui/JvSwingUtils.java
+++ b/src/jalview/gui/JvSwingUtils.java
@@ -24,14 +24,19 @@ import jalview.util.MessageManager;
import java.awt.BorderLayout;
import java.awt.Color;
+import java.awt.Component;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.List;
import java.util.Objects;
import javax.swing.AbstractButton;
import javax.swing.JButton;
+import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenu;
@@ -304,4 +309,46 @@ public final class JvSwingUtils
comp.setFont(JvSwingUtils.getLabelFont());
}
+ /**
+ * A helper method to build a drop-down choice of values, with tooltips for
+ * the entries
+ *
+ * @param entries
+ * @param tooltips
+ */
+ public static JComboBox buildComboWithTooltips(
+ List entries, List tooltips)
+ {
+ JComboBox combo = new JComboBox<>();
+ final ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer();
+ combo.setRenderer(renderer);
+ for (String attName : entries)
+ {
+ combo.addItem(attName);
+ }
+ renderer.setTooltips(tooltips);
+ final MouseAdapter mouseListener = new MouseAdapter()
+ {
+ @Override
+ public void mouseEntered(MouseEvent e)
+ {
+ int j = combo.getSelectedIndex();
+ if (j > -1)
+ {
+ combo.setToolTipText(tooltips.get(j));
+ }
+ }
+ @Override
+ public void mouseExited(MouseEvent e)
+ {
+ combo.setToolTipText(null);
+ }
+ };
+ for (Component c : combo.getComponents())
+ {
+ c.addMouseListener(mouseListener);
+ }
+ return combo;
+ }
+
}
diff --git a/src/jalview/gui/PopupMenu.java b/src/jalview/gui/PopupMenu.java
index 6da7d4f..97d051b 100644
--- a/src/jalview/gui/PopupMenu.java
+++ b/src/jalview/gui/PopupMenu.java
@@ -1635,10 +1635,7 @@ public class PopupMenu extends JPopupMenu implements ColourChangeListener
new Object[]
{ seq.getDisplayId(true) }) + "");
new SequenceAnnotationReport(null).createSequenceAnnotationReport(
- contents, seq, true, true,
- (ap.getSeqPanel().seqCanvas.fr != null)
- ? ap.getSeqPanel().seqCanvas.fr.getMinMax()
- : null);
+ contents, seq, true, true, ap.getSeqPanel().seqCanvas.fr);
contents.append("
");
}
cap.setText("" + contents.toString() + "");
diff --git a/src/jalview/gui/SeqPanel.java b/src/jalview/gui/SeqPanel.java
index 6148a2e..29f68c1 100644
--- a/src/jalview/gui/SeqPanel.java
+++ b/src/jalview/gui/SeqPanel.java
@@ -75,12 +75,11 @@ import javax.swing.ToolTipManager;
public class SeqPanel extends JPanel
implements MouseListener, MouseMotionListener, MouseWheelListener,
SequenceListener, SelectionListener
-
{
- /** DOCUMENT ME!! */
+ private static final int MAX_TOOLTIP_LENGTH = 300;
+
public SeqCanvas seqCanvas;
- /** DOCUMENT ME!! */
public AlignmentPanel ap;
/*
@@ -147,35 +146,33 @@ public class SeqPanel extends JPanel
SearchResultsI lastSearchResults;
/**
- * Creates a new SeqPanel object.
+ * Creates a new SeqPanel object
*
- * @param avp
- * DOCUMENT ME!
- * @param p
- * DOCUMENT ME!
+ * @param viewport
+ * @param alignPanel
*/
- public SeqPanel(AlignViewport av, AlignmentPanel ap)
+ public SeqPanel(AlignViewport viewport, AlignmentPanel alignPanel)
{
linkImageURL = getClass().getResource("/images/link.gif");
seqARep = new SequenceAnnotationReport(linkImageURL.toString());
ToolTipManager.sharedInstance().registerComponent(this);
ToolTipManager.sharedInstance().setInitialDelay(0);
ToolTipManager.sharedInstance().setDismissDelay(10000);
- this.av = av;
+ this.av = viewport;
setBackground(Color.white);
- seqCanvas = new SeqCanvas(ap);
+ seqCanvas = new SeqCanvas(alignPanel);
setLayout(new BorderLayout());
add(seqCanvas, BorderLayout.CENTER);
- this.ap = ap;
+ this.ap = alignPanel;
- if (!av.isDataset())
+ if (!viewport.isDataset())
{
addMouseMotionListener(this);
addMouseListener(this);
addMouseWheelListener(this);
- ssm = av.getStructureSelectionManager();
+ ssm = viewport.getStructureSelectionManager();
ssm.addStructureViewerListener(this);
ssm.addSelectionListener(this);
}
@@ -804,7 +801,7 @@ public class SeqPanel extends JPanel
List features = ap.getFeatureRenderer()
.findFeaturesAtColumn(sequence, column + 1);
seqARep.appendFeatures(tooltipText, pos, features,
- this.ap.getSeqPanel().seqCanvas.fr.getMinMax());
+ this.ap.getSeqPanel().seqCanvas.fr);
}
if (tooltipText.length() == 6) //
{
@@ -813,6 +810,11 @@ public class SeqPanel extends JPanel
}
else
{
+ if (tooltipText.length() > MAX_TOOLTIP_LENGTH) // constant
+ {
+ tooltipText.setLength(MAX_TOOLTIP_LENGTH);
+ tooltipText.append("...");
+ }
String textString = tooltipText.toString();
if (lastTooltip == null || !lastTooltip.equals(textString))
{
diff --git a/src/jalview/io/SequenceAnnotationReport.java b/src/jalview/io/SequenceAnnotationReport.java
index 6d819d3..1f92428 100644
--- a/src/jalview/io/SequenceAnnotationReport.java
+++ b/src/jalview/io/SequenceAnnotationReport.java
@@ -20,14 +20,15 @@
*/
package jalview.io;
+import jalview.api.FeatureColourI;
import jalview.datamodel.DBRefEntry;
import jalview.datamodel.DBRefSource;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
-import jalview.io.gff.GffConstants;
import jalview.util.MessageManager;
import jalview.util.StringUtils;
import jalview.util.UrlLink;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
import java.util.Arrays;
import java.util.Collection;
@@ -87,14 +88,14 @@ public class SequenceAnnotationReport
{
return 1;
}
- int comp = s1 == null ? -1
- : (s2 == null ? 1 : s1.compareToIgnoreCase(s2));
+ int comp = s1 == null ? -1 : (s2 == null ? 1 : s1
+ .compareToIgnoreCase(s2));
if (comp == 0)
{
String a1 = ref1.getAccessionId();
String a2 = ref2.getAccessionId();
- comp = a1 == null ? -1
- : (a2 == null ? 1 : a1.compareToIgnoreCase(a2));
+ comp = a1 == null ? -1 : (a2 == null ? 1 : a1
+ .compareToIgnoreCase(a2));
}
return comp;
}
@@ -115,9 +116,9 @@ public class SequenceAnnotationReport
}
};
- public SequenceAnnotationReport(String linkImageURL)
+ public SequenceAnnotationReport(String linkURL)
{
- this.linkImageURL = linkImageURL;
+ this.linkImageURL = linkURL;
}
/**
@@ -129,13 +130,13 @@ public class SequenceAnnotationReport
* @param minmax
*/
public void appendFeatures(final StringBuilder sb, int rpos,
- List features, Map minmax)
+ List features, FeatureRendererModel fr)
{
if (features != null)
{
for (SequenceFeature feature : features)
{
- appendFeature(sb, rpos, minmax, feature);
+ appendFeature(sb, rpos, fr, feature);
}
}
}
@@ -149,7 +150,7 @@ public class SequenceAnnotationReport
* @param feature
*/
void appendFeature(final StringBuilder sb, int rpos,
- Map minmax, SequenceFeature feature)
+ FeatureRendererModel fr, SequenceFeature feature)
{
if (feature.isContactFeature())
{
@@ -162,60 +163,91 @@ public class SequenceAnnotationReport
sb.append(feature.getType()).append(" ").append(feature.getBegin())
.append(":").append(feature.getEnd());
}
+ return;
}
- else
+
+ if (sb.length() > 6)
+ {
+ sb.append("
");
+ }
+ // TODO: remove this hack to display link only features
+ boolean linkOnly = feature.getValue("linkonly") != null;
+ if (!linkOnly)
{
- if (sb.length() > 6)
+ sb.append(feature.getType()).append(" ");
+ if (rpos != 0)
{
- sb.append("
");
+ // we are marking a positional feature
+ sb.append(feature.begin);
}
- // TODO: remove this hack to display link only features
- boolean linkOnly = feature.getValue("linkonly") != null;
- if (!linkOnly)
+ if (feature.begin != feature.end)
{
- sb.append(feature.getType()).append(" ");
- if (rpos != 0)
- {
- // we are marking a positional feature
- sb.append(feature.begin);
- }
- if (feature.begin != feature.end)
- {
- sb.append(" ").append(feature.end);
- }
+ sb.append(" ").append(feature.end);
+ }
- String description = feature.getDescription();
- if (description != null && !description.equals(feature.getType()))
- {
- description = StringUtils.stripHtmlTags(description);
- sb.append("; ").append(description);
- }
- // check score should be shown
- if (!Float.isNaN(feature.getScore()))
+ String description = feature.getDescription();
+ if (description != null && !description.equals(feature.getType()))
+ {
+ description = StringUtils.stripHtmlTags(description);
+ sb.append("; ").append(description);
+ }
+
+ if (showScore(feature, fr))
+ {
+ sb.append(" Score=").append(String.valueOf(feature.getScore()));
+ }
+ String status = (String) feature.getValue("status");
+ if (status != null && status.length() > 0)
+ {
+ sb.append("; (").append(status).append(")");
+ }
+
+ /*
+ * add attribute value if coloured by attribute
+ */
+ if (fr != null)
+ {
+ FeatureColourI fc = fr.getFeatureColours().get(feature.getType());
+ if (fc != null && fc.isColourByAttribute())
{
- float[][] rng = (minmax == null) ? null
- : minmax.get(feature.getType());
- if (rng != null && rng[0] != null && rng[0][0] != rng[0][1])
+ String attName = fc.getAttributeName();
+ String attVal = feature.getValueAsString(attName);
+ if (attVal != null)
{
- sb.append(" Score=").append(String.valueOf(feature.getScore()));
+ sb.append("; ").append(attName).append("=").append(attVal);
}
}
- String status = (String) feature.getValue("status");
- if (status != null && status.length() > 0)
- {
- sb.append("; (").append(status).append(")");
- }
- String clinSig = (String) feature
- .getValue(GffConstants.CLINICAL_SIGNIFICANCE);
- if (clinSig != null)
- {
- sb.append("; ").append(clinSig);
- }
}
}
}
/**
+ * Answers true if score should be shown, else false. Score is shown if it is
+ * not NaN, and the feature type has a non-trivial min-max score range
+ */
+ boolean showScore(SequenceFeature feature, FeatureRendererModel fr)
+ {
+ if (Float.isNaN(feature.getScore()))
+ {
+ return false;
+ }
+ if (fr == null)
+ {
+ return true;
+ }
+ float[][] minMax = fr.getMinMax().get(feature.getType());
+
+ /*
+ * minMax[0] is the [min, max] score range for positional features
+ */
+ if (minMax == null || minMax[0] == null || minMax[0][0] == minMax[0][1])
+ {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Format and appends any hyperlinks for the sequence feature to the string
* buffer
*
@@ -238,19 +270,20 @@ public class SequenceAnnotationReport
{
for (List urllink : createLinksFrom(null, urlstring))
{
- sb.append("
"
+ sb.append("
"
+ (urllink.get(0).toLowerCase()
- .equals(urllink.get(1).toLowerCase())
- ? urllink.get(0)
- : (urllink.get(0) + ":"
- + urllink.get(1)))
- + "");
+ .equals(urllink.get(1).toLowerCase()) ? urllink
+ .get(0) : (urllink.get(0) + ":" + urllink
+ .get(1))) + "");
}
} catch (Exception x)
{
- System.err.println(
- "problem when creating links from " + urlstring);
+ System.err.println("problem when creating links from "
+ + urlstring);
x.printStackTrace();
}
}
@@ -283,10 +316,10 @@ public class SequenceAnnotationReport
public void createSequenceAnnotationReport(final StringBuilder tip,
SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
- Map minmax)
+ FeatureRendererModel fr)
{
createSequenceAnnotationReport(tip, sequence, showDbRefs, showNpFeats,
- minmax, false);
+ fr, false);
}
/**
@@ -301,13 +334,13 @@ public class SequenceAnnotationReport
* whether to include database references for the sequence
* @param showNpFeats
* whether to include non-positional sequence features
- * @param minmax
+ * @param fr
* @param summary
* @return
*/
int createSequenceAnnotationReport(final StringBuilder sb,
SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
- Map minmax, boolean summary)
+ FeatureRendererModel fr, boolean summary)
{
String tmp;
sb.append("");
@@ -324,7 +357,7 @@ public class SequenceAnnotationReport
{
ds = ds.getDatasetSequence();
}
-
+
if (showDbRefs)
{
maxWidth = Math.max(maxWidth, appendDbRefs(sb, ds, summary));
@@ -339,7 +372,7 @@ public class SequenceAnnotationReport
.getNonPositionalFeatures())
{
int sz = -sb.length();
- appendFeature(sb, 0, minmax, sf);
+ appendFeature(sb, 0, fr, sf);
sz += sb.length();
maxWidth = Math.max(maxWidth, sz);
}
@@ -428,8 +461,7 @@ public class SequenceAnnotationReport
}
if (moreSources)
{
- sb.append("
").append(source)
- .append(COMMA).append(ELLIPSIS);
+ sb.append("
").append(source).append(COMMA).append(ELLIPSIS);
}
if (ellipsis)
{
@@ -443,10 +475,10 @@ public class SequenceAnnotationReport
public void createTooltipAnnotationReport(final StringBuilder tip,
SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
- Map minmax)
+ FeatureRendererModel fr)
{
- int maxWidth = createSequenceAnnotationReport(tip, sequence, showDbRefs,
- showNpFeats, minmax, true);
+ int maxWidth = createSequenceAnnotationReport(tip, sequence,
+ showDbRefs, showNpFeats, fr, true);
if (maxWidth > 60)
{
diff --git a/src/jalview/renderer/seqfeatures/FeatureRenderer.java b/src/jalview/renderer/seqfeatures/FeatureRenderer.java
index 6687e6a..795cd36 100644
--- a/src/jalview/renderer/seqfeatures/FeatureRenderer.java
+++ b/src/jalview/renderer/seqfeatures/FeatureRenderer.java
@@ -304,7 +304,10 @@ public class FeatureRenderer extends FeatureRendererModel
List overlaps = seq.getFeatures().findFeatures(
visiblePositions.getBegin(), visiblePositions.getEnd(), type);
- // filterFeaturesForDisplay(overlaps, fc);
+ if (fc.isSimpleColour())
+ {
+ filterFeaturesForDisplay(overlaps);
+ }
for (SequenceFeature sf : overlaps)
{
diff --git a/src/jalview/schemes/FeatureColour.java b/src/jalview/schemes/FeatureColour.java
index 54d1c6c..df4ea39 100644
--- a/src/jalview/schemes/FeatureColour.java
+++ b/src/jalview/schemes/FeatureColour.java
@@ -29,10 +29,28 @@ import java.awt.Color;
import java.util.StringTokenizer;
/**
- * A class that wraps either a simple colour or a graduated colour
+ * A class that represents a colour scheme for a feature type. Options supported
+ * are currently
+ *
+ * - a simple colour e.g. Red
+ * - colour by label - a colour is generated from the feature description
+ * - graduated colour by feature score
+ *
+ * - minimum and maximum score range must be provided
+ * - minimum and maximum value colours should be specified
+ * - a colour for 'no value' may optionally be provided
+ * - colours for intermediate scores are interpolated RGB values
+ * - there is an optional threshold above/below which to colour values
+ * - the range may be the full value range, or may be limited by the threshold
+ * value
+ *
+ * - colour by (text) value of a named attribute
- graduated colour by
+ * (numeric) value of a named attribute
*/
public class FeatureColour implements FeatureColourI
{
+ static final Color DEFAULT_NO_COLOUR = Color.LIGHT_GRAY;
+
private static final String BAR = "|";
final private Color colour;
@@ -41,10 +59,30 @@ public class FeatureColour implements FeatureColourI
final private Color maxColour;
+ /*
+ * colour to use for colour by attribute when the
+ * attribute value is absent
+ */
+ final private Color noColour;
+
+ /*
+ * if true, then colour has a gradient based on a numerical
+ * range (either feature score, or an attribute value)
+ */
private boolean graduatedColour;
+ /*
+ * if true, colour values are generated from a text string,
+ * either feature description, or an attribute value
+ */
private boolean colourByLabel;
+ /*
+ * if not null, the value of this named attribute is used for
+ * colourByLabel or graduatedColour
+ */
+ private String byAttributeName;
+
private float threshold;
private float base;
@@ -288,6 +326,7 @@ public class FeatureColour implements FeatureColourI
{
minColour = Color.WHITE;
maxColour = Color.BLACK;
+ noColour = DEFAULT_NO_COLOUR;
minRed = 0f;
minGreen = 0f;
minBlue = 0f;
@@ -298,7 +337,8 @@ public class FeatureColour implements FeatureColourI
}
/**
- * Constructor given a colour range and a score range
+ * Constructor given a colour range and a score range, defaulting 'no value
+ * colour' to be the same as minimum colour
*
* @param low
* @param high
@@ -307,36 +347,7 @@ public class FeatureColour implements FeatureColourI
*/
public FeatureColour(Color low, Color high, float min, float max)
{
- if (low == null)
- {
- low = Color.white;
- }
- if (high == null)
- {
- high = Color.black;
- }
- graduatedColour = true;
- colour = null;
- minColour = low;
- maxColour = high;
- threshold = Float.NaN;
- isHighToLow = min >= max;
- minRed = low.getRed() / 255f;
- minGreen = low.getGreen() / 255f;
- minBlue = low.getBlue() / 255f;
- deltaRed = (high.getRed() / 255f) - minRed;
- deltaGreen = (high.getGreen() / 255f) - minGreen;
- deltaBlue = (high.getBlue() / 255f) - minBlue;
- if (isHighToLow)
- {
- base = max;
- range = min - max;
- }
- else
- {
- base = min;
- range = max - min;
- }
+ this(low, high, low, min, max);
}
/**
@@ -350,6 +361,7 @@ public class FeatureColour implements FeatureColourI
colour = fc.colour;
minColour = fc.minColour;
maxColour = fc.maxColour;
+ noColour = fc.noColour;
minRed = fc.minRed;
minGreen = fc.minGreen;
minBlue = fc.minBlue;
@@ -359,6 +371,7 @@ public class FeatureColour implements FeatureColourI
base = fc.base;
range = fc.range;
isHighToLow = fc.isHighToLow;
+ byAttributeName = fc.byAttributeName;
setAboveThreshold(fc.isAboveThreshold());
setBelowThreshold(fc.isBelowThreshold());
setThreshold(fc.getThreshold());
@@ -376,10 +389,46 @@ public class FeatureColour implements FeatureColourI
public FeatureColour(FeatureColour fc, float min, float max)
{
this(fc);
- graduatedColour = true;
+ setGraduatedColour(true);
updateBounds(min, max);
}
+ public FeatureColour(Color low, Color high, Color noValueColour,
+ float min, float max)
+ {
+ if (low == null)
+ {
+ low = Color.white;
+ }
+ if (high == null)
+ {
+ high = Color.black;
+ }
+ graduatedColour = true;
+ colour = null;
+ minColour = low;
+ maxColour = high;
+ noColour = noValueColour;
+ threshold = Float.NaN;
+ isHighToLow = min >= max;
+ minRed = low.getRed() / 255f;
+ minGreen = low.getGreen() / 255f;
+ minBlue = low.getBlue() / 255f;
+ deltaRed = (high.getRed() / 255f) - minRed;
+ deltaGreen = (high.getGreen() / 255f) - minGreen;
+ deltaBlue = (high.getBlue() / 255f) - minBlue;
+ if (isHighToLow)
+ {
+ base = max;
+ range = min - max;
+ }
+ else
+ {
+ base = min;
+ range = max - min;
+ }
+ }
+
@Override
public boolean isGraduatedColour()
{
@@ -418,6 +467,12 @@ public class FeatureColour implements FeatureColourI
}
@Override
+ public Color getNoColour()
+ {
+ return noColour;
+ }
+
+ @Override
public boolean isColourByLabel()
{
return colourByLabel;
@@ -506,10 +561,7 @@ public class FeatureColour implements FeatureColourI
}
/**
- * Updates the base and range appropriately for the given minmax range
- *
- * @param min
- * @param max
+ * {@inheritDoc}
*/
@Override
public void updateBounds(float min, float max)
@@ -542,7 +594,10 @@ public class FeatureColour implements FeatureColourI
{
if (isColourByLabel())
{
- return ColorUtils.createColourFromName(feature.getDescription());
+ String label = byAttributeName == null ? feature.getDescription()
+ : feature.getValueAsString(byAttributeName);
+ return label == null ? noColour : ColorUtils
+ .createColourFromName(label);
}
if (!isGraduatedColour())
@@ -552,17 +607,32 @@ public class FeatureColour implements FeatureColourI
/*
* graduated colour case, optionally with threshold
+ * may be based on feature score on an attribute value
* Float.NaN is assigned minimum visible score colour
+ * no such attribute is assigned the 'no value' colour
*/
float scr = feature.getScore();
+ if (byAttributeName != null)
+ {
+ try
+ {
+ String attVal = feature.getValueAsString(byAttributeName);
+ scr = Float.valueOf(attVal);
+ } catch (Throwable e)
+ {
+ scr = Float.NaN;
+ }
+ }
if (Float.isNaN(scr))
{
- return getMinColour();
+ return noColour;
}
+
if (isAboveThreshold() && scr <= threshold)
{
return null;
}
+
if (isBelowThreshold() && scr >= threshold)
{
return null;
@@ -674,4 +744,22 @@ public class FeatureColour implements FeatureColourI
return String.format("%s\t%s", featureType, colourString);
}
+ @Override
+ public boolean isColourByAttribute()
+ {
+ return byAttributeName != null;
+ }
+
+ @Override
+ public String getAttributeName()
+ {
+ return byAttributeName;
+ }
+
+ @Override
+ public void setAttributeName(String name)
+ {
+ byAttributeName = name;
+ }
+
}
diff --git a/src/jalview/urls/CustomUrlProvider.java b/src/jalview/urls/CustomUrlProvider.java
index 0d5ef99..86d5660 100644
--- a/src/jalview/urls/CustomUrlProvider.java
+++ b/src/jalview/urls/CustomUrlProvider.java
@@ -160,10 +160,24 @@ public class CustomUrlProvider extends UrlProviderImpl
*/
private void upgradeOldLinks(HashMap urls)
{
+ boolean upgrade = false;
// upgrade old SRS link
if (urls.containsKey(SRS_LABEL))
{
urls.remove(SRS_LABEL);
+ upgrade = true;
+ }
+ // upgrade old EBI link - easier just to remove and re-add than faffing
+ // around checking exact url
+ if (urls.containsKey(UrlConstants.DEFAULT_LABEL))
+ {
+ // note because this is called separately for selected and nonselected
+ // urls, the default url will not always be present
+ urls.remove(UrlConstants.DEFAULT_LABEL);
+ upgrade = true;
+ }
+ if (upgrade)
+ {
UrlLink link = new UrlLink(UrlConstants.DEFAULT_STRING);
link.setLabel(UrlConstants.DEFAULT_LABEL);
urls.put(UrlConstants.DEFAULT_LABEL, link);
diff --git a/src/jalview/util/ColorUtils.java b/src/jalview/util/ColorUtils.java
index d4be322..60129fb 100644
--- a/src/jalview/util/ColorUtils.java
+++ b/src/jalview/util/ColorUtils.java
@@ -25,10 +25,17 @@
package jalview.util;
import java.awt.Color;
+import java.util.HashMap;
+import java.util.Map;
import java.util.Random;
public class ColorUtils
{
+ private static final int MAX_CACHE_SIZE = 1729;
+ /*
+ * a cache for colours generated from text strings
+ */
+ static Map myColours = new HashMap<>();
/**
* Generates a random color, will mix with input color. Code taken from
@@ -260,6 +267,10 @@ public class ColorUtils
{
return Color.white;
}
+ if (myColours.containsKey(name))
+ {
+ return myColours.get(name);
+ }
int lsize = name.length();
int start = 0;
int end = lsize / 3;
@@ -291,6 +302,11 @@ public class ColorUtils
Color color = new Color(r, g, b);
+ if (myColours.size() < MAX_CACHE_SIZE)
+ {
+ myColours.put(name, color);
+ }
+
return color;
}
diff --git a/src/jalview/util/UrlConstants.java b/src/jalview/util/UrlConstants.java
index d6ece8d..e5cfaee 100644
--- a/src/jalview/util/UrlConstants.java
+++ b/src/jalview/util/UrlConstants.java
@@ -57,10 +57,20 @@ public class UrlConstants
public static final String DEFAULT_STRING = DEFAULT_LABEL
+ "|https://www.ebi.ac.uk/ebisearch/search.ebi?db=allebi&query=$SEQUENCE_ID$";
+ private static final String COLON = ":";
+
/*
* not instantiable
*/
private UrlConstants()
{
}
+
+ public static boolean isDefaultString(String link)
+ {
+ String sublink = link.substring(link.indexOf(COLON) + 1);
+ String subdefault = DEFAULT_STRING
+ .substring(DEFAULT_STRING.indexOf(COLON) + 1);
+ return sublink.equalsIgnoreCase(subdefault);
+ }
}
diff --git a/src/jalview/util/matcher/KeyedMatcher.java b/src/jalview/util/matcher/KeyedMatcher.java
index 5e42e1c..cd952e7 100644
--- a/src/jalview/util/matcher/KeyedMatcher.java
+++ b/src/jalview/util/matcher/KeyedMatcher.java
@@ -79,7 +79,15 @@ public class KeyedMatcher implements KeyedMatcherI
{
StringBuilder sb = new StringBuilder();
sb.append(key).append(" ").append(matcher.getCondition().toString())
- .append(" ").append(matcher.getPattern());
+ .append(" ");
+ if (matcher.getCondition().isNumeric())
+ {
+ sb.append(matcher.getPattern());
+ }
+ else
+ {
+ sb.append("'").append(matcher.getPattern()).append("'");
+ }
return sb.toString();
}
diff --git a/src/jalview/util/matcher/KeyedMatcherSet.java b/src/jalview/util/matcher/KeyedMatcherSet.java
index adc04ba..35a41c2 100644
--- a/src/jalview/util/matcher/KeyedMatcherSet.java
+++ b/src/jalview/util/matcher/KeyedMatcherSet.java
@@ -1,7 +1,6 @@
package jalview.util.matcher;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
@@ -91,9 +90,9 @@ public class KeyedMatcherSet implements KeyedMatcherSetI
}
@Override
- public Iterator getMatchers()
+ public Iterable getMatchers()
{
- return matchConditions.iterator();
+ return matchConditions;
}
@Override
diff --git a/src/jalview/util/matcher/KeyedMatcherSetI.java b/src/jalview/util/matcher/KeyedMatcherSetI.java
index 7cbebab..25dc96e 100644
--- a/src/jalview/util/matcher/KeyedMatcherSetI.java
+++ b/src/jalview/util/matcher/KeyedMatcherSetI.java
@@ -1,6 +1,5 @@
package jalview.util.matcher;
-import java.util.Iterator;
import java.util.function.Function;
/**
@@ -54,7 +53,7 @@ public interface KeyedMatcherSetI
*
* @return
*/
- Iterator getMatchers();
+ Iterable getMatchers();
/**
* Answers true if this object contains no conditions
diff --git a/src/jalview/util/matcher/Matcher.java b/src/jalview/util/matcher/Matcher.java
index d8c9361..a213a17 100644
--- a/src/jalview/util/matcher/Matcher.java
+++ b/src/jalview/util/matcher/Matcher.java
@@ -213,6 +213,17 @@ public class Matcher implements MatcherI
@Override
public String toString()
{
- return condition.name() + " " + pattern;
+ StringBuilder sb = new StringBuilder();
+ sb.append(condition.name()).append(" ");
+ if (condition.isNumeric())
+ {
+ sb.append(pattern);
+ }
+ else
+ {
+ sb.append("'").append(pattern).append("'");
+ }
+
+ return sb.toString();
}
}
diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
index 6461748..c2f5bb7 100644
--- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
+++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
@@ -490,7 +490,8 @@ public abstract class FeatureRendererModel
if (mmrange != null)
{
FeatureColourI fc = featureColours.get(oldRender[j]);
- if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled())
+ if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled()
+ && !fc.isColourByAttribute())
{
fc.updateBounds(mmrange[0][0], mmrange[0][1]);
}
@@ -520,7 +521,8 @@ public abstract class FeatureRendererModel
if (mmrange != null)
{
FeatureColourI fc = featureColours.get(newf[i]);
- if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled())
+ if (fc != null && !fc.isSimpleColour() && fc.isAutoScaled()
+ && !fc.isColourByAttribute())
{
fc.updateBounds(mmrange[0][0], mmrange[0][1]);
}
@@ -586,7 +588,8 @@ public abstract class FeatureRendererModel
*/
protected boolean showFeatureOfType(String type)
{
- return type == null ? false : av.getFeaturesDisplayed().isVisible(type);
+ return type == null ? false : (av.getFeaturesDisplayed() == null ? true
+ : av.getFeaturesDisplayed().isVisible(type));
}
@Override
@@ -1000,23 +1003,19 @@ public abstract class FeatureRendererModel
/**
* Removes from the list of features any that duplicate the location of a
- * feature of the same type (unless feature is filtered out, or a graduated
- * colour scheme or colour by label is applied). Should be used only for
- * features of the same feature colour (which normally implies the same
- * feature type).
+ * feature of the same type. Should be used only for features of the same,
+ * simple, feature colour (which normally implies the same feature type). Does
+ * not check visibility settings for feature type or feature group.
*
* @param features
- * @param fc
*/
- public void filterFeaturesForDisplay(List features,
- FeatureColourI fc)
+ public void filterFeaturesForDisplay(List features)
{
if (features.isEmpty())
{
return;
}
SequenceFeatures.sortFeatures(features, true);
- boolean simpleColour = fc == null || fc.isSimpleColour();
SequenceFeature lastFeature = null;
Iterator it = features.iterator();
@@ -1030,15 +1029,12 @@ public abstract class FeatureRendererModel
* (checking type and isContactFeature as a fail-safe here, although
* currently they are guaranteed to match in this context)
*/
- if (simpleColour)
+ if (lastFeature != null && sf.getBegin() == lastFeature.getBegin()
+ && sf.getEnd() == lastFeature.getEnd()
+ && sf.isContactFeature() == lastFeature.isContactFeature()
+ && sf.getType().equals(lastFeature.getType()))
{
- if (lastFeature != null && sf.getBegin() == lastFeature.getBegin()
- && sf.getEnd() == lastFeature.getEnd()
- && sf.isContactFeature() == lastFeature.isContactFeature()
- && sf.getType().equals(lastFeature.getType()))
- {
- it.remove();
- }
+ it.remove();
}
lastFeature = sf;
}
diff --git a/test/jalview/io/SequenceAnnotationReportTest.java b/test/jalview/io/SequenceAnnotationReportTest.java
index 9e61bec..87e35c7 100644
--- a/test/jalview/io/SequenceAnnotationReportTest.java
+++ b/test/jalview/io/SequenceAnnotationReportTest.java
@@ -23,15 +23,18 @@ package jalview.io;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
+import jalview.api.FeatureColourI;
import jalview.datamodel.DBRefEntry;
import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
import jalview.gui.JvOptionPane;
import jalview.io.gff.GffConstants;
+import jalview.renderer.seqfeatures.FeatureRenderer;
+import jalview.schemes.FeatureColour;
+import jalview.viewmodel.seqfeatures.FeatureRendererModel;
-import java.util.HashMap;
-import java.util.Hashtable;
+import java.awt.Color;
import java.util.Map;
import junit.extensions.PA;
@@ -95,8 +98,9 @@ public class SequenceAnnotationReportTest
SequenceFeature sf = new SequenceFeature("METAL", "Fe2-S", 1, 3, 1.3f,
"group");
- Map minmax = new Hashtable();
- sar.appendFeature(sb, 1, minmax, sf);
+ FeatureRendererModel fr = new FeatureRenderer(null);
+ Map minmax = fr.getMinMax();
+ sar.appendFeature(sb, 1, fr, sf);
/*
* map has no entry for this feature type - score is not shown:
*/
@@ -106,7 +110,7 @@ public class SequenceAnnotationReportTest
* map has entry for this feature type - score is shown:
*/
minmax.put("METAL", new float[][] { { 0f, 1f }, null });
- sar.appendFeature(sb, 1, minmax, sf);
+ sar.appendFeature(sb, 1, fr, sf);
//
is appended to a buffer > 6 in length
assertEquals("METAL 1 3; Fe2-S
METAL 1 3; Fe2-S Score=1.3",
sb.toString());
@@ -116,7 +120,7 @@ public class SequenceAnnotationReportTest
*/
minmax.put("METAL", new float[][] { { 2f, 2f }, null });
sb.setLength(0);
- sar.appendFeature(sb, 1, minmax, sf);
+ sar.appendFeature(sb, 1, fr, sf);
assertEquals("METAL 1 3; Fe2-S", sb.toString());
}
@@ -132,8 +136,11 @@ public class SequenceAnnotationReportTest
assertEquals("METAL 1 3; Fe2-S", sb.toString());
}
+ /**
+ * A specific attribute value is included if it is used to colour the feature
+ */
@Test(groups = "Functional")
- public void testAppendFeature_clinicalSignificance()
+ public void testAppendFeature_colouredByAttribute()
{
SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
StringBuilder sb = new StringBuilder();
@@ -141,12 +148,35 @@ public class SequenceAnnotationReportTest
Float.NaN, "group");
sf.setValue("clinical_significance", "Benign");
- sar.appendFeature(sb, 1, null, sf);
- assertEquals("METAL 1 3; Fe2-S; Benign", sb.toString());
+ /*
+ * first with no colour by attribute
+ */
+ FeatureRendererModel fr = new FeatureRenderer(null);
+ sar.appendFeature(sb, 1, fr, sf);
+ assertEquals("METAL 1 3; Fe2-S", sb.toString());
+
+ /*
+ * then with colour by an attribute the feature lacks
+ */
+ FeatureColourI fc = new FeatureColour(Color.white, Color.black, 5, 10);
+ fc.setAttributeName("Pfam");
+ fr.setColour("METAL", fc);
+ sb.setLength(0);
+ sar.appendFeature(sb, 1, fr, sf);
+ assertEquals("METAL 1 3; Fe2-S", sb.toString()); // no change
+
+ /*
+ * then with colour by an attribute the feature has
+ */
+ fc.setAttributeName("clinical_significance");
+ sb.setLength(0);
+ sar.appendFeature(sb, 1, fr, sf);
+ assertEquals("METAL 1 3; Fe2-S; clinical_significance=Benign",
+ sb.toString());
}
@Test(groups = "Functional")
- public void testAppendFeature_withScoreStatusClinicalSignificance()
+ public void testAppendFeature_withScoreStatusAttribute()
{
SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
StringBuilder sb = new StringBuilder();
@@ -154,11 +184,17 @@ public class SequenceAnnotationReportTest
"group");
sf.setStatus("Confirmed");
sf.setValue("clinical_significance", "Benign");
- Map minmax = new Hashtable();
+
+ FeatureRendererModel fr = new FeatureRenderer(null);
+ Map minmax = fr.getMinMax();
+ FeatureColourI fc = new FeatureColour(Color.white, Color.blue, 12, 22);
+ fc.setAttributeName("clinical_significance");
+ fr.setColour("METAL", fc);
minmax.put("METAL", new float[][] { { 0f, 1f }, null });
- sar.appendFeature(sb, 1, minmax, sf);
+ sar.appendFeature(sb, 1, fr, sf);
- assertEquals("METAL 1 3; Fe2-S Score=1.3; (Confirmed); Benign",
+ assertEquals(
+ "METAL 1 3; Fe2-S Score=1.3; (Confirmed); clinical_significance=Benign",
sb.toString());
}
@@ -226,7 +262,7 @@ public class SequenceAnnotationReportTest
null));
sb.setLength(0);
sar.createSequenceAnnotationReport(sb, seq, true, true, null);
- String expected = "
SeqDesc
Type1 ; Nonpos";
+ String expected = "
SeqDesc
Type1 ; Nonpos Score=1.0";
assertEquals(expected, sb.toString());
/*
@@ -244,10 +280,13 @@ public class SequenceAnnotationReportTest
*/
seq.addSequenceFeature(new SequenceFeature("Metal", "Desc", 0, 0, 5f,
null));
- Map minmax = new HashMap();
+
+ FeatureRendererModel fr = new FeatureRenderer(null);
+ Map minmax = fr.getMinMax();
minmax.put("Metal", new float[][] { null, new float[] { 2f, 5f } });
+
sb.setLength(0);
- sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
+ sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
expected = "
SeqDesc
Metal ; Desc
Type1 ; Nonpos";
assertEquals(expected, sb.toString());
@@ -260,19 +299,20 @@ public class SequenceAnnotationReportTest
sf.setValue("linkonly", Boolean.TRUE);
seq.addSequenceFeature(sf);
sb.setLength(0);
- sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
+ sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
assertEquals(expected, sb.toString()); // unchanged!
/*
- * 'clinical_significance' currently being specially included
+ * 'clinical_significance' attribute only included when
+ * used for feature colouring
*/
SequenceFeature sf2 = new SequenceFeature("Variant", "Havana", 0, 0,
5f, null);
sf2.setValue(GffConstants.CLINICAL_SIGNIFICANCE, "benign");
seq.addSequenceFeature(sf2);
sb.setLength(0);
- sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
- expected = "
SeqDesc
Metal ; Desc
Type1 ; Nonpos
Variant ; Havana; benign";
+ sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
+ expected = "
SeqDesc
Metal ; Desc
Type1 ; Nonpos
Variant ; Havana";
assertEquals(expected, sb.toString());
/*
@@ -280,18 +320,24 @@ public class SequenceAnnotationReportTest
*/
seq.addDBRef(new DBRefEntry("PDB", "0", "3iu1"));
seq.addDBRef(new DBRefEntry("Uniprot", "1", "P30419"));
+
// with showDbRefs = false
sb.setLength(0);
- sar.createSequenceAnnotationReport(sb, seq, false, true, minmax);
+ sar.createSequenceAnnotationReport(sb, seq, false, true, fr);
assertEquals(expected, sb.toString()); // unchanged
- // with showDbRefs = true
+
+ // with showDbRefs = true, colour Variant features by clinical_significance
sb.setLength(0);
- sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
- expected = "
SeqDesc
UNIPROT P30419
PDB 3iu1
Metal ; Desc
Type1 ; Nonpos
Variant ; Havana; benign";
+ FeatureColourI fc = new FeatureColour(Color.green, Color.pink, 2, 3);
+ fc.setAttributeName("clinical_significance");
+ fr.setColour("Variant", fc);
+ sar.createSequenceAnnotationReport(sb, seq, true, true, fr);
+ expected = "
SeqDesc
UNIPROT P30419
PDB 3iu1
Metal ; Desc
"
+ + "Type1 ; Nonpos
Variant ; Havana; clinical_significance=benign";
assertEquals(expected, sb.toString());
// with showNonPositionalFeatures = false
sb.setLength(0);
- sar.createSequenceAnnotationReport(sb, seq, true, false, minmax);
+ sar.createSequenceAnnotationReport(sb, seq, true, false, fr);
expected = "
SeqDesc
UNIPROT P30419
PDB 3iu1";
assertEquals(expected, sb.toString());
diff --git a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java
index d3cddf9..438feba 100644
--- a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java
+++ b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java
@@ -264,7 +264,7 @@ public class FeatureRendererTest
FeatureRenderer fr = new FeatureRenderer(av);
List features = new ArrayList<>();
- fr.filterFeaturesForDisplay(features, null); // empty list, does nothing
+ fr.filterFeaturesForDisplay(features); // empty list, does nothing
SequenceI seq = av.getAlignment().getSequenceAt(0);
SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
@@ -297,7 +297,7 @@ public class FeatureRendererTest
* filter out duplicate (co-located) features
* note: which gets removed is not guaranteed
*/
- fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue));
+ fr.filterFeaturesForDisplay(features);
assertEquals(features.size(), 3);
assertTrue(features.contains(sf1) || features.contains(sf4));
assertFalse(features.contains(sf1) && features.contains(sf4));
@@ -306,58 +306,17 @@ public class FeatureRendererTest
assertTrue(features.contains(sf5));
/*
- * hide group 3 - sf3 is removed, sf2 is retained
- */
- fr.setGroupVisibility("group3", false);
- features = seq.getSequenceFeatures();
- fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue));
- assertEquals(features.size(), 3);
- assertTrue(features.contains(sf1) || features.contains(sf4));
- assertFalse(features.contains(sf1) && features.contains(sf4));
- assertTrue(features.contains(sf2));
- assertFalse(features.contains(sf3));
- assertTrue(features.contains(sf5));
-
- /*
- * hide group 2, show group 3 - sf2 is removed, sf3 is retained
+ * hide groups 2 and 3 makes no difference to this method
*/
fr.setGroupVisibility("group2", false);
- fr.setGroupVisibility("group3", true);
+ fr.setGroupVisibility("group3", false);
features = seq.getSequenceFeatures();
- fr.filterFeaturesForDisplay(features, null);
+ fr.filterFeaturesForDisplay(features);
assertEquals(features.size(), 3);
assertTrue(features.contains(sf1) || features.contains(sf4));
assertFalse(features.contains(sf1) && features.contains(sf4));
- assertFalse(features.contains(sf2));
- assertTrue(features.contains(sf3));
- assertTrue(features.contains(sf5));
-
- /*
- * no filtering of co-located features with graduated colour scheme
- * filterFeaturesForDisplay does _not_ check colour threshold
- * sf2 is removed as its group is hidden
- */
- features = seq.getSequenceFeatures();
- fr.filterFeaturesForDisplay(features, new FeatureColour(Color.black,
- Color.white, 0f, 1f));
- assertEquals(features.size(), 4);
- assertTrue(features.contains(sf1));
- assertTrue(features.contains(sf3));
- assertTrue(features.contains(sf4));
- assertTrue(features.contains(sf5));
-
- /*
- * co-located features with colour by label
- * should not get filtered
- */
- features = seq.getSequenceFeatures();
- FeatureColour fc = new FeatureColour(Color.black);
- fc.setColourByLabel(true);
- fr.filterFeaturesForDisplay(features, fc);
- assertEquals(features.size(), 4);
- assertTrue(features.contains(sf1));
- assertTrue(features.contains(sf3));
- assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf2) || features.contains(sf3));
+ assertFalse(features.contains(sf2) && features.contains(sf3));
assertTrue(features.contains(sf5));
}
}
diff --git a/test/jalview/schemes/FeatureColourTest.java b/test/jalview/schemes/FeatureColourTest.java
index 7a72c15..3f60152 100644
--- a/test/jalview/schemes/FeatureColourTest.java
+++ b/test/jalview/schemes/FeatureColourTest.java
@@ -33,6 +33,8 @@ import jalview.util.Format;
import java.awt.Color;
+import junit.extensions.PA;
+
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
@@ -57,6 +59,8 @@ public class FeatureColourTest
assertTrue(fc1.getColour().equals(Color.RED));
assertFalse(fc1.isGraduatedColour());
assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
/*
* min-max colour
@@ -68,9 +72,31 @@ public class FeatureColourTest
assertTrue(fc1.isGraduatedColour());
assertFalse(fc1.isColourByLabel());
assertTrue(fc1.isAboveThreshold());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
assertEquals(12f, fc1.getThreshold());
assertEquals(Color.gray, fc1.getMinColour());
assertEquals(Color.black, fc1.getMaxColour());
+ assertEquals(Color.gray, fc1.getNoColour());
+ assertEquals(10f, fc1.getMin());
+ assertEquals(20f, fc1.getMax());
+
+ /*
+ * min-max-noValue colour
+ */
+ fc = new FeatureColour(Color.gray, Color.black, Color.green, 10f, 20f);
+ fc.setAboveThreshold(true);
+ fc.setThreshold(12f);
+ fc1 = new FeatureColour(fc);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertTrue(fc1.isAboveThreshold());
+ assertEquals(12f, fc1.getThreshold());
+ assertEquals(Color.gray, fc1.getMinColour());
+ assertEquals(Color.black, fc1.getMaxColour());
+ assertEquals(Color.green, fc1.getNoColour());
assertEquals(10f, fc1.getMin());
assertEquals(20f, fc1.getMax());
@@ -82,6 +108,102 @@ public class FeatureColourTest
fc1 = new FeatureColour(fc);
assertTrue(fc1.isColourByLabel());
assertFalse(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+
+ /*
+ * colour by attribute (label)
+ */
+ fc = new FeatureColour();
+ fc.setColourByLabel(true);
+ fc.setAttributeName("AF");
+ fc1 = new FeatureColour(fc);
+ assertTrue(fc1.isColourByLabel());
+ assertFalse(fc1.isGraduatedColour());
+ assertTrue(fc1.isColourByAttribute());
+ assertEquals("AF", fc1.getAttributeName());
+
+ /*
+ * colour by attribute (value)
+ */
+ fc = new FeatureColour(Color.gray, Color.black, Color.green, 10f, 20f);
+ fc.setAboveThreshold(true);
+ fc.setThreshold(12f);
+ fc.setAttributeName("AF");
+ fc1 = new FeatureColour(fc);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertTrue(fc1.isColourByAttribute());
+ assertEquals("AF", fc1.getAttributeName());
+ assertTrue(fc1.isAboveThreshold());
+ assertEquals(12f, fc1.getThreshold());
+ assertEquals(Color.gray, fc1.getMinColour());
+ assertEquals(Color.black, fc1.getMaxColour());
+ assertEquals(Color.green, fc1.getNoColour());
+ assertEquals(10f, fc1.getMin());
+ assertEquals(20f, fc1.getMax());
+ }
+
+ @Test(groups = { "Functional" })
+ public void testCopyConstructor_minMax()
+ {
+ /*
+ * graduated colour
+ */
+ FeatureColour fc = new FeatureColour(Color.BLUE, Color.RED, 1f, 5f);
+ assertTrue(fc.isGraduatedColour());
+ assertFalse(fc.isColourByLabel());
+ assertFalse(fc.isColourByAttribute());
+ assertNull(fc.getAttributeName());
+ assertEquals(1f, fc.getMin());
+ assertEquals(5f, fc.getMax());
+
+ /*
+ * update min-max bounds
+ */
+ FeatureColour fc1 = new FeatureColour(fc, 2f, 6f);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertEquals(2f, fc1.getMin());
+ assertEquals(6f, fc1.getMax());
+ assertFalse((boolean) PA.getValue(fc1, "isHighToLow"));
+
+ /*
+ * update min-max bounds - high to low
+ */
+ fc1 = new FeatureColour(fc, 23f, 16f);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertEquals(23f, fc1.getMin());
+ assertEquals(16f, fc1.getMax());
+ assertTrue((boolean) PA.getValue(fc1, "isHighToLow"));
+
+ /*
+ * colour by label
+ */
+ fc = new FeatureColour(Color.BLUE, Color.RED, 1f, 5f);
+ fc.setColourByLabel(true);
+ assertFalse(fc.isGraduatedColour());
+ assertTrue(fc.isColourByLabel());
+ assertFalse(fc.isColourByAttribute());
+ assertNull(fc.getAttributeName());
+ assertEquals(1f, fc.getMin());
+ assertEquals(5f, fc.getMax());
+
+ /*
+ * update min-max bounds - converts to graduated colour
+ */
+ fc1 = new FeatureColour(fc, 2f, 6f);
+ assertTrue(fc1.isGraduatedColour());
+ assertFalse(fc1.isColourByLabel());
+ assertFalse(fc1.isColourByAttribute());
+ assertNull(fc1.getAttributeName());
+ assertEquals(2f, fc1.getMin());
+ assertEquals(6f, fc1.getMax());
}
@Test(groups = { "Functional" })
@@ -106,8 +228,11 @@ public class FeatureColourTest
@Test(groups = { "Functional" })
public void testGetColor_Graduated()
{
- // graduated colour from score 0 to 100, gray(128, 128, 128) to red(255, 0,
- // 0)
+ /*
+ * graduated colour from
+ * score 0 to 100
+ * gray(128, 128, 128) to red(255, 0, 0)
+ */
FeatureColour fc = new FeatureColour(Color.GRAY, Color.RED, 0f, 100f);
// feature score is 75 which is 3/4 of the way from GRAY to RED
SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
@@ -340,4 +465,59 @@ public class FeatureColourTest
fc = FeatureColour.parseJalviewFeatureColour(descriptor);
assertTrue(fc.isGraduatedColour());
}
+
+ @Test(groups = { "Functional" })
+ public void testGetColor_colourByAttributeText()
+ {
+ FeatureColour fc = new FeatureColour();
+ fc.setColourByLabel(true);
+ fc.setAttributeName("consequence");
+ SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 1f,
+ null);
+
+ /*
+ * if feature has no such attribute, use 'no value' colour
+ */
+ assertEquals(FeatureColour.DEFAULT_NO_COLOUR, fc.getColor(sf));
+
+ /*
+ * if feature has attribute, generate colour from value
+ */
+ sf.setValue("consequence", "benign");
+ Color expected = ColorUtils.createColourFromName("benign");
+ assertEquals(expected, fc.getColor(sf));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testGetColor_GraduatedByAttributeValue()
+ {
+ /*
+ * graduated colour based on attribute value for AF
+ * given a min-max range of 0-100
+ */
+ FeatureColour fc = new FeatureColour(new Color(50, 100, 150),
+ new Color(150, 200, 250), Color.yellow, 0f, 100f);
+ String attName = "AF";
+ fc.setAttributeName(attName);
+
+ /*
+ * first case: feature lacks the attribute - use 'no value' colour
+ */
+ SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
+ null);
+ assertEquals(Color.yellow, fc.getColor(sf));
+
+ /*
+ * second case: attribute present but not numeric - treat as if absent
+ */
+ sf.setValue(attName, "twelve");
+ assertEquals(Color.yellow, fc.getColor(sf));
+
+ /*
+ * third case: valid attribute value
+ */
+ sf.setValue(attName, "20.0");
+ Color expected = new Color(70, 120, 170);
+ assertEquals(expected, fc.getColor(sf));
+ }
}
diff --git a/test/jalview/util/matcher/KeyedMatcherSetTest.java b/test/jalview/util/matcher/KeyedMatcherSetTest.java
index 0d2767d..3018cb6 100644
--- a/test/jalview/util/matcher/KeyedMatcherSetTest.java
+++ b/test/jalview/util/matcher/KeyedMatcherSetTest.java
@@ -2,8 +2,10 @@ package jalview.util.matcher;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;
+import java.util.Iterator;
import java.util.function.Function;
import org.testng.annotations.Test;
@@ -66,7 +68,7 @@ public class KeyedMatcherSetTest
assertEquals(km1.toString(), "AF < 1.2");
KeyedMatcher km2 = new KeyedMatcher("CLIN_SIG", Condition.NotContains, "path");
- assertEquals(km2.toString(), "CLIN_SIG Does not contain PATH");
+ assertEquals(km2.toString(), "CLIN_SIG Does not contain 'PATH'");
/*
* AND them
@@ -77,7 +79,7 @@ public class KeyedMatcherSetTest
assertEquals(kms.toString(), "(AF < 1.2)");
kms.and(km2);
assertEquals(kms.toString(),
- "(AF < 1.2) AND (CLIN_SIG Does not contain PATH)");
+ "(AF < 1.2) AND (CLIN_SIG Does not contain 'PATH')");
/*
* OR them
@@ -88,7 +90,7 @@ public class KeyedMatcherSetTest
assertEquals(kms.toString(), "(AF < 1.2)");
kms.or(km2);
assertEquals(kms.toString(),
- "(AF < 1.2) OR (CLIN_SIG Does not contain PATH)");
+ "(AF < 1.2) OR (CLIN_SIG Does not contain 'PATH')");
}
@Test
@@ -121,4 +123,35 @@ public class KeyedMatcherSetTest
kms.and(km);
assertFalse(kms.isEmpty());
}
+
+ @Test
+ public void testGetMatchers()
+ {
+ KeyedMatcherSetI kms = new KeyedMatcherSet();
+
+ /*
+ * empty iterable:
+ */
+ Iterator iterator = kms.getMatchers().iterator();
+ assertFalse(iterator.hasNext());
+
+ /*
+ * one matcher:
+ */
+ KeyedMatcherI km1 = new KeyedMatcher("AF", Condition.GE, -2F);
+ kms.and(km1);
+ iterator = kms.getMatchers().iterator();
+ assertSame(km1, iterator.next());
+ assertFalse(iterator.hasNext());
+
+ /*
+ * two matchers:
+ */
+ KeyedMatcherI km2 = new KeyedMatcher("AF", Condition.LT, 8F);
+ kms.and(km2);
+ iterator = kms.getMatchers().iterator();
+ assertSame(km1, iterator.next());
+ assertSame(km2, iterator.next());
+ assertFalse(iterator.hasNext());
+ }
}
diff --git a/test/jalview/util/matcher/MatcherTest.java b/test/jalview/util/matcher/MatcherTest.java
index d988c3a..489cdce 100644
--- a/test/jalview/util/matcher/MatcherTest.java
+++ b/test/jalview/util/matcher/MatcherTest.java
@@ -196,10 +196,10 @@ public class MatcherTest
assertEquals(m.toString(), "LT 1.2E-6");
m = new Matcher(Condition.NotMatches, "ABC");
- assertEquals(m.toString(), "NotMatches ABC");
+ assertEquals(m.toString(), "NotMatches 'ABC'");
m = new Matcher(Condition.Contains, -1.2f);
- assertEquals(m.toString(), "Contains -1.2");
+ assertEquals(m.toString(), "Contains '-1.2'");
}
@Test