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