f861a7c1cba3229922092a33d05568e313fc7148
[jalview.git] / src / jalview / gui / PCAPanel.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
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
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.gui;
22
23 import jalview.analysis.scoremodels.ScoreModels;
24 import jalview.analysis.scoremodels.SimilarityParams;
25 import jalview.api.analysis.ScoreModelI;
26 import jalview.api.analysis.SimilarityParamsI;
27 import jalview.datamodel.Alignment;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.AlignmentView;
30 import jalview.datamodel.HiddenColumns;
31 import jalview.datamodel.SequenceI;
32 import jalview.jbgui.GPCAPanel;
33 import jalview.util.MessageManager;
34 import jalview.viewmodel.AlignmentViewport;
35 import jalview.viewmodel.PCAModel;
36
37 import java.awt.BorderLayout;
38 import java.awt.Color;
39 import java.awt.Dimension;
40 import java.awt.Graphics;
41 import java.awt.event.ActionEvent;
42 import java.awt.event.ActionListener;
43 import java.awt.print.PageFormat;
44 import java.awt.print.Printable;
45 import java.awt.print.PrinterException;
46 import java.awt.print.PrinterJob;
47
48 import javax.swing.ButtonGroup;
49 import javax.swing.JCheckBoxMenuItem;
50 import javax.swing.JColorChooser;
51 import javax.swing.JMenuItem;
52 import javax.swing.JRadioButtonMenuItem;
53 import javax.swing.event.InternalFrameAdapter;
54 import javax.swing.event.InternalFrameEvent;
55
56 /**
57  * DOCUMENT ME!
58  * 
59  * @author $author$
60  * @version $Revision$
61  */
62 public class PCAPanel extends GPCAPanel
63         implements Runnable, IProgressIndicator
64 {
65
66   private IProgressIndicator progressBar;
67
68   RotatableCanvas rc;
69
70   AlignmentPanel ap;
71
72   AlignmentViewport av;
73
74   PCAModel pcaModel;
75
76   private static final int MIN_WIDTH = 470;
77
78   private static final int MIN_HEIGHT = 250;
79
80   int top = 0;
81
82   /**
83    * Creates a new PCAPanel object using default score model and parameters
84    * 
85    * @param alignPanel
86    */
87   public PCAPanel(AlignmentPanel alignPanel)
88   {
89     this(alignPanel,
90             ScoreModels.getInstance()
91                     .getDefaultModel(
92                             !alignPanel.av.getAlignment().isNucleotide())
93                     .getName(),
94             SimilarityParams.SeqSpace);
95   }
96
97   /**
98    * Constructor given sequence data, a similarity (or distance) score model
99    * name, and score calculation parameters
100    * 
101    * @param alignPanel
102    * @param modelName
103    * @param params
104    */
105   public PCAPanel(AlignmentPanel alignPanel, String modelName,
106           SimilarityParamsI params)
107   {
108     super();
109     this.av = alignPanel.av;
110     this.ap = alignPanel;
111     boolean nucleotide = av.getAlignment().isNucleotide();
112
113     progressBar = new ProgressBar(statusPanel, statusBar);
114
115     addInternalFrameListener(new InternalFrameAdapter()
116     {
117       @Override
118       public void internalFrameClosed(InternalFrameEvent e)
119       {
120         close_actionPerformed();
121       }
122     });
123
124     boolean selected = av.getSelectionGroup() != null
125             && av.getSelectionGroup().getSize() > 0;
126     AlignmentView seqstrings = av.getAlignmentView(selected);
127     SequenceI[] seqs;
128     if (!selected)
129     {
130       seqs = av.getAlignment().getSequencesArray();
131     }
132     else
133     {
134       seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment());
135     }
136
137     ScoreModelI scoreModel = ScoreModels.getInstance()
138             .getScoreModel(modelName, ap);
139     pcaModel = new PCAModel(seqstrings, seqs, nucleotide, scoreModel,
140             params);
141     PaintRefresher.Register(this, av.getSequenceSetId());
142
143     rc = new RotatableCanvas(alignPanel);
144     this.getContentPane().add(rc, BorderLayout.CENTER);
145     Thread worker = new Thread(this);
146     worker.start();
147   }
148
149   /**
150    * Ensure references to potentially very large objects (the PCA matrices) are
151    * nulled when the frame is closed
152    */
153   protected void close_actionPerformed()
154   {
155     pcaModel = null;
156   }
157
158   /**
159    * Repopulate the options and actions under the score model menu when it is
160    * selected. Options will depend on whether 'nucleotide' or 'peptide'
161    * modelling is selected (and also possibly on whether any additional score
162    * models have been added).
163    */
164   @Override
165   protected void scoreModel_menuSelected()
166   {
167     scoreModelMenu.removeAll();
168     for (final ScoreModelI sm : ScoreModels.getInstance().getModels())
169     {
170       final String name = sm.getName();
171       JCheckBoxMenuItem jm = new JCheckBoxMenuItem(name);
172
173       /*
174        * if the score model doesn't provide a description, try to look one
175        * up in the text bundle, falling back on its name
176        */
177       String tooltip = sm.getDescription();
178       if (tooltip == null)
179       {
180         tooltip = MessageManager.getStringOrReturn("label.score_model_",
181                 name);
182       }
183       jm.setToolTipText(tooltip);
184       jm.setSelected(pcaModel.getScoreModelName().equals(name));
185       if ((pcaModel.isNucleotide() && sm.isDNA())
186               || (!pcaModel.isNucleotide() && sm.isProtein()))
187       {
188         jm.addActionListener(new ActionListener()
189         {
190           @Override
191           public void actionPerformed(ActionEvent e)
192           {
193             if (!pcaModel.getScoreModelName().equals(name))
194             {
195               ScoreModelI sm2 = ScoreModels.getInstance()
196                       .getScoreModel(name, ap);
197               pcaModel.setScoreModel(sm2);
198               Thread worker = new Thread(PCAPanel.this);
199               worker.start();
200             }
201           }
202         });
203         scoreModelMenu.add(jm);
204       }
205     }
206   }
207
208   @Override
209   public void bgcolour_actionPerformed(ActionEvent e)
210   {
211     Color col = JColorChooser.showDialog(this,
212             MessageManager.getString("label.select_background_colour"),
213             rc.bgColour);
214
215     if (col != null)
216     {
217       rc.bgColour = col;
218     }
219     rc.repaint();
220   }
221
222   /**
223    * DOCUMENT ME!
224    */
225   @Override
226   public void run()
227   {
228     long progId = System.currentTimeMillis();
229     IProgressIndicator progress = this;
230     String message = MessageManager.getString("label.pca_recalculating");
231     if (getParent() == null)
232     {
233       progress = ap.alignFrame;
234       message = MessageManager.getString("label.pca_calculating");
235     }
236     progress.setProgressBar(message, progId);
237     try
238     {
239       calcSettings.setEnabled(false);
240       pcaModel.run();
241       // ////////////////
242       xCombobox.setSelectedIndex(0);
243       yCombobox.setSelectedIndex(1);
244       zCombobox.setSelectedIndex(2);
245
246       pcaModel.updateRc(rc);
247       // rc.invalidate();
248       nuclSetting.setSelected(pcaModel.isNucleotide());
249       protSetting.setSelected(!pcaModel.isNucleotide());
250       top = pcaModel.getTop();
251
252     } catch (OutOfMemoryError er)
253     {
254       new OOMWarning("calculating PCA", er);
255       return;
256     } finally
257     {
258       progress.setProgressBar("", progId);
259     }
260     calcSettings.setEnabled(true);
261     repaint();
262     if (getParent() == null)
263     {
264       addKeyListener(rc);
265       Desktop.addInternalFrame(this, MessageManager
266               .getString("label.principal_component_analysis"), 475, 450);
267       this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
268     }
269   }
270
271   @Override
272   protected void nuclSetting_actionPerfomed(ActionEvent arg0)
273   {
274     if (!pcaModel.isNucleotide())
275     {
276       pcaModel.setNucleotide(true);
277       pcaModel.setScoreModel(
278               ScoreModels.getInstance().getDefaultModel(false));
279       Thread worker = new Thread(this);
280       worker.start();
281     }
282
283   }
284
285   @Override
286   protected void protSetting_actionPerfomed(ActionEvent arg0)
287   {
288
289     if (pcaModel.isNucleotide())
290     {
291       pcaModel.setNucleotide(false);
292       pcaModel.setScoreModel(
293               ScoreModels.getInstance().getDefaultModel(true));
294       Thread worker = new Thread(this);
295       worker.start();
296     }
297   }
298
299   /**
300    * DOCUMENT ME!
301    */
302   void doDimensionChange()
303   {
304     if (top == 0)
305     {
306       return;
307     }
308
309     int dim1 = top - xCombobox.getSelectedIndex();
310     int dim2 = top - yCombobox.getSelectedIndex();
311     int dim3 = top - zCombobox.getSelectedIndex();
312     pcaModel.updateRcView(dim1, dim2, dim3);
313     rc.img = null;
314     rc.rotmat.setIdentity();
315     rc.initAxes();
316     rc.paint(rc.getGraphics());
317   }
318
319   /**
320    * DOCUMENT ME!
321    * 
322    * @param e
323    *          DOCUMENT ME!
324    */
325   @Override
326   protected void xCombobox_actionPerformed(ActionEvent e)
327   {
328     doDimensionChange();
329   }
330
331   /**
332    * DOCUMENT ME!
333    * 
334    * @param e
335    *          DOCUMENT ME!
336    */
337   @Override
338   protected void yCombobox_actionPerformed(ActionEvent e)
339   {
340     doDimensionChange();
341   }
342
343   /**
344    * DOCUMENT ME!
345    * 
346    * @param e
347    *          DOCUMENT ME!
348    */
349   @Override
350   protected void zCombobox_actionPerformed(ActionEvent e)
351   {
352     doDimensionChange();
353   }
354
355   @Override
356   public void outputValues_actionPerformed(ActionEvent e)
357   {
358     CutAndPasteTransfer cap = new CutAndPasteTransfer();
359     try
360     {
361       cap.setText(pcaModel.getDetails());
362       Desktop.addInternalFrame(cap,
363               MessageManager.getString("label.pca_details"), 500, 500);
364     } catch (OutOfMemoryError oom)
365     {
366       new OOMWarning("opening PCA details", oom);
367       cap.dispose();
368     }
369   }
370
371   @Override
372   public void showLabels_actionPerformed(ActionEvent e)
373   {
374     rc.showLabels(showLabels.getState());
375   }
376
377   @Override
378   public void print_actionPerformed(ActionEvent e)
379   {
380     PCAPrinter printer = new PCAPrinter();
381     printer.start();
382   }
383
384   @Override
385   public void originalSeqData_actionPerformed(ActionEvent e)
386   {
387     // this was cut'n'pasted from the equivalent TreePanel method - we should
388     // make this an abstract function of all jalview analysis windows
389     if (pcaModel.getSeqtrings() == null)
390     {
391       jalview.bin.Cache.log.info(
392               "Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
393       return;
394     }
395     // decide if av alignment is sufficiently different to original data to
396     // warrant a new window to be created
397     // create new alignmnt window with hidden regions (unhiding hidden regions
398     // yields unaligned seqs)
399     // or create a selection box around columns in alignment view
400     // test Alignment(SeqCigar[])
401     char gc = '-';
402     try
403     {
404       // we try to get the associated view's gap character
405       // but this may fail if the view was closed...
406       gc = av.getGapCharacter();
407     } catch (Exception ex)
408     {
409     }
410     ;
411     Object[] alAndColsel = pcaModel.getSeqtrings()
412             .getAlignmentAndHiddenColumns(gc);
413
414     if (alAndColsel != null && alAndColsel[0] != null)
415     {
416       // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
417
418       AlignmentI al = new Alignment((SequenceI[]) alAndColsel[0]);
419       AlignmentI dataset = (av != null && av.getAlignment() != null)
420               ? av.getAlignment().getDataset()
421               : null;
422       if (dataset != null)
423       {
424         al.setDataset(dataset);
425       }
426
427       if (true)
428       {
429         // make a new frame!
430         AlignFrame af = new AlignFrame(al, (HiddenColumns) alAndColsel[1],
431                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
432
433         // >>>This is a fix for the moment, until a better solution is
434         // found!!<<<
435         // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
436
437         // af.addSortByOrderMenuItem(ServiceName + " Ordering",
438         // msaorder);
439
440         Desktop.addInternalFrame(af, MessageManager.formatMessage(
441                 "label.original_data_for_params", new String[]
442                 { this.title }), AlignFrame.DEFAULT_WIDTH,
443                 AlignFrame.DEFAULT_HEIGHT);
444       }
445     }
446     /*
447      * CutAndPasteTransfer cap = new CutAndPasteTransfer(); for (int i = 0; i <
448      * seqs.length; i++) { cap.appendText(new jalview.util.Format("%-" + 15 +
449      * "s").form( seqs[i].getName())); cap.appendText(" " + seqstrings[i] +
450      * "\n"); }
451      * 
452      * Desktop.addInternalFrame(cap, "Original Data", 400, 400);
453      */
454   }
455
456   class PCAPrinter extends Thread implements Printable
457   {
458     @Override
459     public void run()
460     {
461       PrinterJob printJob = PrinterJob.getPrinterJob();
462       PageFormat pf = printJob.pageDialog(printJob.defaultPage());
463
464       printJob.setPrintable(this, pf);
465
466       if (printJob.printDialog())
467       {
468         try
469         {
470           printJob.print();
471         } catch (Exception PrintException)
472         {
473           PrintException.printStackTrace();
474         }
475       }
476     }
477
478     @Override
479     public int print(Graphics pg, PageFormat pf, int pi)
480             throws PrinterException
481     {
482       pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
483
484       rc.drawBackground(pg, rc.bgColour);
485       rc.drawScene(pg);
486       if (rc.drawAxes == true)
487       {
488         rc.drawAxes(pg);
489       }
490
491       if (pi == 0)
492       {
493         return Printable.PAGE_EXISTS;
494       }
495       else
496       {
497         return Printable.NO_SUCH_PAGE;
498       }
499     }
500   }
501
502   /**
503    * DOCUMENT ME!
504    * 
505    * @param e
506    *          DOCUMENT ME!
507    */
508   @Override
509   public void eps_actionPerformed(ActionEvent e)
510   {
511     makePCAImage(jalview.util.ImageMaker.TYPE.EPS);
512   }
513
514   /**
515    * DOCUMENT ME!
516    * 
517    * @param e
518    *          DOCUMENT ME!
519    */
520   @Override
521   public void png_actionPerformed(ActionEvent e)
522   {
523     makePCAImage(jalview.util.ImageMaker.TYPE.PNG);
524   }
525
526   void makePCAImage(jalview.util.ImageMaker.TYPE type)
527   {
528     int width = rc.getWidth();
529     int height = rc.getHeight();
530
531     jalview.util.ImageMaker im;
532
533     if (type == jalview.util.ImageMaker.TYPE.PNG)
534     {
535       im = new jalview.util.ImageMaker(this,
536               jalview.util.ImageMaker.TYPE.PNG, "Make PNG image from PCA",
537               width, height, null, null, null, 0, false);
538     }
539     else if (type == jalview.util.ImageMaker.TYPE.EPS)
540     {
541       im = new jalview.util.ImageMaker(this,
542               jalview.util.ImageMaker.TYPE.EPS, "Make EPS file from PCA",
543               width, height, null, this.getTitle(), null, 0, false);
544     }
545     else
546     {
547       im = new jalview.util.ImageMaker(this,
548               jalview.util.ImageMaker.TYPE.SVG, "Make SVG file from PCA",
549               width, height, null, this.getTitle(), null, 0, false);
550
551     }
552
553     if (im.getGraphics() != null)
554     {
555       rc.drawBackground(im.getGraphics(), Color.black);
556       rc.drawScene(im.getGraphics());
557       if (rc.drawAxes == true)
558       {
559         rc.drawAxes(im.getGraphics());
560       }
561       im.writeImage();
562     }
563   }
564
565   @Override
566   public void viewMenu_menuSelected()
567   {
568     buildAssociatedViewMenu();
569   }
570
571   void buildAssociatedViewMenu()
572   {
573     AlignmentPanel[] aps = PaintRefresher
574             .getAssociatedPanels(av.getSequenceSetId());
575     if (aps.length == 1 && rc.av == aps[0].av)
576     {
577       associateViewsMenu.setVisible(false);
578       return;
579     }
580
581     associateViewsMenu.setVisible(true);
582
583     if ((viewMenu
584             .getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
585     {
586       viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
587     }
588
589     associateViewsMenu.removeAll();
590
591     JRadioButtonMenuItem item;
592     ButtonGroup buttonGroup = new ButtonGroup();
593     int i, iSize = aps.length;
594     final PCAPanel thisPCAPanel = this;
595     for (i = 0; i < iSize; i++)
596     {
597       final AlignmentPanel ap = aps[i];
598       item = new JRadioButtonMenuItem(ap.av.viewName, ap.av == rc.av);
599       buttonGroup.add(item);
600       item.addActionListener(new ActionListener()
601       {
602         @Override
603         public void actionPerformed(ActionEvent evt)
604         {
605           rc.applyToAllViews = false;
606           rc.av = ap.av;
607           rc.ap = ap;
608           PaintRefresher.Register(thisPCAPanel, ap.av.getSequenceSetId());
609         }
610       });
611
612       associateViewsMenu.add(item);
613     }
614
615     final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem(
616             "All Views");
617
618     buttonGroup.add(itemf);
619
620     itemf.setSelected(rc.applyToAllViews);
621     itemf.addActionListener(new ActionListener()
622     {
623       @Override
624       public void actionPerformed(ActionEvent evt)
625       {
626         rc.applyToAllViews = itemf.isSelected();
627       }
628     });
629     associateViewsMenu.add(itemf);
630
631   }
632
633   /*
634    * (non-Javadoc)
635    * 
636    * @see
637    * jalview.jbgui.GPCAPanel#outputPoints_actionPerformed(java.awt.event.ActionEvent
638    * )
639    */
640   @Override
641   protected void outputPoints_actionPerformed(ActionEvent e)
642   {
643     CutAndPasteTransfer cap = new CutAndPasteTransfer();
644     try
645     {
646       cap.setText(pcaModel.getPointsasCsv(false,
647               xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(),
648               zCombobox.getSelectedIndex()));
649       Desktop.addInternalFrame(cap, MessageManager
650               .formatMessage("label.points_for_params", new String[]
651               { this.getTitle() }), 500, 500);
652     } catch (OutOfMemoryError oom)
653     {
654       new OOMWarning("exporting PCA points", oom);
655       cap.dispose();
656     }
657   }
658
659   /*
660    * (non-Javadoc)
661    * 
662    * @see
663    * jalview.jbgui.GPCAPanel#outputProjPoints_actionPerformed(java.awt.event
664    * .ActionEvent)
665    */
666   @Override
667   protected void outputProjPoints_actionPerformed(ActionEvent e)
668   {
669     CutAndPasteTransfer cap = new CutAndPasteTransfer();
670     try
671     {
672       cap.setText(pcaModel.getPointsasCsv(true,
673               xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(),
674               zCombobox.getSelectedIndex()));
675       Desktop.addInternalFrame(cap, MessageManager.formatMessage(
676               "label.transformed_points_for_params", new String[]
677               { this.getTitle() }), 500, 500);
678     } catch (OutOfMemoryError oom)
679     {
680       new OOMWarning("exporting transformed PCA points", oom);
681       cap.dispose();
682     }
683   }
684
685   /*
686    * (non-Javadoc)
687    * 
688    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
689    */
690   @Override
691   public void setProgressBar(String message, long id)
692   {
693     progressBar.setProgressBar(message, id);
694     // if (progressBars == null)
695     // {
696     // progressBars = new Hashtable();
697     // progressBarHandlers = new Hashtable();
698     // }
699     //
700     // JPanel progressPanel;
701     // Long lId = new Long(id);
702     // GridLayout layout = (GridLayout) statusPanel.getLayout();
703     // if (progressBars.get(lId) != null)
704     // {
705     // progressPanel = (JPanel) progressBars.get(new Long(id));
706     // statusPanel.remove(progressPanel);
707     // progressBars.remove(lId);
708     // progressPanel = null;
709     // if (message != null)
710     // {
711     // statusBar.setText(message);
712     // }
713     // if (progressBarHandlers.contains(lId))
714     // {
715     // progressBarHandlers.remove(lId);
716     // }
717     // layout.setRows(layout.getRows() - 1);
718     // }
719     // else
720     // {
721     // progressPanel = new JPanel(new BorderLayout(10, 5));
722     //
723     // JProgressBar progressBar = new JProgressBar();
724     // progressBar.setIndeterminate(true);
725     //
726     // progressPanel.add(new JLabel(message), BorderLayout.WEST);
727     // progressPanel.add(progressBar, BorderLayout.CENTER);
728     //
729     // layout.setRows(layout.getRows() + 1);
730     // statusPanel.add(progressPanel);
731     //
732     // progressBars.put(lId, progressPanel);
733     // }
734     // // update GUI
735     // // setMenusForViewport();
736     // validate();
737   }
738
739   @Override
740   public void registerHandler(final long id,
741           final IProgressIndicatorHandler handler)
742   {
743     progressBar.registerHandler(id, handler);
744     // if (progressBarHandlers == null || !progressBars.contains(new Long(id)))
745     // {
746     // throw new
747     // Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
748     // }
749     // progressBarHandlers.put(new Long(id), handler);
750     // final JPanel progressPanel = (JPanel) progressBars.get(new Long(id));
751     // if (handler.canCancel())
752     // {
753     // JButton cancel = new JButton(
754     // MessageManager.getString("action.cancel"));
755     // final IProgressIndicator us = this;
756     // cancel.addActionListener(new ActionListener()
757     // {
758     //
759     // @Override
760     // public void actionPerformed(ActionEvent e)
761     // {
762     // handler.cancelActivity(id);
763     // us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
764     // new String[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
765     // }
766     // });
767     // progressPanel.add(cancel, BorderLayout.EAST);
768     // }
769   }
770
771   /**
772    * 
773    * @return true if any progress bars are still active
774    */
775   @Override
776   public boolean operationInProgress()
777   {
778     return progressBar.operationInProgress();
779   }
780
781   @Override
782   protected void resetButton_actionPerformed(ActionEvent e)
783   {
784     int t = top;
785     top = 0; // ugly - prevents dimensionChanged events from being processed
786     xCombobox.setSelectedIndex(0);
787     yCombobox.setSelectedIndex(1);
788     top = t;
789     zCombobox.setSelectedIndex(2);
790   }
791 }