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