Merge branch 'JAL-1013_pca_rna_dna' into develop
[jalview.git] / src / jalview / gui / PCAPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)
3  * Copyright (C) 2011 J Procter, AM Waterhouse, J Engelhardt, LM Lui, G Barton, M Clamp, S Searle
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.gui;
19
20 import java.util.*;
21
22 import java.awt.*;
23 import java.awt.event.*;
24 import java.awt.print.*;
25 import javax.swing.*;
26
27 import jalview.analysis.*;
28 import jalview.datamodel.*;
29 import jalview.jbgui.*;
30
31 /**
32  * DOCUMENT ME!
33  * 
34  * @author $author$
35  * @version $Revision$
36  */
37 public class PCAPanel extends GPCAPanel implements Runnable
38 {
39
40   PCA pca;
41
42   int top;
43
44   RotatableCanvas rc;
45
46   AlignmentPanel ap;
47
48   AlignViewport av;
49
50   AlignmentView seqstrings;
51
52   SequenceI[] seqs;
53
54   /**
55    * use the identity matrix for calculating similarity between sequences. 
56    */
57   private boolean nucleotide=false;
58
59   /**
60    * Creates a new PCAPanel object.
61    * 
62    * @param av
63    *          DOCUMENT ME!
64    * @param s
65    *          DOCUMENT ME!
66    */
67   public PCAPanel(AlignmentPanel ap)
68   {
69     this.av = ap.av;
70     this.ap = ap;
71
72     boolean sameLength = true;
73
74     seqstrings = av.getAlignmentView(av.getSelectionGroup() != null);
75     nucleotide=av.getAlignment().isNucleotide();
76     if (av.getSelectionGroup() == null)
77     {
78       seqs = av.getAlignment().getSequencesArray();
79     }
80     else
81     {
82       seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment());
83     }
84     SeqCigar sq[] = seqstrings.getSequences();
85     int length = sq[0].getWidth();
86
87     for (int i = 0; i < seqs.length; i++)
88     {
89       if (sq[i].getWidth() != length)
90       {
91         sameLength = false;
92         break;
93       }
94     }
95
96     if (!sameLength)
97     {
98       JOptionPane
99               .showMessageDialog(
100                       Desktop.desktop,
101                       "The sequences must be aligned before calculating PCA.\n"
102                               + "Try using the Pad function in the edit menu,\n"
103                               + "or one of the multiple sequence alignment web services.",
104                       "Sequences not aligned", JOptionPane.WARNING_MESSAGE);
105
106       return;
107     }
108
109     PaintRefresher.Register(this, av.getSequenceSetId());
110
111     rc = new RotatableCanvas(ap);
112     this.getContentPane().add(rc, BorderLayout.CENTER);
113     Thread worker = new Thread(this);
114     worker.start();
115   }
116
117   public void bgcolour_actionPerformed(ActionEvent e)
118   {
119     Color col = JColorChooser.showDialog(this, "Select Background Colour",
120             rc.bgColour);
121
122     if (col != null)
123     {
124       rc.bgColour = col;
125     }
126     rc.repaint();
127   }
128
129   /**
130    * DOCUMENT ME!
131    */
132   public void run()
133   {
134     try
135     {
136       calcSettings.setEnabled(false);
137       pca = new PCA(seqstrings.getSequenceStrings(' '), nucleotide);
138       pca.run();
139
140       // Now find the component coordinates
141       int ii = 0;
142
143       while ((ii < seqs.length) && (seqs[ii] != null))
144       {
145         ii++;
146       }
147
148       double[][] comps = new double[ii][ii];
149
150       for (int i = 0; i < ii; i++)
151       {
152         if (pca.getEigenvalue(i) > 1e-4)
153         {
154           comps[i] = pca.component(i);
155         }
156       }
157
158       // ////////////////
159       xCombobox.setSelectedIndex(0);
160       yCombobox.setSelectedIndex(1);
161       zCombobox.setSelectedIndex(2);
162
163       top = pca.getM().rows - 1;
164
165       Vector points = new Vector();
166       float[][] scores = pca.getComponents(top - 1, top - 2, top - 3, 100);
167
168       for (int i = 0; i < pca.getM().rows; i++)
169       {
170         SequencePoint sp = new SequencePoint(seqs[i], scores[i]);
171         points.addElement(sp);
172       }
173
174       rc.setPoints(points, pca.getM().rows);
175       // rc.invalidate();
176       nuclSetting.setSelected(nucleotide);
177       protSetting.setSelected(!nucleotide);
178
179     } catch (OutOfMemoryError er)
180     {
181       new OOMWarning("calculating PCA", er);
182       return;
183     }
184     calcSettings.setEnabled(true);
185     repaint();
186     if (getParent()==null)
187     {
188       addKeyListener(rc);
189       Desktop.addInternalFrame(this, "Principal component analysis", 400, 400);
190     }
191   }
192   @Override
193   protected void nuclSetting_actionPerfomed(ActionEvent arg0)
194   {
195     nucleotide=true;
196     Thread worker = new Thread(this);
197     worker.start();
198   }
199   @Override
200   protected void protSetting_actionPerfomed(ActionEvent arg0)
201   {
202     nucleotide=false;
203     Thread worker = new Thread(this);
204     worker.start();
205   }
206   /**
207    * DOCUMENT ME!
208    */
209   void doDimensionChange()
210   {
211     if (top == 0)
212     {
213       return;
214     }
215
216     int dim1 = top - xCombobox.getSelectedIndex();
217     int dim2 = top - yCombobox.getSelectedIndex();
218     int dim3 = top - zCombobox.getSelectedIndex();
219
220     float[][] scores = pca.getComponents(dim1, dim2, dim3, 100);
221
222     for (int i = 0; i < pca.getM().rows; i++)
223     {
224       ((SequencePoint) rc.points.elementAt(i)).coord = scores[i];
225     }
226
227     rc.img = null;
228     rc.rotmat.setIdentity();
229     rc.initAxes();
230     rc.paint(rc.getGraphics());
231   }
232
233   /**
234    * DOCUMENT ME!
235    * 
236    * @param e
237    *          DOCUMENT ME!
238    */
239   protected void xCombobox_actionPerformed(ActionEvent e)
240   {
241     doDimensionChange();
242   }
243
244   /**
245    * DOCUMENT ME!
246    * 
247    * @param e
248    *          DOCUMENT ME!
249    */
250   protected void yCombobox_actionPerformed(ActionEvent e)
251   {
252     doDimensionChange();
253   }
254
255   /**
256    * DOCUMENT ME!
257    * 
258    * @param e
259    *          DOCUMENT ME!
260    */
261   protected void zCombobox_actionPerformed(ActionEvent e)
262   {
263     doDimensionChange();
264   }
265
266   public void outputValues_actionPerformed(ActionEvent e)
267   {
268     CutAndPasteTransfer cap = new CutAndPasteTransfer();
269     try
270     {
271       cap.setText(pca.getDetails());
272       Desktop.addInternalFrame(cap, "PCA details", 500, 500);
273     } catch (OutOfMemoryError oom)
274     {
275       new OOMWarning("opening PCA details", oom);
276       cap.dispose();
277     }
278   }
279
280   public void showLabels_actionPerformed(ActionEvent e)
281   {
282     rc.showLabels(showLabels.getState());
283   }
284
285   public void print_actionPerformed(ActionEvent e)
286   {
287     PCAPrinter printer = new PCAPrinter();
288     printer.start();
289   }
290
291   public void originalSeqData_actionPerformed(ActionEvent e)
292   {
293     // this was cut'n'pasted from the equivalent TreePanel method - we should
294     // make this an abstract function of all jalview analysis windows
295     if (seqstrings == null)
296     {
297       jalview.bin.Cache.log
298               .info("Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
299       return;
300     }
301     // decide if av alignment is sufficiently different to original data to
302     // warrant a new window to be created
303     // create new alignmnt window with hidden regions (unhiding hidden regions
304     // yields unaligned seqs)
305     // or create a selection box around columns in alignment view
306     // test Alignment(SeqCigar[])
307     char gc = '-';
308     try
309     {
310       // we try to get the associated view's gap character
311       // but this may fail if the view was closed...
312       gc = av.getGapCharacter();
313     } catch (Exception ex)
314     {
315     }
316     ;
317     Object[] alAndColsel = seqstrings.getAlignmentAndColumnSelection(gc);
318
319     if (alAndColsel != null && alAndColsel[0] != null)
320     {
321       // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
322
323       Alignment al = new Alignment((SequenceI[]) alAndColsel[0]);
324       Alignment dataset = (av != null && av.getAlignment() != null) ? av
325               .getAlignment().getDataset() : null;
326       if (dataset != null)
327       {
328         al.setDataset(dataset);
329       }
330
331       if (true)
332       {
333         // make a new frame!
334         AlignFrame af = new AlignFrame(al,
335                 (ColumnSelection) alAndColsel[1], AlignFrame.DEFAULT_WIDTH,
336                 AlignFrame.DEFAULT_HEIGHT);
337
338         // >>>This is a fix for the moment, until a better solution is
339         // found!!<<<
340         // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
341
342         // af.addSortByOrderMenuItem(ServiceName + " Ordering",
343         // msaorder);
344
345         Desktop.addInternalFrame(af, "Original Data for " + this.title,
346                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
347       }
348     }
349     /*
350      * CutAndPasteTransfer cap = new CutAndPasteTransfer(); for (int i = 0; i <
351      * seqs.length; i++) { cap.appendText(new jalview.util.Format("%-" + 15 +
352      * "s").form( seqs[i].getName())); cap.appendText(" " + seqstrings[i] +
353      * "\n"); }
354      * 
355      * Desktop.addInternalFrame(cap, "Original Data", 400, 400);
356      */
357   }
358
359   class PCAPrinter extends Thread implements Printable
360   {
361     public void run()
362     {
363       PrinterJob printJob = PrinterJob.getPrinterJob();
364       PageFormat pf = printJob.pageDialog(printJob.defaultPage());
365
366       printJob.setPrintable(this, pf);
367
368       if (printJob.printDialog())
369       {
370         try
371         {
372           printJob.print();
373         } catch (Exception PrintException)
374         {
375           PrintException.printStackTrace();
376         }
377       }
378     }
379
380     public int print(Graphics pg, PageFormat pf, int pi)
381             throws PrinterException
382     {
383       pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
384
385       rc.drawBackground(pg, rc.bgColour);
386       rc.drawScene(pg);
387       if (rc.drawAxes == true)
388       {
389         rc.drawAxes(pg);
390       }
391
392       if (pi == 0)
393       {
394         return Printable.PAGE_EXISTS;
395       }
396       else
397       {
398         return Printable.NO_SUCH_PAGE;
399       }
400     }
401   }
402
403   /**
404    * DOCUMENT ME!
405    * 
406    * @param e
407    *          DOCUMENT ME!
408    */
409   public void eps_actionPerformed(ActionEvent e)
410   {
411     makePCAImage(jalview.util.ImageMaker.EPS);
412   }
413
414   /**
415    * DOCUMENT ME!
416    * 
417    * @param e
418    *          DOCUMENT ME!
419    */
420   public void png_actionPerformed(ActionEvent e)
421   {
422     makePCAImage(jalview.util.ImageMaker.PNG);
423   }
424
425   void makePCAImage(int type)
426   {
427     int width = rc.getWidth();
428     int height = rc.getHeight();
429
430     jalview.util.ImageMaker im;
431
432     if (type == jalview.util.ImageMaker.PNG)
433     {
434       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.PNG,
435               "Make PNG image from PCA", width, height, null, null);
436     }
437     else
438     {
439       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.EPS,
440               "Make EPS file from PCA", width, height, null,
441               this.getTitle());
442     }
443
444     if (im.getGraphics() != null)
445     {
446       rc.drawBackground(im.getGraphics(), Color.black);
447       rc.drawScene(im.getGraphics());
448       if (rc.drawAxes == true)
449       {
450         rc.drawAxes(im.getGraphics());
451       }
452       im.writeImage();
453     }
454   }
455
456   public void viewMenu_menuSelected()
457   {
458     buildAssociatedViewMenu();
459   }
460
461   void buildAssociatedViewMenu()
462   {
463     AlignmentPanel[] aps = PaintRefresher.getAssociatedPanels(av
464             .getSequenceSetId());
465     if (aps.length == 1 && rc.av == aps[0].av)
466     {
467       associateViewsMenu.setVisible(false);
468       return;
469     }
470
471     associateViewsMenu.setVisible(true);
472
473     if ((viewMenu.getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
474     {
475       viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
476     }
477
478     associateViewsMenu.removeAll();
479
480     JRadioButtonMenuItem item;
481     ButtonGroup buttonGroup = new ButtonGroup();
482     int i, iSize = aps.length;
483     final PCAPanel thisPCAPanel = this;
484     for (i = 0; i < iSize; i++)
485     {
486       final AlignmentPanel ap = aps[i];
487       item = new JRadioButtonMenuItem(ap.av.viewName, ap.av == rc.av);
488       buttonGroup.add(item);
489       item.addActionListener(new ActionListener()
490       {
491         public void actionPerformed(ActionEvent evt)
492         {
493           rc.applyToAllViews = false;
494           rc.av = ap.av;
495           rc.ap = ap;
496           PaintRefresher.Register(thisPCAPanel, ap.av.getSequenceSetId());
497         }
498       });
499
500       associateViewsMenu.add(item);
501     }
502
503     final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem("All Views");
504
505     buttonGroup.add(itemf);
506
507     itemf.setSelected(rc.applyToAllViews);
508     itemf.addActionListener(new ActionListener()
509     {
510       public void actionPerformed(ActionEvent evt)
511       {
512         rc.applyToAllViews = itemf.isSelected();
513       }
514     });
515     associateViewsMenu.add(itemf);
516
517   }
518
519   /*
520    * (non-Javadoc)
521    * 
522    * @see
523    * jalview.jbgui.GPCAPanel#outputPoints_actionPerformed(java.awt.event.ActionEvent
524    * )
525    */
526   protected void outputPoints_actionPerformed(ActionEvent e)
527   {
528     CutAndPasteTransfer cap = new CutAndPasteTransfer();
529     try
530     {
531       cap.setText(getPointsasCsv(false));
532       Desktop.addInternalFrame(cap, "Points for " + getTitle(), 500, 500);
533     } catch (OutOfMemoryError oom)
534     {
535       new OOMWarning("exporting PCA points", oom);
536       cap.dispose();
537     }
538   }
539
540   private String getPointsasCsv(boolean transformed)
541   {
542     StringBuffer csv = new StringBuffer();
543     csv.append("\"Sequence\"");
544     if (transformed)
545     {
546       csv.append(",");
547       csv.append(xCombobox.getSelectedIndex());
548       csv.append(",");
549       csv.append(yCombobox.getSelectedIndex());
550       csv.append(",");
551       csv.append(zCombobox.getSelectedIndex());
552     }
553     else
554     {
555       for (int d = 1, dmax = pca.component(1).length; d <= dmax; d++)
556       {
557         csv.append("," + d);
558       }
559     }
560     csv.append("\n");
561     for (int s = 0; s < seqs.length; s++)
562     {
563       csv.append("\"" + seqs[s].getName() + "\"");
564       double fl[];
565       if (!transformed)
566       {
567         // output pca in correct order
568         fl = pca.component(s);
569         for (int d = fl.length - 1; d >= 0; d--)
570         {
571           csv.append(",");
572           csv.append(fl[d]);
573         }
574       }
575       else
576       {
577         // output current x,y,z coords for points
578         fl = rc.getPointPosition(s);
579         for (int d = 0; d < fl.length; d++)
580         {
581           csv.append(",");
582           csv.append(fl[d]);
583         }
584       }
585       csv.append("\n");
586     }
587     return csv.toString();
588   }
589
590   /*
591    * (non-Javadoc)
592    * 
593    * @see
594    * jalview.jbgui.GPCAPanel#outputProjPoints_actionPerformed(java.awt.event
595    * .ActionEvent)
596    */
597   protected void outputProjPoints_actionPerformed(ActionEvent e)
598   {
599     CutAndPasteTransfer cap = new CutAndPasteTransfer();
600     try
601     {
602       cap.setText(getPointsasCsv(true));
603       Desktop.addInternalFrame(cap, "Transformed points for " + getTitle(),
604               500, 500);
605     } catch (OutOfMemoryError oom)
606     {
607       new OOMWarning("exporting transformed PCA points", oom);
608       cap.dispose();
609     }
610   }
611
612 }