JAL-3949 Complete new abstracted logging framework in jalview.log. Updated log calls...
[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.api.AlignViewportI;
25 import jalview.api.analysis.ScoreModelI;
26 import jalview.api.analysis.SimilarityParamsI;
27 import jalview.bin.Cache;
28 import jalview.datamodel.Alignment;
29 import jalview.datamodel.AlignmentI;
30 import jalview.datamodel.AlignmentView;
31 import jalview.datamodel.HiddenColumns;
32 import jalview.datamodel.SequenceI;
33 import jalview.gui.ImageExporter.ImageWriterI;
34 import jalview.gui.JalviewColourChooser.ColourChooserListener;
35 import jalview.jbgui.GPCAPanel;
36 import jalview.math.RotatableMatrix.Axis;
37 import jalview.util.ImageMaker;
38 import jalview.util.MessageManager;
39 import jalview.viewmodel.AlignmentViewport;
40 import jalview.viewmodel.PCAModel;
41
42 import java.awt.BorderLayout;
43 import java.awt.Color;
44 import java.awt.Dimension;
45 import java.awt.Graphics;
46 import java.awt.event.ActionEvent;
47 import java.awt.event.ActionListener;
48 import java.awt.print.PageFormat;
49 import java.awt.print.Printable;
50 import java.awt.print.PrinterException;
51 import java.awt.print.PrinterJob;
52
53 import javax.swing.ButtonGroup;
54 import javax.swing.JMenuItem;
55 import javax.swing.JRadioButtonMenuItem;
56 import javax.swing.event.InternalFrameAdapter;
57 import javax.swing.event.InternalFrameEvent;
58
59 /**
60  * The panel holding the Principal Component Analysis 3-D visualisation
61  */
62 public class PCAPanel extends GPCAPanel
63         implements Runnable, IProgressIndicator
64 {
65   private static final int MIN_WIDTH = 470;
66
67   private static final int MIN_HEIGHT = 250;
68
69   private RotatableCanvas rc;
70
71   AlignmentPanel ap;
72
73   AlignmentViewport av;
74
75   private PCAModel pcaModel;
76
77   private int top = 0;
78
79   private IProgressIndicator progressBar;
80
81   private boolean working;
82
83   /**
84    * Constructor given sequence data, a similarity (or distance) score model
85    * name, and score calculation parameters
86    * 
87    * @param alignPanel
88    * @param modelName
89    * @param params
90    */
91   public PCAPanel(AlignmentPanel alignPanel, String modelName,
92           SimilarityParamsI params)
93   {
94     super();
95     this.av = alignPanel.av;
96     this.ap = alignPanel;
97     boolean nucleotide = av.getAlignment().isNucleotide();
98
99     progressBar = new ProgressBar(statusPanel, statusBar);
100
101     addInternalFrameListener(new InternalFrameAdapter()
102     {
103       @Override
104       public void internalFrameClosed(InternalFrameEvent e)
105       {
106         close_actionPerformed();
107       }
108     });
109
110     boolean selected = av.getSelectionGroup() != null
111             && av.getSelectionGroup().getSize() > 0;
112     AlignmentView seqstrings = av.getAlignmentView(selected);
113     SequenceI[] seqs;
114     if (!selected)
115     {
116       seqs = av.getAlignment().getSequencesArray();
117     }
118     else
119     {
120       seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment());
121     }
122
123     ScoreModelI scoreModel = ScoreModels.getInstance()
124             .getScoreModel(modelName, ap);
125     setPcaModel(new PCAModel(seqstrings, seqs, nucleotide, scoreModel,
126             params));
127     PaintRefresher.Register(this, av.getSequenceSetId());
128
129     setRotatableCanvas(new RotatableCanvas(alignPanel));
130     this.getContentPane().add(getRotatableCanvas(), BorderLayout.CENTER);
131
132     addKeyListener(getRotatableCanvas());
133     validate();
134   }
135
136   /**
137    * Ensure references to potentially very large objects (the PCA matrices) are
138    * nulled when the frame is closed
139    */
140   protected void close_actionPerformed()
141   {
142     setPcaModel(null);
143     if (this.rc != null)
144     {
145       this.rc.sequencePoints = null;
146       this.rc.setAxisEndPoints(null);
147       this.rc = null;
148     }
149   }
150
151   @Override
152   protected void bgcolour_actionPerformed()
153   {
154     String ttl = MessageManager.getString("label.select_background_colour");
155     ColourChooserListener listener = new ColourChooserListener()
156     {
157       @Override
158       public void colourSelected(Color c)
159       {
160         rc.setBgColour(c);
161         rc.repaint();
162       }
163     };
164     JalviewColourChooser.showColourChooser(this, ttl, rc.getBgColour(),
165             listener);
166   }
167
168   /**
169    * Calculates the PCA and displays the results
170    */
171   @Override
172   public void run()
173   {
174     working = true;
175     long progId = System.currentTimeMillis();
176     IProgressIndicator progress = this;
177     String message = MessageManager.getString("label.pca_recalculating");
178     if (getParent() == null)
179     {
180       progress = ap.alignFrame;
181       message = MessageManager.getString("label.pca_calculating");
182     }
183     progress.setProgressBar(message, progId);
184     try
185     {
186       getPcaModel().calculate();
187
188       xCombobox.setSelectedIndex(0);
189       yCombobox.setSelectedIndex(1);
190       zCombobox.setSelectedIndex(2);
191
192       getPcaModel().updateRc(getRotatableCanvas());
193       // rc.invalidate();
194       setTop(getPcaModel().getTop());
195
196     } catch (OutOfMemoryError er)
197     {
198       new OOMWarning("calculating PCA", er);
199       working = false;
200       return;
201     } finally
202     {
203       progress.setProgressBar("", progId);
204     }
205
206     repaint();
207     if (getParent() == null)
208     {
209       Desktop.addInternalFrame(this,
210               MessageManager.formatMessage("label.calc_title", "PCA",
211                       getPcaModel().getScoreModelName()),
212               475, 450);
213       this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
214     }
215     working = false;
216   }
217
218   /**
219    * Updates the PCA display after a change of component to use for x, y or z
220    * axis
221    */
222   @Override
223   protected void doDimensionChange()
224   {
225     if (getTop() == 0)
226     {
227       return;
228     }
229
230     int dim1 = getTop() - xCombobox.getSelectedIndex();
231     int dim2 = getTop() - yCombobox.getSelectedIndex();
232     int dim3 = getTop() - zCombobox.getSelectedIndex();
233     getPcaModel().updateRcView(dim1, dim2, dim3);
234     getRotatableCanvas().resetView();
235   }
236
237   /**
238    * Sets the selected checkbox item index for PCA dimension (1, 2, 3...) for
239    * the given axis (X/Y/Z)
240    * 
241    * @param index
242    * @param axis
243    */
244   public void setSelectedDimensionIndex(int index, Axis axis)
245   {
246     switch (axis)
247     {
248     case X:
249       xCombobox.setSelectedIndex(index);
250       break;
251     case Y:
252       yCombobox.setSelectedIndex(index);
253       break;
254     case Z:
255       zCombobox.setSelectedIndex(index);
256       break;
257     default:
258     }
259   }
260
261   @Override
262   protected void outputValues_actionPerformed()
263   {
264     CutAndPasteTransfer cap = new CutAndPasteTransfer();
265     try
266     {
267       cap.setText(getPcaModel().getDetails());
268       Desktop.addInternalFrame(cap,
269               MessageManager.getString("label.pca_details"), 500, 500);
270     } catch (OutOfMemoryError oom)
271     {
272       new OOMWarning("opening PCA details", oom);
273       cap.dispose();
274     }
275   }
276
277   @Override
278   protected void showLabels_actionPerformed()
279   {
280     getRotatableCanvas().showLabels(showLabels.getState());
281   }
282
283   @Override
284   protected void print_actionPerformed()
285   {
286     PCAPrinter printer = new PCAPrinter();
287     printer.start();
288   }
289
290   /**
291    * If available, shows the data which formed the inputs for the PCA as a new
292    * alignment
293    */
294   @Override
295   public void originalSeqData_actionPerformed()
296   {
297     // JAL-2647 disabled after load from project (until save to project done)
298     if (getPcaModel().getInputData() == null)
299     {
300       Cache.info(
301               "Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
302       return;
303     }
304     // decide if av alignment is sufficiently different to original data to
305     // warrant a new window to be created
306     // create new alignment window with hidden regions (unhiding hidden regions
307     // yields unaligned seqs)
308     // or create a selection box around columns in alignment view
309     // test Alignment(SeqCigar[])
310     char gc = '-';
311     try
312     {
313       // we try to get the associated view's gap character
314       // but this may fail if the view was closed...
315       gc = av.getGapCharacter();
316     } catch (Exception ex)
317     {
318     }
319
320     Object[] alAndColsel = getPcaModel().getInputData()
321             .getAlignmentAndHiddenColumns(gc);
322
323     if (alAndColsel != null && alAndColsel[0] != null)
324     {
325       // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
326
327       AlignmentI al = new Alignment((SequenceI[]) alAndColsel[0]);
328       AlignmentI dataset = (av != null && av.getAlignment() != null)
329               ? av.getAlignment().getDataset()
330               : null;
331       if (dataset != null)
332       {
333         al.setDataset(dataset);
334       }
335
336       if (true)
337       {
338         // make a new frame!
339         AlignFrame af = new AlignFrame(al, (HiddenColumns) alAndColsel[1],
340                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
341
342         // >>>This is a fix for the moment, until a better solution is
343         // found!!<<<
344         // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
345
346         // af.addSortByOrderMenuItem(ServiceName + " Ordering",
347         // msaorder);
348
349         Desktop.addInternalFrame(af, MessageManager.formatMessage(
350                 "label.original_data_for_params", new String[]
351                 { this.title }), AlignFrame.DEFAULT_WIDTH,
352                 AlignFrame.DEFAULT_HEIGHT);
353       }
354     }
355     /*
356      * CutAndPasteTransfer cap = new CutAndPasteTransfer(); for (int i = 0; i <
357      * seqs.length; i++) { cap.appendText(new jalview.util.Format("%-" + 15 +
358      * "s").form( seqs[i].getName())); cap.appendText(" " + seqstrings[i] +
359      * "\n"); }
360      * 
361      * Desktop.addInternalFrame(cap, "Original Data", 400, 400);
362      */
363   }
364
365   class PCAPrinter extends Thread implements Printable
366   {
367     @Override
368     public void run()
369     {
370       PrinterJob printJob = PrinterJob.getPrinterJob();
371       PageFormat defaultPage = printJob.defaultPage();
372       PageFormat pf = printJob.pageDialog(defaultPage);
373
374       if (defaultPage == pf)
375       {
376         /*
377          * user cancelled
378          */
379         return;
380       }
381
382       printJob.setPrintable(this, pf);
383
384       if (printJob.printDialog())
385       {
386         try
387         {
388           printJob.print();
389         } catch (Exception PrintException)
390         {
391           PrintException.printStackTrace();
392         }
393       }
394     }
395
396     @Override
397     public int print(Graphics pg, PageFormat pf, int pi)
398             throws PrinterException
399     {
400       pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
401
402       getRotatableCanvas().drawBackground(pg);
403       getRotatableCanvas().drawScene(pg);
404       if (getRotatableCanvas().drawAxes)
405       {
406         getRotatableCanvas().drawAxes(pg);
407       }
408
409       if (pi == 0)
410       {
411         return Printable.PAGE_EXISTS;
412       }
413       else
414       {
415         return Printable.NO_SUCH_PAGE;
416       }
417     }
418   }
419
420   public void makePCAImage(ImageMaker.TYPE type)
421   {
422     int width = getRotatableCanvas().getWidth();
423     int height = getRotatableCanvas().getHeight();
424     ImageWriterI writer = new ImageWriterI()
425     {
426       @Override
427       public void exportImage(Graphics g) throws Exception
428       {
429         RotatableCanvas canvas = getRotatableCanvas();
430         canvas.drawBackground(g);
431         canvas.drawScene(g);
432         if (canvas.drawAxes)
433         {
434           canvas.drawAxes(g);
435         }
436       }
437     };
438     String pca = MessageManager.getString("label.pca");
439     ImageExporter exporter = new ImageExporter(writer, null, type, pca);
440     exporter.doExport(null, this, width, height, pca);
441   }
442
443   @Override
444   protected void viewMenu_menuSelected()
445   {
446     buildAssociatedViewMenu();
447   }
448
449   /**
450    * Builds the menu showing the choice of possible views (for the associated
451    * sequence data) to which the PCA may be linked
452    */
453   void buildAssociatedViewMenu()
454   {
455     AlignmentPanel[] aps = PaintRefresher
456             .getAssociatedPanels(av.getSequenceSetId());
457     if (aps.length == 1 && getRotatableCanvas().av == aps[0].av)
458     {
459       associateViewsMenu.setVisible(false);
460       return;
461     }
462
463     associateViewsMenu.setVisible(true);
464
465     if ((viewMenu
466             .getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
467     {
468       viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
469     }
470
471     associateViewsMenu.removeAll();
472
473     JRadioButtonMenuItem item;
474     ButtonGroup buttonGroup = new ButtonGroup();
475     int iSize = aps.length;
476
477     for (int i = 0; i < iSize; i++)
478     {
479       final AlignmentPanel panel = aps[i];
480       item = new JRadioButtonMenuItem(panel.av.getViewName(),
481               panel.av == getRotatableCanvas().av);
482       buttonGroup.add(item);
483       item.addActionListener(new ActionListener()
484       {
485         @Override
486         public void actionPerformed(ActionEvent evt)
487         {
488           selectAssociatedView(panel);
489         }
490       });
491
492       associateViewsMenu.add(item);
493     }
494
495     final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem(
496             "All Views");
497
498     buttonGroup.add(itemf);
499
500     itemf.setSelected(getRotatableCanvas().isApplyToAllViews());
501     itemf.addActionListener(new ActionListener()
502     {
503       @Override
504       public void actionPerformed(ActionEvent evt)
505       {
506         getRotatableCanvas().setApplyToAllViews(itemf.isSelected());
507       }
508     });
509     associateViewsMenu.add(itemf);
510
511   }
512
513   /*
514    * (non-Javadoc)
515    * 
516    * @see
517    * jalview.jbgui.GPCAPanel#outputPoints_actionPerformed(java.awt.event.ActionEvent
518    * )
519    */
520   @Override
521   protected void outputPoints_actionPerformed()
522   {
523     CutAndPasteTransfer cap = new CutAndPasteTransfer();
524     try
525     {
526       cap.setText(getPcaModel().getPointsasCsv(false,
527               xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(),
528               zCombobox.getSelectedIndex()));
529       Desktop.addInternalFrame(cap, MessageManager
530               .formatMessage("label.points_for_params", new String[]
531               { this.getTitle() }), 500, 500);
532     } catch (OutOfMemoryError oom)
533     {
534       new OOMWarning("exporting PCA points", oom);
535       cap.dispose();
536     }
537   }
538
539   /*
540    * (non-Javadoc)
541    * 
542    * @see
543    * jalview.jbgui.GPCAPanel#outputProjPoints_actionPerformed(java.awt.event
544    * .ActionEvent)
545    */
546   @Override
547   protected void outputProjPoints_actionPerformed()
548   {
549     CutAndPasteTransfer cap = new CutAndPasteTransfer();
550     try
551     {
552       cap.setText(getPcaModel().getPointsasCsv(true,
553               xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(),
554               zCombobox.getSelectedIndex()));
555       Desktop.addInternalFrame(cap, MessageManager.formatMessage(
556               "label.transformed_points_for_params", new String[]
557               { this.getTitle() }), 500, 500);
558     } catch (OutOfMemoryError oom)
559     {
560       new OOMWarning("exporting transformed PCA points", oom);
561       cap.dispose();
562     }
563   }
564
565   /*
566    * (non-Javadoc)
567    * 
568    * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
569    */
570   @Override
571   public void setProgressBar(String message, long id)
572   {
573     progressBar.setProgressBar(message, id);
574     // if (progressBars == null)
575     // {
576     // progressBars = new Hashtable();
577     // progressBarHandlers = new Hashtable();
578     // }
579     //
580     // JPanel progressPanel;
581     // Long lId = Long.valueOf(id);
582     // GridLayout layout = (GridLayout) statusPanel.getLayout();
583     // if (progressBars.get(lId) != null)
584     // {
585     // progressPanel = (JPanel) progressBars.get(Long.valueOf(id));
586     // statusPanel.remove(progressPanel);
587     // progressBars.remove(lId);
588     // progressPanel = null;
589     // if (message != null)
590     // {
591     // statusBar.setText(message);
592     // }
593     // if (progressBarHandlers.contains(lId))
594     // {
595     // progressBarHandlers.remove(lId);
596     // }
597     // layout.setRows(layout.getRows() - 1);
598     // }
599     // else
600     // {
601     // progressPanel = new JPanel(new BorderLayout(10, 5));
602     //
603     // JProgressBar progressBar = new JProgressBar();
604     // progressBar.setIndeterminate(true);
605     //
606     // progressPanel.add(new JLabel(message), BorderLayout.WEST);
607     // progressPanel.add(progressBar, BorderLayout.CENTER);
608     //
609     // layout.setRows(layout.getRows() + 1);
610     // statusPanel.add(progressPanel);
611     //
612     // progressBars.put(lId, progressPanel);
613     // }
614     // // update GUI
615     // // setMenusForViewport();
616     // validate();
617   }
618
619   @Override
620   public void registerHandler(final long id,
621           final IProgressIndicatorHandler handler)
622   {
623     progressBar.registerHandler(id, handler);
624     // if (progressBarHandlers == null || !progressBars.contains(Long.valueOf(id)))
625     // {
626     // throw new
627     // Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
628     // }
629     // progressBarHandlers.put(Long.valueOf(id), handler);
630     // final JPanel progressPanel = (JPanel) progressBars.get(Long.valueOf(id));
631     // if (handler.canCancel())
632     // {
633     // JButton cancel = new JButton(
634     // MessageManager.getString("action.cancel"));
635     // final IProgressIndicator us = this;
636     // cancel.addActionListener(new ActionListener()
637     // {
638     //
639     // @Override
640     // public void actionPerformed(ActionEvent e)
641     // {
642     // handler.cancelActivity(id);
643     // us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
644     // new String[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
645     // }
646     // });
647     // progressPanel.add(cancel, BorderLayout.EAST);
648     // }
649   }
650
651   /**
652    * 
653    * @return true if any progress bars are still active
654    */
655   @Override
656   public boolean operationInProgress()
657   {
658     return progressBar.operationInProgress();
659   }
660
661   @Override
662   protected void resetButton_actionPerformed()
663   {
664     int t = getTop();
665     setTop(0); // ugly - prevents dimensionChanged events from being processed
666     xCombobox.setSelectedIndex(0);
667     yCombobox.setSelectedIndex(1);
668     setTop(t);
669     zCombobox.setSelectedIndex(2);
670   }
671
672   /**
673    * Answers true if PCA calculation is in progress, else false
674    * 
675    * @return
676    */
677   public boolean isWorking()
678   {
679     return working;
680   }
681
682   /**
683    * Answers the selected checkbox item index for PCA dimension for the X, Y or
684    * Z axis of the display
685    * 
686    * @param axis
687    * @return
688    */
689   public int getSelectedDimensionIndex(Axis axis)
690   {
691     switch (axis)
692     {
693     case X:
694       return xCombobox.getSelectedIndex();
695     case Y:
696       return yCombobox.getSelectedIndex();
697     default:
698       return zCombobox.getSelectedIndex();
699     }
700   }
701
702   public void setShowLabels(boolean show)
703   {
704     showLabels.setSelected(show);
705   }
706
707   /**
708    * Sets the input data used to calculate the PCA. This is provided for
709    * 'restore from project', which does not currently support this (AL-2647), so
710    * sets the value to null, and hides the menu option for "Input Data...". J
711    * 
712    * @param data
713    */
714   public void setInputData(AlignmentView data)
715   {
716     getPcaModel().setInputData(data);
717     originalSeqData.setVisible(data != null);
718   }
719
720   public AlignViewportI getAlignViewport()
721   {
722     return av;
723   }
724
725   public PCAModel getPcaModel()
726   {
727     return pcaModel;
728   }
729
730   public void setPcaModel(PCAModel pcaModel)
731   {
732     this.pcaModel = pcaModel;
733   }
734
735   public RotatableCanvas getRotatableCanvas()
736   {
737     return rc;
738   }
739
740   public void setRotatableCanvas(RotatableCanvas rc)
741   {
742     this.rc = rc;
743   }
744
745   public int getTop()
746   {
747     return top;
748   }
749
750   public void setTop(int top)
751   {
752     this.top = top;
753   }
754
755   /**
756    * set the associated view for this PCA.
757    * 
758    * @param panel
759    */
760   public void selectAssociatedView(AlignmentPanel panel)
761   {
762     getRotatableCanvas().setApplyToAllViews(false);
763
764     ap = panel;
765     av = panel.av;
766
767     getRotatableCanvas().av = panel.av;
768     getRotatableCanvas().ap = panel;
769     PaintRefresher.Register(PCAPanel.this, panel.av.getSequenceSetId());
770   }
771 }