JAL-1125 option to switch between Jalview and SeqSpace PCA calculation modes
[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 import jalview.viewmodel.PCAModel;
31
32 /**
33  * DOCUMENT ME!
34  * 
35  * @author $author$
36  * @version $Revision$
37  */
38 public class PCAPanel extends GPCAPanel implements Runnable, IProgressIndicator
39 {
40
41
42   RotatableCanvas rc;
43
44   AlignmentPanel ap;
45
46   AlignViewport av;
47   PCAModel pcaModel;
48   int top=0;
49
50   /**
51    * Creates a new PCAPanel object.
52    * 
53    * @param av
54    *          DOCUMENT ME!
55    * @param s
56    *          DOCUMENT ME!
57    */
58   public PCAPanel(AlignmentPanel ap)
59   {
60     this.av = ap.av;
61     this.ap = ap;
62
63     boolean sameLength = true;
64
65     AlignmentView seqstrings = av.getAlignmentView(av.getSelectionGroup() != null);
66     boolean nucleotide=av.getAlignment().isNucleotide();
67     SequenceI[] seqs;
68     if (av.getSelectionGroup() == null)
69     {
70       seqs = av.getAlignment().getSequencesArray();
71     }
72     else
73     {
74       seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment());
75     }
76     SeqCigar sq[] = seqstrings.getSequences();
77     int length = sq[0].getWidth();
78
79     for (int i = 0; i < seqs.length; i++)
80     {
81       if (sq[i].getWidth() != length)
82       {
83         sameLength = false;
84         break;
85       }
86     }
87
88     if (!sameLength)
89     {
90       JOptionPane
91               .showMessageDialog(
92                       Desktop.desktop,
93                       "The sequences must be aligned before calculating PCA.\n"
94                               + "Try using the Pad function in the edit menu,\n"
95                               + "or one of the multiple sequence alignment web services.",
96                       "Sequences not aligned", JOptionPane.WARNING_MESSAGE);
97
98       return;
99     }
100     pcaModel = new PCAModel(seqstrings, seqs, nucleotide);
101     PaintRefresher.Register(this, av.getSequenceSetId());
102     
103     rc = new RotatableCanvas(ap);
104     this.getContentPane().add(rc, BorderLayout.CENTER);
105     Thread worker = new Thread(this);
106     worker.start();
107   }
108
109   public void bgcolour_actionPerformed(ActionEvent e)
110   {
111     Color col = JColorChooser.showDialog(this, "Select Background Colour",
112             rc.bgColour);
113
114     if (col != null)
115     {
116       rc.bgColour = col;
117     }
118     rc.repaint();
119   }
120
121   /**
122    * DOCUMENT ME!
123    */
124   public void run()
125   {
126     long progId=System.currentTimeMillis();
127     IProgressIndicator progress=this;
128     String message="Recalculating PCA";
129     if (getParent()==null) {
130       progress=ap.alignFrame;
131       message = "Calculating PCA";
132     }
133     progress.setProgressBar(message, progId);
134     try
135     {
136       calcSettings.setEnabled(false);
137       pcaModel.run();
138       // ////////////////
139       xCombobox.setSelectedIndex(0);
140       yCombobox.setSelectedIndex(1);
141       zCombobox.setSelectedIndex(2);
142       
143       pcaModel.updateRc(rc);
144       // rc.invalidate();
145       nuclSetting.setSelected(pcaModel.isNucleotide());
146       protSetting.setSelected(!pcaModel.isNucleotide());
147       top=pcaModel.getTop();
148       jvVersionSetting.setSelected(pcaModel.isJvCalcMode());
149
150     } catch (OutOfMemoryError er)
151     {
152       new OOMWarning("calculating PCA", er);
153       return;
154     }
155     finally {
156       progress.setProgressBar("", progId);
157     }
158     calcSettings.setEnabled(true);
159     repaint();
160     if (getParent()==null)
161     {
162       addKeyListener(rc);
163       Desktop.addInternalFrame(this, "Principal component analysis", 475, 450);
164     }
165   }
166   @Override
167   protected void nuclSetting_actionPerfomed(ActionEvent arg0)
168   {
169     if (!pcaModel.isNucleotide())
170     {
171       pcaModel.setNucleotide(true);
172     Thread worker = new Thread(this);
173     worker.start();
174     }
175     
176   }
177   @Override
178   protected void protSetting_actionPerfomed(ActionEvent arg0)
179   {
180     
181     if (pcaModel.isNucleotide())
182     {
183       pcaModel.setNucleotide(false);
184       Thread worker = new Thread(this);
185       worker.start();
186     }
187   }
188
189   @Override
190   protected void jvVersionSetting_actionPerfomed(ActionEvent arg0)
191   {
192     pcaModel.setJvCalcMode(jvVersionSetting.isSelected());
193     Thread worker = new Thread(this);
194     worker.start();
195   }
196
197   /**
198    * DOCUMENT ME!
199    */
200   void doDimensionChange()
201   {
202     if (top == 0)
203     {
204       return;
205     }
206
207     int dim1 = top - xCombobox.getSelectedIndex();
208     int dim2 = top - yCombobox.getSelectedIndex();
209     int dim3 = top - zCombobox.getSelectedIndex();
210     pcaModel.updateRcView(dim1, dim2, dim3);
211     rc.img = null;
212     rc.rotmat.setIdentity();
213     rc.initAxes();
214     rc.paint(rc.getGraphics());
215   }
216
217   /**
218    * DOCUMENT ME!
219    * 
220    * @param e
221    *          DOCUMENT ME!
222    */
223   protected void xCombobox_actionPerformed(ActionEvent e)
224   {
225     doDimensionChange();
226   }
227
228   /**
229    * DOCUMENT ME!
230    * 
231    * @param e
232    *          DOCUMENT ME!
233    */
234   protected void yCombobox_actionPerformed(ActionEvent e)
235   {
236     doDimensionChange();
237   }
238
239   /**
240    * DOCUMENT ME!
241    * 
242    * @param e
243    *          DOCUMENT ME!
244    */
245   protected void zCombobox_actionPerformed(ActionEvent e)
246   {
247     doDimensionChange();
248   }
249
250   public void outputValues_actionPerformed(ActionEvent e)
251   {
252     CutAndPasteTransfer cap = new CutAndPasteTransfer();
253     try
254     {
255       cap.setText(pcaModel.getDetails());
256       Desktop.addInternalFrame(cap, "PCA details", 500, 500);
257     } catch (OutOfMemoryError oom)
258     {
259       new OOMWarning("opening PCA details", oom);
260       cap.dispose();
261     }
262   }
263
264   public void showLabels_actionPerformed(ActionEvent e)
265   {
266     rc.showLabels(showLabels.getState());
267   }
268
269   public void print_actionPerformed(ActionEvent e)
270   {
271     PCAPrinter printer = new PCAPrinter();
272     printer.start();
273   }
274
275   public void originalSeqData_actionPerformed(ActionEvent e)
276   {
277     // this was cut'n'pasted from the equivalent TreePanel method - we should
278     // make this an abstract function of all jalview analysis windows
279     if (pcaModel.getSeqtrings() == null)
280     {
281       jalview.bin.Cache.log
282               .info("Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
283       return;
284     }
285     // decide if av alignment is sufficiently different to original data to
286     // warrant a new window to be created
287     // create new alignmnt window with hidden regions (unhiding hidden regions
288     // yields unaligned seqs)
289     // or create a selection box around columns in alignment view
290     // test Alignment(SeqCigar[])
291     char gc = '-';
292     try
293     {
294       // we try to get the associated view's gap character
295       // but this may fail if the view was closed...
296       gc = av.getGapCharacter();
297     } catch (Exception ex)
298     {
299     }
300     ;
301     Object[] alAndColsel = pcaModel.getSeqtrings().getAlignmentAndColumnSelection(gc);
302
303     if (alAndColsel != null && alAndColsel[0] != null)
304     {
305       // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
306
307       Alignment al = new Alignment((SequenceI[]) alAndColsel[0]);
308       Alignment dataset = (av != null && av.getAlignment() != null) ? av
309               .getAlignment().getDataset() : null;
310       if (dataset != null)
311       {
312         al.setDataset(dataset);
313       }
314
315       if (true)
316       {
317         // make a new frame!
318         AlignFrame af = new AlignFrame(al,
319                 (ColumnSelection) alAndColsel[1], AlignFrame.DEFAULT_WIDTH,
320                 AlignFrame.DEFAULT_HEIGHT);
321
322         // >>>This is a fix for the moment, until a better solution is
323         // found!!<<<
324         // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
325
326         // af.addSortByOrderMenuItem(ServiceName + " Ordering",
327         // msaorder);
328
329         Desktop.addInternalFrame(af, "Original Data for " + this.title,
330                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
331       }
332     }
333     /*
334      * CutAndPasteTransfer cap = new CutAndPasteTransfer(); for (int i = 0; i <
335      * seqs.length; i++) { cap.appendText(new jalview.util.Format("%-" + 15 +
336      * "s").form( seqs[i].getName())); cap.appendText(" " + seqstrings[i] +
337      * "\n"); }
338      * 
339      * Desktop.addInternalFrame(cap, "Original Data", 400, 400);
340      */
341   }
342
343   class PCAPrinter extends Thread implements Printable
344   {
345     public void run()
346     {
347       PrinterJob printJob = PrinterJob.getPrinterJob();
348       PageFormat pf = printJob.pageDialog(printJob.defaultPage());
349
350       printJob.setPrintable(this, pf);
351
352       if (printJob.printDialog())
353       {
354         try
355         {
356           printJob.print();
357         } catch (Exception PrintException)
358         {
359           PrintException.printStackTrace();
360         }
361       }
362     }
363
364     public int print(Graphics pg, PageFormat pf, int pi)
365             throws PrinterException
366     {
367       pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
368
369       rc.drawBackground(pg, rc.bgColour);
370       rc.drawScene(pg);
371       if (rc.drawAxes == true)
372       {
373         rc.drawAxes(pg);
374       }
375
376       if (pi == 0)
377       {
378         return Printable.PAGE_EXISTS;
379       }
380       else
381       {
382         return Printable.NO_SUCH_PAGE;
383       }
384     }
385   }
386
387   /**
388    * DOCUMENT ME!
389    * 
390    * @param e
391    *          DOCUMENT ME!
392    */
393   public void eps_actionPerformed(ActionEvent e)
394   {
395     makePCAImage(jalview.util.ImageMaker.EPS);
396   }
397
398   /**
399    * DOCUMENT ME!
400    * 
401    * @param e
402    *          DOCUMENT ME!
403    */
404   public void png_actionPerformed(ActionEvent e)
405   {
406     makePCAImage(jalview.util.ImageMaker.PNG);
407   }
408
409   void makePCAImage(int type)
410   {
411     int width = rc.getWidth();
412     int height = rc.getHeight();
413
414     jalview.util.ImageMaker im;
415
416     if (type == jalview.util.ImageMaker.PNG)
417     {
418       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.PNG,
419               "Make PNG image from PCA", width, height, null, null);
420     }
421     else
422     {
423       im = new jalview.util.ImageMaker(this, jalview.util.ImageMaker.EPS,
424               "Make EPS file from PCA", width, height, null,
425               this.getTitle());
426     }
427
428     if (im.getGraphics() != null)
429     {
430       rc.drawBackground(im.getGraphics(), Color.black);
431       rc.drawScene(im.getGraphics());
432       if (rc.drawAxes == true)
433       {
434         rc.drawAxes(im.getGraphics());
435       }
436       im.writeImage();
437     }
438   }
439
440   public void viewMenu_menuSelected()
441   {
442     buildAssociatedViewMenu();
443   }
444
445   void buildAssociatedViewMenu()
446   {
447     AlignmentPanel[] aps = PaintRefresher.getAssociatedPanels(av
448             .getSequenceSetId());
449     if (aps.length == 1 && rc.av == aps[0].av)
450     {
451       associateViewsMenu.setVisible(false);
452       return;
453     }
454
455     associateViewsMenu.setVisible(true);
456
457     if ((viewMenu.getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
458     {
459       viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
460     }
461
462     associateViewsMenu.removeAll();
463
464     JRadioButtonMenuItem item;
465     ButtonGroup buttonGroup = new ButtonGroup();
466     int i, iSize = aps.length;
467     final PCAPanel thisPCAPanel = this;
468     for (i = 0; i < iSize; i++)
469     {
470       final AlignmentPanel ap = aps[i];
471       item = new JRadioButtonMenuItem(ap.av.viewName, ap.av == rc.av);
472       buttonGroup.add(item);
473       item.addActionListener(new ActionListener()
474       {
475         public void actionPerformed(ActionEvent evt)
476         {
477           rc.applyToAllViews = false;
478           rc.av = ap.av;
479           rc.ap = ap;
480           PaintRefresher.Register(thisPCAPanel, ap.av.getSequenceSetId());
481         }
482       });
483
484       associateViewsMenu.add(item);
485     }
486
487     final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem("All Views");
488
489     buttonGroup.add(itemf);
490
491     itemf.setSelected(rc.applyToAllViews);
492     itemf.addActionListener(new ActionListener()
493     {
494       public void actionPerformed(ActionEvent evt)
495       {
496         rc.applyToAllViews = itemf.isSelected();
497       }
498     });
499     associateViewsMenu.add(itemf);
500
501   }
502
503   /*
504    * (non-Javadoc)
505    * 
506    * @see
507    * jalview.jbgui.GPCAPanel#outputPoints_actionPerformed(java.awt.event.ActionEvent
508    * )
509    */
510   protected void outputPoints_actionPerformed(ActionEvent e)
511   {
512     CutAndPasteTransfer cap = new CutAndPasteTransfer();
513     try
514     {
515       cap.setText(pcaModel.getPointsasCsv(false, xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(), zCombobox.getSelectedIndex()));
516       Desktop.addInternalFrame(cap, "Points for " + getTitle(), 500, 500);
517     } catch (OutOfMemoryError oom)
518     {
519       new OOMWarning("exporting PCA points", oom);
520       cap.dispose();
521     }
522   }
523
524   
525
526   /*
527    * (non-Javadoc)
528    * 
529    * @see
530    * jalview.jbgui.GPCAPanel#outputProjPoints_actionPerformed(java.awt.event
531    * .ActionEvent)
532    */
533   protected void outputProjPoints_actionPerformed(ActionEvent e)
534   {
535     CutAndPasteTransfer cap = new CutAndPasteTransfer();
536     try
537     {
538       cap.setText(pcaModel.getPointsasCsv(true, xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(), zCombobox.getSelectedIndex()));
539       Desktop.addInternalFrame(cap, "Transformed points for " + getTitle(),
540               500, 500);
541     } catch (OutOfMemoryError oom)
542     {
543       new OOMWarning("exporting transformed PCA points", oom);
544       cap.dispose();
545     }
546   }
547
548   // methods for implementing IProgressIndicator
549   // need to refactor to a reusable stub class
550   Hashtable progressBars, progressBarHandlers;
551
552   /*
553    * (non-Javadoc)
554    *
555    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
556    */
557   @Override
558   public void setProgressBar(String message, long id)
559   {
560     if (progressBars == null)
561     {
562       progressBars = new Hashtable();
563       progressBarHandlers = new Hashtable();
564     }
565
566     JPanel progressPanel;
567     Long lId = new Long(id);
568     GridLayout layout = (GridLayout) statusPanel.getLayout();
569     if (progressBars.get(lId) != null)
570     {
571       progressPanel = (JPanel) progressBars.get(new Long(id));
572       statusPanel.remove(progressPanel);
573       progressBars.remove(lId);
574       progressPanel = null;
575       if (message != null)
576       {
577         statusBar.setText(message);
578       }
579       if (progressBarHandlers.contains(lId))
580       {
581         progressBarHandlers.remove(lId);
582       }
583       layout.setRows(layout.getRows() - 1);
584     }
585     else
586     {
587       progressPanel = new JPanel(new BorderLayout(10, 5));
588
589       JProgressBar progressBar = new JProgressBar();
590       progressBar.setIndeterminate(true);
591
592       progressPanel.add(new JLabel(message), BorderLayout.WEST);
593       progressPanel.add(progressBar, BorderLayout.CENTER);
594
595       layout.setRows(layout.getRows() + 1);
596       statusPanel.add(progressPanel);
597
598       progressBars.put(lId, progressPanel);
599     }
600     // update GUI
601     // setMenusForViewport();
602     validate();
603   }
604
605   @Override
606   public void registerHandler(final long id,
607           final IProgressIndicatorHandler handler)
608   {
609     if (progressBarHandlers == null || !progressBars.contains(new Long(id)))
610     {
611       throw new Error(
612               "call setProgressBar before registering the progress bar's handler.");
613     }
614     progressBarHandlers.put(new Long(id), handler);
615     final JPanel progressPanel = (JPanel) progressBars.get(new Long(id));
616     if (handler.canCancel())
617     {
618       JButton cancel = new JButton("Cancel");
619       final IProgressIndicator us = this;
620       cancel.addActionListener(new ActionListener()
621       {
622
623         @Override
624         public void actionPerformed(ActionEvent e)
625         {
626           handler.cancelActivity(id);
627           us.setProgressBar(
628                   "Cancelled "
629                           + ((JLabel) progressPanel.getComponent(0))
630                                   .getText(), id);
631         }
632       });
633       progressPanel.add(cancel, BorderLayout.EAST);
634     }
635   }
636
637   /**
638    *
639    * @return true if any progress bars are still active
640    */
641   @Override
642   public boolean operationInProgress()
643   {
644     if (progressBars != null && progressBars.size() > 0)
645     {
646       return true;
647     }
648     return false;
649   }
650   @Override
651   protected void resetButton_actionPerformed(ActionEvent e)
652   {
653     int t=top;
654     top=0; // ugly - prevents dimensionChanged events from being processed
655     xCombobox.setSelectedIndex(0);
656     yCombobox.setSelectedIndex(1);
657     top=t;
658     zCombobox.setSelectedIndex(2);
659   }
660 }