(JAL-1013) specify sequence type to ensure PCA calculation employs correct substituti...
[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, 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 useidentity=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     useidentity=av.getAlignment().isNucleotide();
76     if (av.getSelectionGroup() == null)
77     {
78       seqs = av.alignment.getSequencesArray();
79     }
80     else
81     {
82       seqs = av.getSelectionGroup().getSequencesInOrder(av.alignment);
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     Desktop.addInternalFrame(this, "Principal component analysis", 400, 400);
110
111     PaintRefresher.Register(this, av.getSequenceSetId());
112
113     rc = new RotatableCanvas(ap);
114     this.getContentPane().add(rc, BorderLayout.CENTER);
115     Thread worker = new Thread(this);
116     worker.start();
117   }
118
119   public void bgcolour_actionPerformed(ActionEvent e)
120   {
121     Color col = JColorChooser.showDialog(this, "Select Background Colour",
122             rc.bgColour);
123
124     if (col != null)
125     {
126       rc.bgColour = col;
127     }
128     rc.repaint();
129   }
130
131   /**
132    * DOCUMENT ME!
133    */
134   public void run()
135   {
136     try
137     {
138       pca = new PCA(seqstrings.getSequenceStrings(' '), useidentity);
139       pca.run();
140
141       // Now find the component coordinates
142       int ii = 0;
143
144       while ((ii < seqs.length) && (seqs[ii] != null))
145       {
146         ii++;
147       }
148
149       double[][] comps = new double[ii][ii];
150
151       for (int i = 0; i < ii; i++)
152       {
153         if (pca.getEigenvalue(i) > 1e-4)
154         {
155           comps[i] = pca.component(i);
156         }
157       }
158
159       // ////////////////
160       xCombobox.setSelectedIndex(0);
161       yCombobox.setSelectedIndex(1);
162       zCombobox.setSelectedIndex(2);
163
164       top = pca.getM().rows - 1;
165
166       Vector points = new Vector();
167       float[][] scores = pca.getComponents(top - 1, top - 2, top - 3, 100);
168
169       for (int i = 0; i < pca.getM().rows; i++)
170       {
171         SequencePoint sp = new SequencePoint(seqs[i], scores[i]);
172         points.addElement(sp);
173       }
174
175       rc.setPoints(points, pca.getM().rows);
176       rc.repaint();
177
178       addKeyListener(rc);
179
180     } catch (OutOfMemoryError er)
181     {
182       new OOMWarning("calculating PCA", er);
183
184     }
185
186   }
187
188   /**
189    * DOCUMENT ME!
190    */
191   void doDimensionChange()
192   {
193     if (top == 0)
194     {
195       return;
196     }
197
198     int dim1 = top - xCombobox.getSelectedIndex();
199     int dim2 = top - yCombobox.getSelectedIndex();
200     int dim3 = top - zCombobox.getSelectedIndex();
201
202     float[][] scores = pca.getComponents(dim1, dim2, dim3, 100);
203
204     for (int i = 0; i < pca.getM().rows; i++)
205     {
206       ((SequencePoint) rc.points.elementAt(i)).coord = scores[i];
207     }
208
209     rc.img = null;
210     rc.rotmat.setIdentity();
211     rc.initAxes();
212     rc.paint(rc.getGraphics());
213   }
214
215   /**
216    * DOCUMENT ME!
217    * 
218    * @param e
219    *          DOCUMENT ME!
220    */
221   protected void xCombobox_actionPerformed(ActionEvent e)
222   {
223     doDimensionChange();
224   }
225
226   /**
227    * DOCUMENT ME!
228    * 
229    * @param e
230    *          DOCUMENT ME!
231    */
232   protected void yCombobox_actionPerformed(ActionEvent e)
233   {
234     doDimensionChange();
235   }
236
237   /**
238    * DOCUMENT ME!
239    * 
240    * @param e
241    *          DOCUMENT ME!
242    */
243   protected void zCombobox_actionPerformed(ActionEvent e)
244   {
245     doDimensionChange();
246   }
247
248   public void outputValues_actionPerformed(ActionEvent e)
249   {
250     CutAndPasteTransfer cap = new CutAndPasteTransfer();
251     try
252     {
253       cap.setText(pca.getDetails());
254       Desktop.addInternalFrame(cap, "PCA details", 500, 500);
255     } catch (OutOfMemoryError oom)
256     {
257       new OOMWarning("opening PCA details", oom);
258       cap.dispose();
259     }
260   }
261
262   public void showLabels_actionPerformed(ActionEvent e)
263   {
264     rc.showLabels(showLabels.getState());
265   }
266
267   public void print_actionPerformed(ActionEvent e)
268   {
269     PCAPrinter printer = new PCAPrinter();
270     printer.start();
271   }
272
273   public void originalSeqData_actionPerformed(ActionEvent e)
274   {
275     // this was cut'n'pasted from the equivalent TreePanel method - we should
276     // make this an abstract function of all jalview analysis windows
277     if (seqstrings == null)
278     {
279       jalview.bin.Cache.log
280               .info("Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
281       return;
282     }
283     // decide if av alignment is sufficiently different to original data to
284     // warrant a new window to be created
285     // create new alignmnt window with hidden regions (unhiding hidden regions
286     // yields unaligned seqs)
287     // or create a selection box around columns in alignment view
288     // test Alignment(SeqCigar[])
289     char gc = '-';
290     try
291     {
292       // we try to get the associated view's gap character
293       // but this may fail if the view was closed...
294       gc = av.getGapCharacter();
295     } catch (Exception ex)
296     {
297     }
298     ;
299     Object[] alAndColsel = seqstrings.getAlignmentAndColumnSelection(gc);
300
301     if (alAndColsel != null && alAndColsel[0] != null)
302     {
303       // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
304
305       Alignment al = new Alignment((SequenceI[]) alAndColsel[0]);
306       Alignment dataset = (av != null && av.getAlignment() != null) ? av
307               .getAlignment().getDataset() : null;
308       if (dataset != null)
309       {
310         al.setDataset(dataset);
311       }
312
313       if (true)
314       {
315         // make a new frame!
316         AlignFrame af = new AlignFrame(al,
317                 (ColumnSelection) alAndColsel[1], AlignFrame.DEFAULT_WIDTH,
318                 AlignFrame.DEFAULT_HEIGHT);
319
320         // >>>This is a fix for the moment, until a better solution is
321         // found!!<<<
322         // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
323
324         // af.addSortByOrderMenuItem(ServiceName + " Ordering",
325         // msaorder);
326
327         Desktop.addInternalFrame(af, "Original Data for " + this.title,
328                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
329       }
330     }
331     /*
332      * CutAndPasteTransfer cap = new CutAndPasteTransfer(); for (int i = 0; i <
333      * seqs.length; i++) { cap.appendText(new jalview.util.Format("%-" + 15 +
334      * "s").form( seqs[i].getName())); cap.appendText(" " + seqstrings[i] +
335      * "\n"); }
336      * 
337      * Desktop.addInternalFrame(cap, "Original Data", 400, 400);
338      */
339   }
340
341   class PCAPrinter extends Thread implements Printable
342   {
343     public void run()
344     {
345       PrinterJob printJob = PrinterJob.getPrinterJob();
346       PageFormat pf = printJob.pageDialog(printJob.defaultPage());
347
348       printJob.setPrintable(this, pf);
349
350       if (printJob.printDialog())
351       {
352         try
353         {
354           printJob.print();
355         } catch (Exception PrintException)
356         {
357           PrintException.printStackTrace();
358         }
359       }
360     }
361
362     public int print(Graphics pg, PageFormat pf, int pi)
363             throws PrinterException
364     {
365       pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
366
367       rc.drawBackground(pg, rc.bgColour);
368       rc.drawScene(pg);
369       if (rc.drawAxes == true)
370       {
371         rc.drawAxes(pg);
372       }
373
374       if (pi == 0)
375       {
376         return Printable.PAGE_EXISTS;
377       }
378       else
379       {
380         return Printable.NO_SUCH_PAGE;
381       }
382     }
383   }
384
385   /**
386    * DOCUMENT ME!
387    * 
388    * @param e
389    *          DOCUMENT ME!
390    */
391   public void eps_actionPerformed(ActionEvent e)
392   {
393     makePCAImage(jalview.util.ImageMaker.EPS);
394   }
395
396   /**
397    * DOCUMENT ME!
398    * 
399    * @param e
400    *          DOCUMENT ME!
401    */
402   public void png_actionPerformed(ActionEvent e)
403   {
404     makePCAImage(jalview.util.ImageMaker.PNG);
405   }
406
407   void makePCAImage(int type)
408   {
409     int width = rc.getWidth();
410     int height = rc.getHeight();
411
412     jalview.util.ImageMaker im;
413
414     if (type == jalview.util.ImageMaker.PNG)
415     {
416       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.PNG,
417               "Make PNG image from PCA", width, height, null, null);
418     }
419     else
420     {
421       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.EPS,
422               "Make EPS file from PCA", width, height, null,
423               this.getTitle());
424     }
425
426     if (im.getGraphics() != null)
427     {
428       rc.drawBackground(im.getGraphics(), Color.black);
429       rc.drawScene(im.getGraphics());
430       if (rc.drawAxes == true)
431       {
432         rc.drawAxes(im.getGraphics());
433       }
434       im.writeImage();
435     }
436   }
437
438   public void viewMenu_menuSelected()
439   {
440     buildAssociatedViewMenu();
441   }
442
443   void buildAssociatedViewMenu()
444   {
445     AlignmentPanel[] aps = PaintRefresher.getAssociatedPanels(av
446             .getSequenceSetId());
447     if (aps.length == 1 && rc.av == aps[0].av)
448     {
449       associateViewsMenu.setVisible(false);
450       return;
451     }
452
453     associateViewsMenu.setVisible(true);
454
455     if ((viewMenu.getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
456     {
457       viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
458     }
459
460     associateViewsMenu.removeAll();
461
462     JRadioButtonMenuItem item;
463     ButtonGroup buttonGroup = new ButtonGroup();
464     int i, iSize = aps.length;
465     final PCAPanel thisPCAPanel = this;
466     for (i = 0; i < iSize; i++)
467     {
468       final AlignmentPanel ap = aps[i];
469       item = new JRadioButtonMenuItem(ap.av.viewName, ap.av == rc.av);
470       buttonGroup.add(item);
471       item.addActionListener(new ActionListener()
472       {
473         public void actionPerformed(ActionEvent evt)
474         {
475           rc.applyToAllViews = false;
476           rc.av = ap.av;
477           rc.ap = ap;
478           PaintRefresher.Register(thisPCAPanel, ap.av.getSequenceSetId());
479         }
480       });
481
482       associateViewsMenu.add(item);
483     }
484
485     final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem("All Views");
486
487     buttonGroup.add(itemf);
488
489     itemf.setSelected(rc.applyToAllViews);
490     itemf.addActionListener(new ActionListener()
491     {
492       public void actionPerformed(ActionEvent evt)
493       {
494         rc.applyToAllViews = itemf.isSelected();
495       }
496     });
497     associateViewsMenu.add(itemf);
498
499   }
500
501   /*
502    * (non-Javadoc)
503    * 
504    * @see
505    * jalview.jbgui.GPCAPanel#outputPoints_actionPerformed(java.awt.event.ActionEvent
506    * )
507    */
508   protected void outputPoints_actionPerformed(ActionEvent e)
509   {
510     CutAndPasteTransfer cap = new CutAndPasteTransfer();
511     try
512     {
513       cap.setText(getPointsasCsv(false));
514       Desktop.addInternalFrame(cap, "Points for " + getTitle(), 500, 500);
515     } catch (OutOfMemoryError oom)
516     {
517       new OOMWarning("exporting PCA points", oom);
518       cap.dispose();
519     }
520   }
521
522   private String getPointsasCsv(boolean transformed)
523   {
524     StringBuffer csv = new StringBuffer();
525     csv.append("\"Sequence\"");
526     if (transformed)
527     {
528       csv.append(",");
529       csv.append(xCombobox.getSelectedIndex());
530       csv.append(",");
531       csv.append(yCombobox.getSelectedIndex());
532       csv.append(",");
533       csv.append(zCombobox.getSelectedIndex());
534     }
535     else
536     {
537       for (int d = 1, dmax = pca.component(1).length; d <= dmax; d++)
538       {
539         csv.append("," + d);
540       }
541     }
542     csv.append("\n");
543     for (int s = 0; s < seqs.length; s++)
544     {
545       csv.append("\"" + seqs[s].getName() + "\"");
546       double fl[];
547       if (!transformed)
548       {
549         // output pca in correct order
550         fl = pca.component(s);
551         for (int d = fl.length - 1; d >= 0; d--)
552         {
553           csv.append(",");
554           csv.append(fl[d]);
555         }
556       }
557       else
558       {
559         // output current x,y,z coords for points
560         fl = rc.getPointPosition(s);
561         for (int d = 0; d < fl.length; d++)
562         {
563           csv.append(",");
564           csv.append(fl[d]);
565         }
566       }
567       csv.append("\n");
568     }
569     return csv.toString();
570   }
571
572   /*
573    * (non-Javadoc)
574    * 
575    * @see
576    * jalview.jbgui.GPCAPanel#outputProjPoints_actionPerformed(java.awt.event
577    * .ActionEvent)
578    */
579   protected void outputProjPoints_actionPerformed(ActionEvent e)
580   {
581     CutAndPasteTransfer cap = new CutAndPasteTransfer();
582     try
583     {
584       cap.setText(getPointsasCsv(true));
585       Desktop.addInternalFrame(cap, "Transformed points for " + getTitle(),
586               500, 500);
587     } catch (OutOfMemoryError oom)
588     {
589       new OOMWarning("exporting transformed PCA points", oom);
590       cap.dispose();
591     }
592   }
593
594 }