4c8a2516f65949b7f97354b20d71ac8d681ede51
[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, IProgressIndicator
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     long progId=System.currentTimeMillis();
135     IProgressIndicator progress=this;
136     String message="Recalculating PCA";
137     if (getParent()==null) {
138       progress=ap.alignFrame;
139       message = "Calculating PCA";
140     }
141     progress.setProgressBar(message, progId);
142     try
143     {
144       calcSettings.setEnabled(false);
145       
146       pca = new PCA(seqstrings.getSequenceStrings(' '), nucleotide);
147       pca.run();
148
149       // Now find the component coordinates
150       int ii = 0;
151
152       while ((ii < seqs.length) && (seqs[ii] != null))
153       {
154         ii++;
155       }
156
157       double[][] comps = new double[ii][ii];
158
159       for (int i = 0; i < ii; i++)
160       {
161         if (pca.getEigenvalue(i) > 1e-4)
162         {
163           comps[i] = pca.component(i);
164         }
165       }
166
167       // ////////////////
168       xCombobox.setSelectedIndex(0);
169       yCombobox.setSelectedIndex(1);
170       zCombobox.setSelectedIndex(2);
171
172       top = pca.getM().rows - 1;
173
174       Vector points = new Vector();
175       float[][] scores = pca.getComponents(top - 1, top - 2, top - 3, 100);
176
177       for (int i = 0; i < pca.getM().rows; i++)
178       {
179         SequencePoint sp = new SequencePoint(seqs[i], scores[i]);
180         points.addElement(sp);
181       }
182
183       rc.setPoints(points, pca.getM().rows);
184       // rc.invalidate();
185       nuclSetting.setSelected(nucleotide);
186       protSetting.setSelected(!nucleotide);
187
188     } catch (OutOfMemoryError er)
189     {
190       new OOMWarning("calculating PCA", er);
191       return;
192     }
193     finally {
194       progress.setProgressBar("", progId);
195     }
196     calcSettings.setEnabled(true);
197     repaint();
198     if (getParent()==null)
199     {
200       addKeyListener(rc);
201       Desktop.addInternalFrame(this, "Principal component analysis", 475, 450);
202     }
203   }
204   @Override
205   protected void nuclSetting_actionPerfomed(ActionEvent arg0)
206   {
207     if (!nucleotide)
208     {
209     nucleotide=true;
210     Thread worker = new Thread(this);
211     worker.start();
212     }
213     
214   }
215   @Override
216   protected void protSetting_actionPerfomed(ActionEvent arg0)
217   {
218     
219     if (nucleotide)
220     {
221     nucleotide=false;
222     Thread worker = new Thread(this);
223     worker.start();
224     }
225   }
226   /**
227    * DOCUMENT ME!
228    */
229   void doDimensionChange()
230   {
231     if (top == 0)
232     {
233       return;
234     }
235
236     int dim1 = top - xCombobox.getSelectedIndex();
237     int dim2 = top - yCombobox.getSelectedIndex();
238     int dim3 = top - zCombobox.getSelectedIndex();
239
240     float[][] scores = pca.getComponents(dim1, dim2, dim3, 100);
241
242     for (int i = 0; i < pca.getM().rows; i++)
243     {
244       ((SequencePoint) rc.points.elementAt(i)).coord = scores[i];
245     }
246
247     rc.img = null;
248     rc.rotmat.setIdentity();
249     rc.initAxes();
250     rc.paint(rc.getGraphics());
251   }
252
253   /**
254    * DOCUMENT ME!
255    * 
256    * @param e
257    *          DOCUMENT ME!
258    */
259   protected void xCombobox_actionPerformed(ActionEvent e)
260   {
261     doDimensionChange();
262   }
263
264   /**
265    * DOCUMENT ME!
266    * 
267    * @param e
268    *          DOCUMENT ME!
269    */
270   protected void yCombobox_actionPerformed(ActionEvent e)
271   {
272     doDimensionChange();
273   }
274
275   /**
276    * DOCUMENT ME!
277    * 
278    * @param e
279    *          DOCUMENT ME!
280    */
281   protected void zCombobox_actionPerformed(ActionEvent e)
282   {
283     doDimensionChange();
284   }
285
286   public void outputValues_actionPerformed(ActionEvent e)
287   {
288     CutAndPasteTransfer cap = new CutAndPasteTransfer();
289     try
290     {
291       cap.setText(pca.getDetails());
292       Desktop.addInternalFrame(cap, "PCA details", 500, 500);
293     } catch (OutOfMemoryError oom)
294     {
295       new OOMWarning("opening PCA details", oom);
296       cap.dispose();
297     }
298   }
299
300   public void showLabels_actionPerformed(ActionEvent e)
301   {
302     rc.showLabels(showLabels.getState());
303   }
304
305   public void print_actionPerformed(ActionEvent e)
306   {
307     PCAPrinter printer = new PCAPrinter();
308     printer.start();
309   }
310
311   public void originalSeqData_actionPerformed(ActionEvent e)
312   {
313     // this was cut'n'pasted from the equivalent TreePanel method - we should
314     // make this an abstract function of all jalview analysis windows
315     if (seqstrings == null)
316     {
317       jalview.bin.Cache.log
318               .info("Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
319       return;
320     }
321     // decide if av alignment is sufficiently different to original data to
322     // warrant a new window to be created
323     // create new alignmnt window with hidden regions (unhiding hidden regions
324     // yields unaligned seqs)
325     // or create a selection box around columns in alignment view
326     // test Alignment(SeqCigar[])
327     char gc = '-';
328     try
329     {
330       // we try to get the associated view's gap character
331       // but this may fail if the view was closed...
332       gc = av.getGapCharacter();
333     } catch (Exception ex)
334     {
335     }
336     ;
337     Object[] alAndColsel = seqstrings.getAlignmentAndColumnSelection(gc);
338
339     if (alAndColsel != null && alAndColsel[0] != null)
340     {
341       // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
342
343       Alignment al = new Alignment((SequenceI[]) alAndColsel[0]);
344       Alignment dataset = (av != null && av.getAlignment() != null) ? av
345               .getAlignment().getDataset() : null;
346       if (dataset != null)
347       {
348         al.setDataset(dataset);
349       }
350
351       if (true)
352       {
353         // make a new frame!
354         AlignFrame af = new AlignFrame(al,
355                 (ColumnSelection) alAndColsel[1], AlignFrame.DEFAULT_WIDTH,
356                 AlignFrame.DEFAULT_HEIGHT);
357
358         // >>>This is a fix for the moment, until a better solution is
359         // found!!<<<
360         // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
361
362         // af.addSortByOrderMenuItem(ServiceName + " Ordering",
363         // msaorder);
364
365         Desktop.addInternalFrame(af, "Original Data for " + this.title,
366                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
367       }
368     }
369     /*
370      * CutAndPasteTransfer cap = new CutAndPasteTransfer(); for (int i = 0; i <
371      * seqs.length; i++) { cap.appendText(new jalview.util.Format("%-" + 15 +
372      * "s").form( seqs[i].getName())); cap.appendText(" " + seqstrings[i] +
373      * "\n"); }
374      * 
375      * Desktop.addInternalFrame(cap, "Original Data", 400, 400);
376      */
377   }
378
379   class PCAPrinter extends Thread implements Printable
380   {
381     public void run()
382     {
383       PrinterJob printJob = PrinterJob.getPrinterJob();
384       PageFormat pf = printJob.pageDialog(printJob.defaultPage());
385
386       printJob.setPrintable(this, pf);
387
388       if (printJob.printDialog())
389       {
390         try
391         {
392           printJob.print();
393         } catch (Exception PrintException)
394         {
395           PrintException.printStackTrace();
396         }
397       }
398     }
399
400     public int print(Graphics pg, PageFormat pf, int pi)
401             throws PrinterException
402     {
403       pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
404
405       rc.drawBackground(pg, rc.bgColour);
406       rc.drawScene(pg);
407       if (rc.drawAxes == true)
408       {
409         rc.drawAxes(pg);
410       }
411
412       if (pi == 0)
413       {
414         return Printable.PAGE_EXISTS;
415       }
416       else
417       {
418         return Printable.NO_SUCH_PAGE;
419       }
420     }
421   }
422
423   /**
424    * DOCUMENT ME!
425    * 
426    * @param e
427    *          DOCUMENT ME!
428    */
429   public void eps_actionPerformed(ActionEvent e)
430   {
431     makePCAImage(jalview.util.ImageMaker.EPS);
432   }
433
434   /**
435    * DOCUMENT ME!
436    * 
437    * @param e
438    *          DOCUMENT ME!
439    */
440   public void png_actionPerformed(ActionEvent e)
441   {
442     makePCAImage(jalview.util.ImageMaker.PNG);
443   }
444
445   void makePCAImage(int type)
446   {
447     int width = rc.getWidth();
448     int height = rc.getHeight();
449
450     jalview.util.ImageMaker im;
451
452     if (type == jalview.util.ImageMaker.PNG)
453     {
454       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.PNG,
455               "Make PNG image from PCA", width, height, null, null);
456     }
457     else
458     {
459       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.EPS,
460               "Make EPS file from PCA", width, height, null,
461               this.getTitle());
462     }
463
464     if (im.getGraphics() != null)
465     {
466       rc.drawBackground(im.getGraphics(), Color.black);
467       rc.drawScene(im.getGraphics());
468       if (rc.drawAxes == true)
469       {
470         rc.drawAxes(im.getGraphics());
471       }
472       im.writeImage();
473     }
474   }
475
476   public void viewMenu_menuSelected()
477   {
478     buildAssociatedViewMenu();
479   }
480
481   void buildAssociatedViewMenu()
482   {
483     AlignmentPanel[] aps = PaintRefresher.getAssociatedPanels(av
484             .getSequenceSetId());
485     if (aps.length == 1 && rc.av == aps[0].av)
486     {
487       associateViewsMenu.setVisible(false);
488       return;
489     }
490
491     associateViewsMenu.setVisible(true);
492
493     if ((viewMenu.getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
494     {
495       viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
496     }
497
498     associateViewsMenu.removeAll();
499
500     JRadioButtonMenuItem item;
501     ButtonGroup buttonGroup = new ButtonGroup();
502     int i, iSize = aps.length;
503     final PCAPanel thisPCAPanel = this;
504     for (i = 0; i < iSize; i++)
505     {
506       final AlignmentPanel ap = aps[i];
507       item = new JRadioButtonMenuItem(ap.av.viewName, ap.av == rc.av);
508       buttonGroup.add(item);
509       item.addActionListener(new ActionListener()
510       {
511         public void actionPerformed(ActionEvent evt)
512         {
513           rc.applyToAllViews = false;
514           rc.av = ap.av;
515           rc.ap = ap;
516           PaintRefresher.Register(thisPCAPanel, ap.av.getSequenceSetId());
517         }
518       });
519
520       associateViewsMenu.add(item);
521     }
522
523     final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem("All Views");
524
525     buttonGroup.add(itemf);
526
527     itemf.setSelected(rc.applyToAllViews);
528     itemf.addActionListener(new ActionListener()
529     {
530       public void actionPerformed(ActionEvent evt)
531       {
532         rc.applyToAllViews = itemf.isSelected();
533       }
534     });
535     associateViewsMenu.add(itemf);
536
537   }
538
539   /*
540    * (non-Javadoc)
541    * 
542    * @see
543    * jalview.jbgui.GPCAPanel#outputPoints_actionPerformed(java.awt.event.ActionEvent
544    * )
545    */
546   protected void outputPoints_actionPerformed(ActionEvent e)
547   {
548     CutAndPasteTransfer cap = new CutAndPasteTransfer();
549     try
550     {
551       cap.setText(getPointsasCsv(false));
552       Desktop.addInternalFrame(cap, "Points for " + getTitle(), 500, 500);
553     } catch (OutOfMemoryError oom)
554     {
555       new OOMWarning("exporting PCA points", oom);
556       cap.dispose();
557     }
558   }
559
560   private String getPointsasCsv(boolean transformed)
561   {
562     StringBuffer csv = new StringBuffer();
563     csv.append("\"Sequence\"");
564     if (transformed)
565     {
566       csv.append(",");
567       csv.append(xCombobox.getSelectedIndex());
568       csv.append(",");
569       csv.append(yCombobox.getSelectedIndex());
570       csv.append(",");
571       csv.append(zCombobox.getSelectedIndex());
572     }
573     else
574     {
575       for (int d = 1, dmax = pca.component(1).length; d <= dmax; d++)
576       {
577         csv.append("," + d);
578       }
579     }
580     csv.append("\n");
581     for (int s = 0; s < seqs.length; s++)
582     {
583       csv.append("\"" + seqs[s].getName() + "\"");
584       double fl[];
585       if (!transformed)
586       {
587         // output pca in correct order
588         fl = pca.component(s);
589         for (int d = fl.length - 1; d >= 0; d--)
590         {
591           csv.append(",");
592           csv.append(fl[d]);
593         }
594       }
595       else
596       {
597         // output current x,y,z coords for points
598         fl = rc.getPointPosition(s);
599         for (int d = 0; d < fl.length; d++)
600         {
601           csv.append(",");
602           csv.append(fl[d]);
603         }
604       }
605       csv.append("\n");
606     }
607     return csv.toString();
608   }
609
610   /*
611    * (non-Javadoc)
612    * 
613    * @see
614    * jalview.jbgui.GPCAPanel#outputProjPoints_actionPerformed(java.awt.event
615    * .ActionEvent)
616    */
617   protected void outputProjPoints_actionPerformed(ActionEvent e)
618   {
619     CutAndPasteTransfer cap = new CutAndPasteTransfer();
620     try
621     {
622       cap.setText(getPointsasCsv(true));
623       Desktop.addInternalFrame(cap, "Transformed points for " + getTitle(),
624               500, 500);
625     } catch (OutOfMemoryError oom)
626     {
627       new OOMWarning("exporting transformed PCA points", oom);
628       cap.dispose();
629     }
630   }
631
632   // methods for implementing IProgressIndicator
633   // need to refactor to a reusable stub class
634   Hashtable progressBars, progressBarHandlers;
635
636   /*
637    * (non-Javadoc)
638    *
639    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
640    */
641   @Override
642   public void setProgressBar(String message, long id)
643   {
644     if (progressBars == null)
645     {
646       progressBars = new Hashtable();
647       progressBarHandlers = new Hashtable();
648     }
649
650     JPanel progressPanel;
651     Long lId = new Long(id);
652     GridLayout layout = (GridLayout) statusPanel.getLayout();
653     if (progressBars.get(lId) != null)
654     {
655       progressPanel = (JPanel) progressBars.get(new Long(id));
656       statusPanel.remove(progressPanel);
657       progressBars.remove(lId);
658       progressPanel = null;
659       if (message != null)
660       {
661         statusBar.setText(message);
662       }
663       if (progressBarHandlers.contains(lId))
664       {
665         progressBarHandlers.remove(lId);
666       }
667       layout.setRows(layout.getRows() - 1);
668     }
669     else
670     {
671       progressPanel = new JPanel(new BorderLayout(10, 5));
672
673       JProgressBar progressBar = new JProgressBar();
674       progressBar.setIndeterminate(true);
675
676       progressPanel.add(new JLabel(message), BorderLayout.WEST);
677       progressPanel.add(progressBar, BorderLayout.CENTER);
678
679       layout.setRows(layout.getRows() + 1);
680       statusPanel.add(progressPanel);
681
682       progressBars.put(lId, progressPanel);
683     }
684     // update GUI
685     // setMenusForViewport();
686     validate();
687   }
688
689   @Override
690   public void registerHandler(final long id,
691           final IProgressIndicatorHandler handler)
692   {
693     if (progressBarHandlers == null || !progressBars.contains(new Long(id)))
694     {
695       throw new Error(
696               "call setProgressBar before registering the progress bar's handler.");
697     }
698     progressBarHandlers.put(new Long(id), handler);
699     final JPanel progressPanel = (JPanel) progressBars.get(new Long(id));
700     if (handler.canCancel())
701     {
702       JButton cancel = new JButton("Cancel");
703       final IProgressIndicator us = this;
704       cancel.addActionListener(new ActionListener()
705       {
706
707         @Override
708         public void actionPerformed(ActionEvent e)
709         {
710           handler.cancelActivity(id);
711           us.setProgressBar(
712                   "Cancelled "
713                           + ((JLabel) progressPanel.getComponent(0))
714                                   .getText(), id);
715         }
716       });
717       progressPanel.add(cancel, BorderLayout.EAST);
718     }
719   }
720
721   /**
722    *
723    * @return true if any progress bars are still active
724    */
725   @Override
726   public boolean operationInProgress()
727   {
728     if (progressBars != null && progressBars.size() > 0)
729     {
730       return true;
731     }
732     return false;
733   }
734   @Override
735   protected void resetButton_actionPerformed(ActionEvent e)
736   {
737     int t=top;
738     top=0; // ugly - prevents dimensionChanged events from being processed
739     xCombobox.setSelectedIndex(0);
740     yCombobox.setSelectedIndex(1);
741     top=t;
742     zCombobox.setSelectedIndex(2);
743   }
744 }