From d2e7e2612a39826b30cf92889441ce99b36e7a40 Mon Sep 17 00:00:00 2001 From: jprocter Date: Tue, 31 Mar 2009 16:40:22 +0000 Subject: [PATCH] added feature gradient colours and sortby feature score/density to feature settings dialog. --- src/jalview/appletgui/FeatureColourChooser.java | 379 +++++++++++++++++++++++ src/jalview/appletgui/FeatureRenderer.java | 109 ++++++- src/jalview/appletgui/FeatureSettings.java | 156 +++++++++- src/jalview/appletgui/UserDefinedColours.java | 9 + 4 files changed, 646 insertions(+), 7 deletions(-) create mode 100644 src/jalview/appletgui/FeatureColourChooser.java diff --git a/src/jalview/appletgui/FeatureColourChooser.java b/src/jalview/appletgui/FeatureColourChooser.java new file mode 100644 index 0000000..d389b9a --- /dev/null +++ b/src/jalview/appletgui/FeatureColourChooser.java @@ -0,0 +1,379 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer (Version 2.4) + * Copyright (C) 2008 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ +package jalview.appletgui; + +import java.util.*; + +import java.awt.*; +import java.awt.event.*; + +import jalview.datamodel.*; +import jalview.schemes.*; +import java.awt.Rectangle; + +public class FeatureColourChooser extends Panel implements + ActionListener, AdjustmentListener, ItemListener, MouseListener +{ + Frame frame; + + FeatureRenderer fr; + FeatureSettings fs; + AlignmentPanel ap; + + GraduatedColor cs; + Object oldcs; + + Hashtable oldgroupColours; + + + boolean adjusting = false; + private float min,max; + String type=null; + + public FeatureColourChooser(FeatureSettings fsettings, String type) + { + this.fs = fsettings; + this.type = type; + fr = fsettings.fr; + ap = fsettings.ap; + float mm[] = ((float[][]) fr.minmax.get(type))[0]; + min = mm[0]; + max = mm[1]; + oldcs = fr.featureColours.get(type); + if (oldcs instanceof GraduatedColor) + { + cs = new GraduatedColor((GraduatedColor) oldcs, min, max); + } else { + // promote original color to a graduated color + Color bl = Color.black; + if (oldcs instanceof Color) + { + bl = (Color) oldcs; + } + // original colour becomes the maximum colour + cs = new GraduatedColor(Color.white,bl,mm[0],mm[1]); + } + minColour.setBackground(cs.getMinColor()); + maxColour.setBackground(cs.getMaxColor()); + adjusting = true; + + try + { + jbInit(); + } catch (Exception ex) + { + } + // To HERE! + adjusting = false; + changeColour(); + slider.addAdjustmentListener(this); + slider.addMouseListener(this); + frame = new Frame(); + frame.add(this); + jalview.bin.JalviewLite.addFrame(frame, "Graduated Feature Colour for "+type, 480, + 145); + validate(); + } + + public FeatureColourChooser() + { + try + { + jbInit(); + } catch (Exception ex) + { + ex.printStackTrace(); + } + } + + private void jbInit() throws Exception + { + minColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); + minColour.setLabel("Min Colour"); + minColour.addActionListener(this); + + maxColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); + maxColour.setLabel("Max Colour"); + maxColour.addActionListener(this); + + thresholdIsMin.addItemListener(this); + ok.setLabel("OK"); + ok.addActionListener(this); + + cancel.setLabel("Cancel"); + cancel.addActionListener(this); + + this.setLayout(borderLayout1); + jPanel2.setLayout(flowLayout1); + + jPanel1.setBackground(Color.white); + jPanel2.setBackground(Color.white); + threshold.addItemListener(this); + threshold.addItem("No Threshold"); + threshold.addItem("Above Threshold"); + threshold.addItem("Below Threshold"); + jPanel3.setLayout(null); + thresholdValue.addActionListener(this); + + slider.setBackground(Color.white); + slider.setEnabled(false); + slider.setBounds(new Rectangle(153, 3, 93, 21)); + thresholdValue.setEnabled(false); + thresholdValue.setBounds(new Rectangle(248, 2, 79, 22)); + thresholdValue.setColumns(5); + jPanel3.setBackground(Color.white); + //currentColours.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11)); + //currentColours.setLabel("Use Original Colours"); + //currentColours.addItemListener(this); + + threshold.setBounds(new Rectangle(11, 3, 139, 22)); + thresholdIsMin.setBackground(Color.white); + thresholdIsMin.setLabel("Threshold is min/max"); + thresholdIsMin.setBounds(new Rectangle(328, 3, 135, 23)); + jPanel1.add(ok); + jPanel1.add(cancel); + //jPanel2.add(currentColours); + jPanel2.add(minColour); + jPanel2.add(maxColour); + jPanel3.add(threshold); + jPanel3.add(slider); + jPanel3.add(thresholdValue); + jPanel3.add(thresholdIsMin); + this.add(jPanel2, java.awt.BorderLayout.NORTH); + this.add(jPanel3, java.awt.BorderLayout.CENTER); + this.add(jPanel1, java.awt.BorderLayout.SOUTH); + } + + Button minColour = new Button(); + + Button maxColour = new Button(); + + Button ok = new Button(); + + Button cancel = new Button(); + + Panel jPanel1 = new Panel(); + + Panel jPanel2 = new Panel(); + + Choice threshold = new Choice(); + + FlowLayout flowLayout1 = new FlowLayout(); + + Panel jPanel3 = new Panel(); + + Scrollbar slider = new Scrollbar(Scrollbar.HORIZONTAL); + + TextField thresholdValue = new TextField(20); + + + BorderLayout borderLayout1 = new BorderLayout(); + + Checkbox thresholdIsMin = new Checkbox(); + + private GraphLine threshline; + + public void actionPerformed(ActionEvent evt) + { + if (evt.getSource() == thresholdValue) + { + try + { + float f = new Float(thresholdValue.getText()).floatValue(); + slider.setValue((int) (f * 1000)); + adjustmentValueChanged(null); + } catch (NumberFormatException ex) + { + } + } + else if (evt.getSource() == minColour) + { + minColour_actionPerformed(null); + } + else if (evt.getSource() == maxColour) + { + maxColour_actionPerformed(null); + } + + else if (evt.getSource() == ok) + { + changeColour(); + frame.setVisible(false); + } + else if (evt.getSource() == cancel) + { + reset(); + ap.paintAlignment(true); + frame.setVisible(false); + } + + else + { + changeColour(); + } + } + + public void itemStateChanged(ItemEvent evt) + { + + changeColour(); + } + + public void adjustmentValueChanged(AdjustmentEvent evt) + { + if (!adjusting) + { + thresholdValue.setText(((float) slider.getValue() / 1000f) + ""); + valueChanged(); + } + } + protected void valueChanged() { + changeColour(); + threshline.value = (float) slider.getValue() / 1000f; + ap.paintAlignment(false); + } + public void minColour_actionPerformed(Color newCol) + { + if (newCol != null) + { + minColour.setBackground(newCol); + minColour.repaint(); + changeColour(); + } + else + { + new UserDefinedColours(this, "Select Colour for Minimum Value", minColour.getBackground()); + } + + } + + public void maxColour_actionPerformed(Color newCol) + { + if (newCol != null) + { + maxColour.setBackground(newCol); + maxColour.repaint(); + changeColour(); + } + else + { + new UserDefinedColours(this, "Select Colour for Maximum Value", maxColour.getBackground()); + } + } + + void changeColour() + { + // Check if combobox is still adjusting + if (adjusting) + { + return; + } + + int aboveThreshold = AnnotationColourGradient.NO_THRESHOLD; + if (threshold.getSelectedItem().equals("Above Threshold")) + { + aboveThreshold = AnnotationColourGradient.ABOVE_THRESHOLD; + } + else if (threshold.getSelectedItem().equals("Below Threshold")) + { + aboveThreshold = AnnotationColourGradient.BELOW_THRESHOLD; + } + + slider.setEnabled(true); + thresholdValue.setEnabled(true); + GraduatedColor acg = new GraduatedColor(minColour.getBackground(), maxColour.getBackground(), min, max); + + if (aboveThreshold == AnnotationColourGradient.NO_THRESHOLD) + { + slider.setEnabled(false); + thresholdValue.setEnabled(false); + thresholdValue.setText(""); + } + + else if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD + && threshline == null) + { + // todo visual indication of feature threshold + threshline = new jalview.datamodel.GraphLine( + (max - min) / 2f, + "Threshold", Color.black); + } + + if (aboveThreshold != AnnotationColourGradient.NO_THRESHOLD) + { + adjusting = true; + acg.setThresh(threshline.value); + + float range = max * 1000f + - min * 1000f; + + slider.setMinimum((int) (min * 1000)); + slider.setMaximum((int) (max * 1000)); + slider.setValue((int) (threshline.value * 1000)); + thresholdValue.setText(threshline.value + ""); + slider.setEnabled(true); + thresholdValue.setEnabled(true); + adjusting = false; + } + + acg.setThreshType(aboveThreshold); + if (thresholdIsMin.getState() && aboveThreshold != AnnotationColourGradient.NO_THRESHOLD) + { + if (aboveThreshold==AnnotationColourGradient.ABOVE_THRESHOLD) + { + acg = new GraduatedColor(acg, threshline.value, max); + } else { + acg = new GraduatedColor(acg, min,threshline.value); + } + } + + fr.featureColours.put(type,acg); + cs = acg; + ap.paintAlignment(false); + } + + void reset() + { + fr.featureColours.put(type, oldcs); + ap.paintAlignment(true); + + } + + public void mouseClicked(MouseEvent evt) + { + } + + public void mousePressed(MouseEvent evt) + { + } + + public void mouseReleased(MouseEvent evt) + { + ap.paintAlignment(true); + } + + public void mouseEntered(MouseEvent evt) + { + } + + public void mouseExited(MouseEvent evt) + { + } + +} diff --git a/src/jalview/appletgui/FeatureRenderer.java b/src/jalview/appletgui/FeatureRenderer.java index cb9736e..249dd4a 100755 --- a/src/jalview/appletgui/FeatureRenderer.java +++ b/src/jalview/appletgui/FeatureRenderer.java @@ -26,6 +26,7 @@ import java.awt.event.*; import jalview.appletgui.FeatureSettings.MyCheckbox; import jalview.datamodel.*; +import jalview.schemes.GraduatedColor; /** * DOCUMENT ME! @@ -542,8 +543,8 @@ public class FeatureRenderer if (sequenceFeatures[sfindex].begin <= start && sequenceFeatures[sfindex].end >= start) { - currentColour = av.featuresDisplayed - .get(sequenceFeatures[sfindex].type); + currentColour = new Integer(getColour(sequenceFeatures[sfindex]).getRGB());//av.featuresDisplayed + //.get(sequenceFeatures[sfindex].type); } } @@ -573,7 +574,7 @@ public class FeatureRenderer renderFeature(g, seq, seq .findIndex(sequenceFeatures[sfindex].begin) - 1, seq .findIndex(sequenceFeatures[sfindex].end) - 1, - getColour(sequenceFeatures[sfindex].type), start, end, y1); + getColour(sequenceFeatures[sfindex]), start, end, y1); } } @@ -633,13 +634,15 @@ public class FeatureRenderer } } } - + Hashtable minmax=null; void findAllFeatures() { jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(); av.featuresDisplayed = new Hashtable(); Vector allfeatures = new Vector(); + minmax = new Hashtable(); + for (int i = 0; i < av.alignment.getHeight(); i++) { SequenceFeature[] features = av.alignment.getSequenceAt(i) @@ -653,6 +656,10 @@ public class FeatureRenderer int index = 0; while (index < features.length) { + if (features[index].begin==0 && features[index].end==0) { + index++; + continue; + } if (!av.featuresDisplayed.containsKey(features[index].getType())) { if (getColour(features[index].getType()) == null) @@ -665,6 +672,33 @@ public class FeatureRenderer getColour(features[index].getType()).getRGB())); allfeatures.addElement(features[index].getType()); } + if (features[index].score != Float.NaN) + { + int nonpos= features[index].getBegin()>=1 ? 0 : 1; + float[][] mm = (float[][]) minmax.get(features[index].getType()); + if (mm == null) + { + mm = new float[][] {null, null }; + minmax.put(features[index].getType(), mm); + } + if (mm[nonpos]==null) + { + mm[nonpos] = new float[] { features[index].score, features[index].score }; + + } + else + { + if (mm[nonpos][0] > features[index].score) + { + mm[nonpos][0] = features[index].score; + } + if (mm[nonpos][1] < features[index].score) + { + mm[nonpos][1] = features[index].score; + } + } + } + index++; } } @@ -681,15 +715,57 @@ public class FeatureRenderer public Color getColour(String featureType) { - if (!featureColours.containsKey(featureType)) + Object fc = featureColours.get(featureType); + if (fc == null) { jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(); Color col = ucs.createColourFromName(featureType); featureColours.put(featureType, col); return col; } + else if (fc instanceof Color) + { + return (Color) fc; + } else - return (Color) featureColours.get(featureType); + { + if (fc instanceof GraduatedColor) + { + return ((GraduatedColor) fc).getMinColor(); + } + // TODO: raise an implementation error here. + return null; // Color.white; + } + } + /** + * implement graduated colouring for features with scores + * + * @param feature + * @return render colour for the given feature + */ + public Color getColour(SequenceFeature feature) + { + Object fc = featureColours.get(feature.type); + if (fc == null) + { + jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(); + Color col = ucs.createColourFromName(feature.type); + featureColours.put(feature.type, col); + return col; + } + else if (fc instanceof Color) + { + return (Color) fc; + } + else + { + if (fc instanceof GraduatedColor) + { + return ((GraduatedColor) fc).findColor(feature); + } + // TODO: raise an implementation error here. + return null; // Color.white; + } } public void setColour(String featureType, Color col) @@ -973,6 +1049,27 @@ public class FeatureRenderer return alignmentHasFeatures; } + /** + * + * @return the displayed feature type as an array of strings + */ + protected String[] getDisplayedFeatureTypes() + { + String[] typ = null; + synchronized (renderOrder) + { + typ = new String[renderOrder.length]; + System.arraycopy(renderOrder, 0, typ, 0, typ.length); + for (int i = 0; i < typ.length; i++) + { + if (av.featuresDisplayed.get(typ[i]) == null) + { + typ[i] = null; + } + } + } + return typ; + } } class TransparencySetter diff --git a/src/jalview/appletgui/FeatureSettings.java b/src/jalview/appletgui/FeatureSettings.java index 4649786..f71159d 100755 --- a/src/jalview/appletgui/FeatureSettings.java +++ b/src/jalview/appletgui/FeatureSettings.java @@ -23,6 +23,8 @@ import java.util.*; import java.awt.*; import java.awt.event.*; +import jalview.analysis.AlignmentSorter; +import jalview.commands.OrderCommand; import jalview.datamodel.*; public class FeatureSettings extends Panel implements ItemListener, @@ -152,6 +154,78 @@ public class FeatureSettings extends Panel implements ItemListener, g.drawString("(Features can be added from searches or", 10, 40); g.drawString("from Jalview / GFF features files)", 10, 60); } + protected void popupSort(final String type, final Hashtable minmax, + int x, int y) + { + java.awt.PopupMenu men = new PopupMenu("Settings for " + type); + java.awt.MenuItem scr = new MenuItem("Sort by Score"); + men.add(scr); + final FeatureSettings me = this; + scr.addActionListener(new ActionListener() + { + + public void actionPerformed(ActionEvent e) + { + me.sortByScore(new String[] + { type }); + } + + }); + MenuItem dens = new MenuItem("Sort by Density"); + dens.addActionListener(new ActionListener() + { + + public void actionPerformed(ActionEvent e) + { + me.sortByDens(new String[] + { type }); + } + + }); + men.add(dens); + if (minmax != null) + { + final Object typeMinMax = minmax.get(type); + final java.awt.CheckboxMenuItem chb = new java.awt.CheckboxMenuItem("Vary Height"); + // this is broken at the moment + chb.setState(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 && ((float[][]) typeMinMax)[0] != null) + { + // graduated colourschemes for those where minmax exists for the positional features + MenuItem mxcol = new MenuItem("Min Max Colour"); + men.add(mxcol); + mxcol.addActionListener(new ActionListener() + { + + public void actionPerformed(ActionEvent e) + { + new FeatureColourChooser(me, type); + } + + }); + } + } + this.featurePanel.add(men); + men.show(this.featurePanel, x, y); + } public void setTableData() { @@ -502,7 +576,10 @@ public class FeatureSettings extends Panel implements ItemListener, public void mouseClicked(MouseEvent evt) { MyCheckbox check = (MyCheckbox) evt.getSource(); - + if ((evt.getModifiers() & InputEvent.BUTTON3_MASK)!=0) + { + this.popupSort(check.getLabel(), fr.minmax, evt.getX(), evt.getY()); + } if (fr.featureLinks != null && fr.featureLinks.containsKey(check.getLabel())) { @@ -562,4 +639,81 @@ public class FeatureSettings extends Panel implements ItemListener, } } } + protected void sortByDens(String[] typ) + { + sortBy(typ, "Sort by Density", AlignmentSorter.FEATURE_DENSITY); + } + private String[] getDisplayedFeatureTypes() + { + String[] typ = null; + if (fr != null) + { + synchronized (fr.renderOrder) + { + typ = new String[fr.renderOrder.length]; + System.arraycopy(fr.renderOrder, 0, typ, 0, typ.length); + for (int i = 0; i < typ.length; i++) + { + if (av.featuresDisplayed.get(typ[i]) == null) + { + typ[i] = null; + } + } + } + } + return typ; + } + + + protected void sortBy(String[] typ, String methodText, final String method) + { + if (typ == null) + { + typ = getDisplayedFeatureTypes(); + } + String gps[] = null; + gps = fr.getGroups(true); + if (typ != null) + { + for (int i = 0; i < typ.length; i++) + { + System.err.println("Sorting on Types:" + typ[i]); + } + } + if (gps != null) + { + + for (int i = 0; i < gps.length; i++) + { + System.err.println("Sorting on groups:" + gps[i]); + } + } + AlignmentPanel alignPanel = ap; + AlignmentI al = alignPanel.av.getAlignment(); + + int start, stop; + SequenceGroup sg = alignPanel.av.getSelectionGroup(); + if (sg != null) + { + start = sg.getStartRes(); + stop = sg.getEndRes(); + } + else + { + start = 0; + stop = al.getWidth(); + } + SequenceI[] oldOrder = al.getSequencesArray(); + AlignmentSorter.sortByFeature(typ, gps, start, stop, al, method); + this.ap.alignFrame.addHistoryItem(new OrderCommand(methodText, oldOrder, alignPanel.av + .getAlignment())); + alignPanel.paintAlignment(true); + + } + + protected void sortByScore(String[] typ) + { + sortBy(typ, "Sort by Feature Score", AlignmentSorter.FEATURE_SCORE); + } + } diff --git a/src/jalview/appletgui/UserDefinedColours.java b/src/jalview/appletgui/UserDefinedColours.java index 0f30539..e8e0e57 100755 --- a/src/jalview/appletgui/UserDefinedColours.java +++ b/src/jalview/appletgui/UserDefinedColours.java @@ -383,6 +383,15 @@ public class UserDefinedColours extends Panel implements ActionListener, { ((FeatureRenderer) caller).colourPanel.setBackground(getColor()); } + else if (caller instanceof FeatureColourChooser) + { + if (originalLabel.indexOf("inimum")>-1) + { + ((FeatureColourChooser) caller).minColour_actionPerformed(getColor()); + } else { + ((FeatureColourChooser) caller).maxColour_actionPerformed(getColor()); + } + } return; } -- 1.7.10.2