001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021package org.biojava.nbio.structure.align.gui.aligpanel;
022
023import java.awt.Color;
024import java.awt.Font;
025import java.awt.Graphics;
026import java.awt.Graphics2D;
027import java.awt.Point;
028import java.awt.Rectangle;
029import java.awt.RenderingHints;
030import java.awt.event.ActionEvent;
031import java.awt.event.WindowEvent;
032import java.awt.event.WindowListener;
033import java.util.ArrayList;
034import java.util.BitSet;
035import java.util.List;
036
037import org.biojava.nbio.structure.Atom;
038import org.biojava.nbio.structure.StructureException;
039import org.biojava.nbio.structure.align.gui.JPrintPanel;
040import org.biojava.nbio.structure.align.gui.MenuCreator;
041import org.biojava.nbio.structure.align.gui.MultipleAlignmentJmolDisplay;
042import org.biojava.nbio.structure.align.gui.jmol.AbstractAlignmentJmol;
043import org.biojava.nbio.structure.align.gui.jmol.JmolTools;
044import org.biojava.nbio.structure.align.model.AFPChain;
045import org.biojava.nbio.structure.align.multiple.MultipleAlignment;
046import org.biojava.nbio.structure.align.multiple.MultipleAlignmentEnsembleImpl;
047import org.biojava.nbio.structure.align.multiple.util.MultipleAlignmentTools;
048import org.biojava.nbio.structure.align.multiple.util.MultipleAlignmentWriter;
049import org.biojava.nbio.structure.align.util.AFPAlignmentDisplay;
050import org.biojava.nbio.structure.gui.events.AlignmentPositionListener;
051import org.biojava.nbio.structure.gui.util.AlignedPosition;
052
053/**
054 * A JPanel that can display the sequence alignment of a
055 * {@link MultipleAlignment} in a nice way and interact with Jmol by
056 * selecting the aligned atoms of the  sequence selection.
057 * <p>
058 * Coloring options include: sequence similarity, by Block or by Structure.
059 * Colors are connected with the JmolPanel using the same pattelete.
060 * <p>
061 * The positions can be selected individually or in ranges and they will be
062 * translated to jmol commands.
063 *
064 * @author Aleix Lafita
065 * @since 4.1.0
066 *
067 */
068public class MultipleAligPanel extends JPrintPanel
069implements AlignmentPositionListener, WindowListener {
070
071        private static final long serialVersionUID = -6892229111166263764L;
072
073        private MultipleAlignment multAln;
074        private List<String> alnSeq; //sequence alignment
075        private List<Integer> mapSeqToStruct;  //mapping from sequence to structure
076
077        int size;                       //number of structures
078        int length;             //number of aligned positions in sequence alignment
079
080        private Font seqFont;
081        private Font eqFont;
082
083        private AbstractAlignmentJmol jmol;
084        private MultipleAligPanelMouseMotionListener mouseMoLi;
085        private MultipleAlignmentCoordManager coordManager;
086
087        private BitSet selection;
088        private boolean selectionLocked;
089
090        private boolean colorBySimilarity=false;
091        private boolean colorByAlignmentBlock=false;
092
093        private static final Color COLOR_EQUAL   = Color.decode("#6A93D4");
094        private static final Color COLOR_SIMILAR = Color.decode("#D460CF");
095
096        /**
097         * Default constructor. Empty MultipleAligPanel instance.
098         */
099        public MultipleAligPanel(){
100                super();
101                this.setBackground(Color.white);
102                seqFont = new Font("SansSerif",Font.PLAIN,12);
103                eqFont = new Font("SansSerif",Font.BOLD,12);
104
105                mouseMoLi = new MultipleAligPanelMouseMotionListener(this);
106                this.addMouseMotionListener(mouseMoLi);
107                this.addMouseListener(mouseMoLi);
108                mouseMoLi.addAligPosListener(this);
109                selection = new BitSet();
110
111                multAln = null;
112                alnSeq = null;
113                mapSeqToStruct = null;
114        }
115
116        /**
117         * Constructor using an afpChain and the atom arrays for pairwise
118         * alignments. The AFPChain is converted into a MultipleAlignment.
119         *
120         * @param afpChain
121         * @param ca1
122         * @param ca2
123         * @throws StructureException
124         */
125        public MultipleAligPanel(AFPChain afpChain, Atom[] ca1, Atom[] ca2,
126                        AbstractAlignmentJmol jmol) throws StructureException {
127
128                this();
129
130                String algorithm = afpChain.getAlgorithmName();
131                boolean flex = false;
132                if (algorithm != null){
133                        if (algorithm.contains("flexible")) flex = true;
134                }
135
136                //Convert the apfChain into a MultipleAlignment object
137                MultipleAlignmentEnsembleImpl ensemble =
138                                new MultipleAlignmentEnsembleImpl(afpChain, ca1, ca2, flex);
139                this.multAln = ensemble.getMultipleAlignment(0);
140
141                //Create the sequence alignment and the structure-sequence mapping.
142                this.mapSeqToStruct = new ArrayList<Integer>();
143                this.alnSeq = MultipleAlignmentTools.getSequenceAlignment(
144                                this.multAln, this.mapSeqToStruct);
145
146                //Initialize other memeber variables of the panel
147                this.size = multAln.size();
148                this.length = alnSeq.get(0).length();
149
150                coordManager = new MultipleAlignmentCoordManager(size, length);
151                this.jmol = jmol;
152        }
153
154        /**
155         * Constructor using a MultipleAlignment.
156         *
157         * @param multAln
158         * @param colors
159         */
160        public MultipleAligPanel(MultipleAlignment msa, AbstractAlignmentJmol jm) {
161                this();
162                this.multAln = msa;
163
164                //Create the sequence alignment and the structure-sequence mapping.
165                this.mapSeqToStruct = new ArrayList<Integer>();
166                this.alnSeq = MultipleAlignmentTools.getSequenceAlignment(
167                                this.multAln, this.mapSeqToStruct);
168
169                this.size = multAln.size();
170                this.length = this.alnSeq.get(0).length();
171
172                coordManager = new MultipleAlignmentCoordManager(size, length);
173                this.jmol = jm;
174        }
175
176        public MultipleAlignmentCoordManager getCoordManager() {
177                return coordManager;
178        }
179
180        public void addAlignmentPositionListener(AlignmentPositionListener li){
181                mouseMoLi.addAligPosListener(li);
182        }
183
184        public void destroy(){
185
186                multAln = null;
187                alnSeq = null;
188                mouseMoLi.destroy();
189                jmol = null;
190                selection = null;
191        }
192
193        @Override
194        public void paintComponent(Graphics g){
195
196                super.paintComponent(g);
197
198                Graphics2D g2D = (Graphics2D) g;
199
200                g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
201                                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
202
203                g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
204                                RenderingHints.VALUE_ANTIALIAS_ON);
205
206                int startpos = 0;
207                int endpos = length;
208
209                String summary = multAln.toString();
210                g2D.drawString(summary, 20, coordManager.getSummaryPos());
211
212                Color significantCol = Color.black;
213                //if (multAln.isSignificantResult()) significantCol = Color.green;
214
215                g2D.setPaint(significantCol);
216                Rectangle sig = new Rectangle(10,10,10,10);
217                g2D.fill(sig);
218
219                for (int i = startpos; i < endpos; i++){
220
221                        boolean isGapped = false;
222                        g2D.setFont(seqFont);
223
224                        if (mapSeqToStruct.get(i)!=-1) g2D.setFont(eqFont);
225                        else isGapped = true;
226
227                        //Loop through every structure to get all the points
228                        List<Point> points = new ArrayList<Point>();
229                        for (int str=0; str<size; str++) points.add(
230                                        coordManager.getPanelPos(str,i));
231                        Point p1 = points.get(0);
232                        Point p2 = points.get(points.size()-1);
233
234                        for (int str=0; str<size; str++){
235
236                                char c = alnSeq.get(str).charAt(i);
237                                Color bg = jmol.getColorPalette().getColorPalette(size)[str];
238
239                                //Color only if the position is aligned
240                                if (!isGapped){
241                                        //Color by sequence similarity (equal or similar)
242                                        if (colorBySimilarity){
243                                                boolean equal = true;
244                                                boolean similar = true;
245                                                char c1 = '-';
246                                                for (int st=0; st<size-1; st++){
247                                                        if (alnSeq.get(st).charAt(i) != '-') {
248                                                                c1 = alnSeq.get(st).charAt(i);
249                                                        }
250                                                        char c2 = alnSeq.get(st+1).charAt(i);
251                                                        //If any position is a gap continue
252                                                        if (c1=='-' || c2=='-' ||
253                                                                        Character.isLowerCase(c1) ||
254                                                                        Character.isLowerCase(c2)) {
255                                                                continue;
256                                                        }
257                                                        if (equal && c1 == c2)
258                                                                continue;
259                                                        else equal = false;
260                                                        if (AFPAlignmentDisplay.aaScore(c1, c2) > 0)
261                                                                continue;
262                                                        else similar = false; break;
263                                                }
264                                                if (equal) bg = COLOR_EQUAL;
265                                                else if (similar) bg = COLOR_SIMILAR;
266                                                else bg = Color.LIGHT_GRAY;
267                                        }
268                                        //Color by alignment block the same way as in the Jmol
269                                        else if (colorByAlignmentBlock){
270                                                int blockNr = MultipleAlignmentTools.
271                                                                getBlockForSequencePosition(
272                                                                                multAln,mapSeqToStruct,i);
273                                                bg = jmol.getColorPalette().getColorPalette(
274                                                                multAln.getBlocks().size())[blockNr];
275                                        }
276                                        if (isSelected(i)) bg = Color.YELLOW;
277
278                                        if (Character.isUpperCase(c) && c!='-'){
279                                                g2D.setPaint(bg);
280                                                Rectangle rec = new Rectangle(points.get(str).x-1,
281                                                                points.get(str).y-11, (p2.x-p1.x)+12,
282                                                                (p2.y-p1.y)/size);
283                                                g2D.fill(rec);
284                                        }
285                                }
286
287                                // draw the AA sequence
288                                g2D.setColor(Color.black);
289                                g2D.drawString(String.valueOf(c), points.get(str).x, points.get(str).y);
290                        }
291                }
292
293                int nrLines = (length-1) /
294                                (MultipleAlignmentCoordManager.DEFAULT_LINE_LENGTH);
295
296                for (int i = 0 ; i < nrLines+1 ; i++){
297
298                        // draw legend at i
299                        for (int str=0; str<size; str++){
300
301                                Point p1 = coordManager.getLegendPosition(i,str);
302
303                                int aligPos = i *
304                                                MultipleAlignmentCoordManager.DEFAULT_LINE_LENGTH;
305                                Atom a1 = null;
306                                while (a1==null &&
307                                                aligPos < Math.min((i+1)*MultipleAlignmentCoordManager.
308                                                                DEFAULT_LINE_LENGTH-1,length)){
309                                        a1 = MultipleAlignmentTools.getAtomForSequencePosition(
310                                                        multAln, mapSeqToStruct, str, aligPos);
311                                        aligPos++;
312                                }
313                                String label1 = JmolTools.getPdbInfo(a1,false);
314                                g2D.drawString(label1, p1.x,p1.y);
315
316                                Point p3 = coordManager.getEndLegendPosition(i,str);
317
318                                aligPos = (i*MultipleAlignmentCoordManager.DEFAULT_LINE_LENGTH+
319                                                MultipleAlignmentCoordManager.DEFAULT_LINE_LENGTH - 1);
320                                if (aligPos > length) aligPos = length-1;
321                                Atom a3 = null;
322                                while (a3==null && aligPos > Math.max(i*
323                                                MultipleAlignmentCoordManager.DEFAULT_LINE_LENGTH,0)){
324                                        a3 = MultipleAlignmentTools.getAtomForSequencePosition(
325                                                        multAln, mapSeqToStruct, str, aligPos);
326                                        aligPos--;
327                                }
328
329                                String label3 = JmolTools.getPdbInfo(a3,false);
330
331                                g2D.drawString(label3, p3.x,p3.y);
332                        }
333                }
334        }
335
336        private boolean isSelected(int alignmentPosition) {
337                return selection.get(alignmentPosition);
338        }
339
340        @Override
341        public void mouseOverPosition(AlignedPosition p) {
342
343                if (!selectionLocked) selection.clear();
344
345                selection.set(p.getPos1());
346                updateJmolDisplay();
347                this.repaint();
348        }
349
350        private void updateJmolDisplay() {
351
352                if (jmol == null) return;
353
354                StringBuffer cmd = new StringBuffer("select ");
355                int nrSelected = 0;
356                for (int i=0; i<length; i++){
357                        if (selection.get(i)){
358                                for (int str=0; str<size; str++){
359                                        Atom a = MultipleAlignmentTools.getAtomForSequencePosition(
360                                                        multAln, mapSeqToStruct,str,i);
361                                        if (a != null) {
362                                                cmd.append(JmolTools.getPdbInfo(a));
363                                                cmd.append("/"+(str+1)+", ");
364                                        }
365                                }
366                                nrSelected++;
367                        }
368                }
369                if (nrSelected == 0) cmd.append(" none;");
370                else cmd.append(" none; set display selected;");
371                //System.out.println(cmd.toString());
372                jmol.evalString(cmd.toString());
373        }
374
375
376        @Override
377        public void positionSelected(AlignedPosition p) {
378                mouseOverPosition(p);
379        }
380
381        @Override
382        public void rangeSelected(AlignedPosition start, AlignedPosition end) {
383
384                if (!selectionLocked) selection.clear();
385                selection.set(start.getPos1(), end.getPos1()+1);
386                updateJmolDisplay();
387                this.repaint();
388        }
389
390        @Override
391        public void selectionLocked() {
392                selectionLocked = true;
393        }
394
395        @Override
396        public void selectionUnlocked() {
397                selectionLocked = false;
398                selection.clear();
399                this.repaint();
400        }
401
402        @Override
403        public void toggleSelection(AlignedPosition p) {
404                selection.flip(p.getPos1());
405                updateJmolDisplay();
406                this.repaint();
407        }
408
409        public void setStructureAlignmentJmol(AbstractAlignmentJmol jmol) {
410                this.jmol = jmol;
411
412        }
413
414        @Override
415        public void windowActivated(WindowEvent e) {}
416
417        @Override
418        public void windowClosed(WindowEvent e) {}
419
420        @Override
421        public void windowClosing(WindowEvent e) {
422                destroy();
423        }
424
425        @Override
426        public void windowDeactivated(WindowEvent e) {}
427
428        @Override
429        public void windowDeiconified(WindowEvent e) {}
430
431        @Override
432        public void windowIconified(WindowEvent e) {}
433
434        @Override
435        public void windowOpened(WindowEvent e) {}
436
437        @Override
438        public void actionPerformed(ActionEvent e) {
439                String cmd = e.getActionCommand();
440                if ( cmd.equals(MenuCreator.PRINT)) {
441                        super.actionPerformed(e);
442                } else if (cmd.equals(MenuCreator.FASTA_FORMAT)){
443                        String result = MultipleAlignmentWriter.toFASTA(multAln);
444                        MultipleAlignmentJmolDisplay.showAlignmentImage(multAln, result);
445                } else if ( cmd.equals(MenuCreator.PAIRS_ONLY)) {
446                        String result = MultipleAlignmentWriter.toAlignedResidues(multAln);
447                        MultipleAlignmentJmolDisplay.showAlignmentImage(multAln, result);
448                } else if (cmd.equals(MenuCreator.FATCAT_TEXT)){
449                        String result = MultipleAlignmentWriter.toFatCat(multAln);
450                        MultipleAlignmentJmolDisplay.showAlignmentImage(multAln, result);
451                } else if (cmd.equals(MenuCreator.SELECT_EQR)){
452                        selectEQR();
453                } else if ( cmd.equals(MenuCreator.SIMILARITY_COLOR)){
454                        colorBySimilarity(true);
455                } else if (cmd.equals(MenuCreator.EQR_COLOR)){
456                        colorBySimilarity(false);
457                } else if ( cmd.equals(MenuCreator.FATCAT_BLOCK)){
458                        colorByAlignmentBlock();
459                } else {
460                        System.err.println("Unknown command:" + cmd);
461                }
462        }
463
464        private void selectEQR() {
465
466                selection.clear();
467
468                for (int pos=0; pos<length; pos++){
469                        if (mapSeqToStruct.get(pos)!=-1) selection.flip(pos);
470                }
471                mouseMoLi.triggerSelectionLocked(true);
472                updateJmolDisplay();
473                this.repaint();
474        }
475
476        private void colorByAlignmentBlock() {
477                colorByAlignmentBlock = true;
478                colorBySimilarity = false;
479                this.repaint();
480        }
481
482        private void colorBySimilarity(boolean flag) {
483                this.colorBySimilarity = flag;
484                colorByAlignmentBlock = false;
485                this.repaint();
486        }
487
488        public List<Atom[]> getAtomArrays() {
489                return multAln.getAtomArrays();
490        }
491        public MultipleAlignment getMultipleAlignment(){
492                return multAln;
493        }
494        public List<String> getAlnSequences(){
495                return alnSeq;
496        }
497        public List<Integer> getMapSeqToStruct(){
498                return mapSeqToStruct;
499        }
500}