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