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