2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Dimension;
26 import java.awt.Graphics;
27 import java.awt.event.ActionEvent;
28 import java.awt.event.ActionListener;
29 import java.awt.print.PageFormat;
30 import java.awt.print.Printable;
31 import java.awt.print.PrinterException;
32 import java.awt.print.PrinterJob;
34 import javax.swing.ButtonGroup;
35 import javax.swing.JMenuItem;
36 import javax.swing.JRadioButtonMenuItem;
37 import javax.swing.event.InternalFrameAdapter;
38 import javax.swing.event.InternalFrameEvent;
40 import jalview.analysis.scoremodels.ScoreModels;
41 import jalview.api.AlignViewportI;
42 import jalview.api.analysis.ScoreModelI;
43 import jalview.api.analysis.SimilarityParamsI;
44 import jalview.bin.Console;
45 import jalview.datamodel.Alignment;
46 import jalview.datamodel.AlignmentI;
47 import jalview.datamodel.AlignmentView;
48 import jalview.datamodel.HiddenColumns;
49 import jalview.datamodel.SequenceI;
50 import jalview.gui.ImageExporter.ImageWriterI;
51 import jalview.gui.JalviewColourChooser.ColourChooserListener;
52 import jalview.io.exceptions.ImageOutputException;
53 import jalview.jbgui.GPCAPanel;
54 import jalview.math.RotatableMatrix.Axis;
55 import jalview.util.ImageMaker;
56 import jalview.util.MessageManager;
57 import jalview.viewmodel.AlignmentViewport;
58 import jalview.viewmodel.PCAModel;
61 * The panel holding the Principal Component Analysis 3-D visualisation
63 public class PCAPanel extends GPCAPanel
64 implements Runnable, IProgressIndicator
66 private static final int MIN_WIDTH = 470;
68 private static final int MIN_HEIGHT = 250;
70 private RotatableCanvas rc;
76 private PCAModel pcaModel;
80 private IProgressIndicator progressBar;
82 private boolean working;
85 * Constructor given sequence data, a similarity (or distance) score model
86 * name, and score calculation parameters
92 public PCAPanel(AlignmentPanel alignPanel, String modelName,
93 SimilarityParamsI params)
96 this.setFrameIcon(null);
97 this.av = alignPanel.av;
99 boolean nucleotide = av.getAlignment().isNucleotide();
101 progressBar = new ProgressBar(statusPanel, statusBar);
103 addInternalFrameListener(new InternalFrameAdapter()
106 public void internalFrameClosed(InternalFrameEvent e)
108 close_actionPerformed();
112 boolean selected = av.getSelectionGroup() != null
113 && av.getSelectionGroup().getSize() > 0;
114 AlignmentView seqstrings = av.getAlignmentView(selected);
118 seqs = av.getAlignment().getSequencesArray();
122 seqs = av.getSelectionGroup().getSequencesInOrder(av.getAlignment());
125 ScoreModelI scoreModel = ScoreModels.getInstance()
126 .getScoreModel(modelName, ap);
128 new PCAModel(seqstrings, seqs, nucleotide, scoreModel, params));
129 PaintRefresher.Register(this, av.getSequenceSetId());
131 setRotatableCanvas(new RotatableCanvas(alignPanel));
132 this.getContentPane().add(getRotatableCanvas(), BorderLayout.CENTER);
134 addKeyListener(getRotatableCanvas());
139 * Ensure references to potentially very large objects (the PCA matrices) are
140 * nulled when the frame is closed
142 protected void close_actionPerformed()
147 this.rc.sequencePoints = null;
148 this.rc.setAxisEndPoints(null);
154 protected void bgcolour_actionPerformed()
156 String ttl = MessageManager.getString("label.select_background_colour");
157 ColourChooserListener listener = new ColourChooserListener()
160 public void colourSelected(Color c)
166 JalviewColourChooser.showColourChooser(this, ttl, rc.getBgColour(),
171 * Calculates the PCA and displays the results
177 long progId = System.currentTimeMillis();
178 IProgressIndicator progress = this;
179 String message = MessageManager.getString("label.pca_recalculating");
180 if (getParent() == null)
182 progress = ap.alignFrame;
183 message = MessageManager.getString("label.pca_calculating");
185 progress.setProgressBar(message, progId);
188 getPcaModel().calculate();
190 xCombobox.setSelectedIndex(0);
191 yCombobox.setSelectedIndex(1);
192 zCombobox.setSelectedIndex(2);
194 getPcaModel().updateRc(getRotatableCanvas());
196 setTop(getPcaModel().getTop());
198 } catch (OutOfMemoryError er)
200 new OOMWarning("calculating PCA", er);
205 progress.setProgressBar("", progId);
209 if (getParent() == null)
211 Desktop.addInternalFrame(this,
212 MessageManager.formatMessage("label.calc_title", "PCA",
213 getPcaModel().getScoreModelName()),
215 this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
221 * Updates the PCA display after a change of component to use for x, y or z
225 protected void doDimensionChange()
232 int dim1 = getTop() - xCombobox.getSelectedIndex();
233 int dim2 = getTop() - yCombobox.getSelectedIndex();
234 int dim3 = getTop() - zCombobox.getSelectedIndex();
235 getPcaModel().updateRcView(dim1, dim2, dim3);
236 getRotatableCanvas().resetView();
240 * Sets the selected checkbox item index for PCA dimension (1, 2, 3...) for
241 * the given axis (X/Y/Z)
246 public void setSelectedDimensionIndex(int index, Axis axis)
251 xCombobox.setSelectedIndex(index);
254 yCombobox.setSelectedIndex(index);
257 zCombobox.setSelectedIndex(index);
264 protected void outputValues_actionPerformed()
266 CutAndPasteTransfer cap = new CutAndPasteTransfer();
269 cap.setText(getPcaModel().getDetails());
270 Desktop.addInternalFrame(cap,
271 MessageManager.getString("label.pca_details"), 500, 500);
272 } catch (OutOfMemoryError oom)
274 new OOMWarning("opening PCA details", oom);
280 protected void showLabels_actionPerformed()
282 getRotatableCanvas().showLabels(showLabels.getState());
286 protected void print_actionPerformed()
288 PCAPrinter printer = new PCAPrinter();
293 * If available, shows the data which formed the inputs for the PCA as a new
297 public void originalSeqData_actionPerformed()
299 // JAL-2647 disabled after load from project (until save to project done)
300 if (getPcaModel().getInputData() == null)
303 "Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
306 // decide if av alignment is sufficiently different to original data to
307 // warrant a new window to be created
308 // create new alignment window with hidden regions (unhiding hidden regions
309 // yields unaligned seqs)
310 // or create a selection box around columns in alignment view
311 // test Alignment(SeqCigar[])
315 // we try to get the associated view's gap character
316 // but this may fail if the view was closed...
317 gc = av.getGapCharacter();
318 } catch (Exception ex)
322 Object[] alAndColsel = getPcaModel().getInputData()
323 .getAlignmentAndHiddenColumns(gc);
325 if (alAndColsel != null && alAndColsel[0] != null)
327 // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
329 AlignmentI al = new Alignment((SequenceI[]) alAndColsel[0]);
330 AlignmentI dataset = (av != null && av.getAlignment() != null)
331 ? av.getAlignment().getDataset()
335 al.setDataset(dataset);
341 AlignFrame af = new AlignFrame(al, (HiddenColumns) alAndColsel[1],
342 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
344 // >>>This is a fix for the moment, until a better solution is
346 // af.getFeatureRenderer().transferSettings(alignFrame.getFeatureRenderer());
348 // af.addSortByOrderMenuItem(ServiceName + " Ordering",
351 Desktop.addInternalFrame(af, MessageManager.formatMessage(
352 "label.original_data_for_params", new String[]
353 { this.title }), AlignFrame.DEFAULT_WIDTH,
354 AlignFrame.DEFAULT_HEIGHT);
358 * CutAndPasteTransfer cap = new CutAndPasteTransfer(); for (int i = 0; i <
359 * seqs.length; i++) { cap.appendText(new jalview.util.Format("%-" + 15 +
360 * "s").form( seqs[i].getName())); cap.appendText(" " + seqstrings[i] +
363 * Desktop.addInternalFrame(cap, "Original Data", 400, 400);
367 class PCAPrinter extends Thread implements Printable
372 PrinterJob printJob = PrinterJob.getPrinterJob();
373 PageFormat defaultPage = printJob.defaultPage();
374 PageFormat pf = printJob.pageDialog(defaultPage);
376 if (defaultPage == pf)
384 printJob.setPrintable(this, pf);
386 if (printJob.printDialog())
391 } catch (Exception PrintException)
393 PrintException.printStackTrace();
399 public int print(Graphics pg, PageFormat pf, int pi)
400 throws PrinterException
402 pg.translate((int) pf.getImageableX(), (int) pf.getImageableY());
404 getRotatableCanvas().drawBackground(pg);
405 getRotatableCanvas().drawScene(pg);
406 if (getRotatableCanvas().drawAxes)
408 getRotatableCanvas().drawAxes(pg);
413 return Printable.PAGE_EXISTS;
417 return Printable.NO_SUCH_PAGE;
422 public void makePCAImage(ImageMaker.TYPE type)
424 int width = getRotatableCanvas().getWidth();
425 int height = getRotatableCanvas().getHeight();
426 ImageWriterI writer = new ImageWriterI()
429 public void exportImage(Graphics g) throws Exception
431 RotatableCanvas canvas = getRotatableCanvas();
432 canvas.drawBackground(g);
440 String pca = MessageManager.getString("label.pca");
441 ImageExporter exporter = new ImageExporter(writer, null, type, pca);
444 exporter.doExport(null, this, width, height, pca);
445 } catch (ImageOutputException ioex)
447 Console.error("Unexpected error whilst writing " + type.toString(),
453 protected void viewMenu_menuSelected()
455 buildAssociatedViewMenu();
459 * Builds the menu showing the choice of possible views (for the associated
460 * sequence data) to which the PCA may be linked
462 void buildAssociatedViewMenu()
464 AlignmentPanel[] aps = PaintRefresher
465 .getAssociatedPanels(av.getSequenceSetId());
466 if (aps.length == 1 && getRotatableCanvas().av == aps[0].av)
468 associateViewsMenu.setVisible(false);
472 associateViewsMenu.setVisible(true);
475 .getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
477 viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
480 associateViewsMenu.removeAll();
482 JRadioButtonMenuItem item;
483 ButtonGroup buttonGroup = new ButtonGroup();
484 int iSize = aps.length;
486 for (int i = 0; i < iSize; i++)
488 final AlignmentPanel panel = aps[i];
489 item = new JRadioButtonMenuItem(panel.av.getViewName(),
490 panel.av == getRotatableCanvas().av);
491 buttonGroup.add(item);
492 item.addActionListener(new ActionListener()
495 public void actionPerformed(ActionEvent evt)
497 selectAssociatedView(panel);
501 associateViewsMenu.add(item);
504 final JRadioButtonMenuItem itemf = new JRadioButtonMenuItem(
507 buttonGroup.add(itemf);
509 itemf.setSelected(getRotatableCanvas().isApplyToAllViews());
510 itemf.addActionListener(new ActionListener()
513 public void actionPerformed(ActionEvent evt)
515 getRotatableCanvas().setApplyToAllViews(itemf.isSelected());
518 associateViewsMenu.add(itemf);
526 * jalview.jbgui.GPCAPanel#outputPoints_actionPerformed(java.awt.event.ActionEvent
530 protected void outputPoints_actionPerformed()
532 CutAndPasteTransfer cap = new CutAndPasteTransfer();
535 cap.setText(getPcaModel().getPointsasCsv(false,
536 xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(),
537 zCombobox.getSelectedIndex()));
538 Desktop.addInternalFrame(cap, MessageManager
539 .formatMessage("label.points_for_params", new String[]
540 { this.getTitle() }), 500, 500);
541 } catch (OutOfMemoryError oom)
543 new OOMWarning("exporting PCA points", oom);
552 * jalview.jbgui.GPCAPanel#outputProjPoints_actionPerformed(java.awt.event
556 protected void outputProjPoints_actionPerformed()
558 CutAndPasteTransfer cap = new CutAndPasteTransfer();
561 cap.setText(getPcaModel().getPointsasCsv(true,
562 xCombobox.getSelectedIndex(), yCombobox.getSelectedIndex(),
563 zCombobox.getSelectedIndex()));
564 Desktop.addInternalFrame(cap, MessageManager.formatMessage(
565 "label.transformed_points_for_params", new String[]
566 { this.getTitle() }), 500, 500);
567 } catch (OutOfMemoryError oom)
569 new OOMWarning("exporting transformed PCA points", oom);
577 * @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
580 public void setProgressBar(String message, long id)
582 progressBar.setProgressBar(message, id);
583 // if (progressBars == null)
585 // progressBars = new Hashtable();
586 // progressBarHandlers = new Hashtable();
589 // JPanel progressPanel;
590 // Long lId = Long.valueOf(id);
591 // GridLayout layout = (GridLayout) statusPanel.getLayout();
592 // if (progressBars.get(lId) != null)
594 // progressPanel = (JPanel) progressBars.get(Long.valueOf(id));
595 // statusPanel.remove(progressPanel);
596 // progressBars.remove(lId);
597 // progressPanel = null;
598 // if (message != null)
600 // statusBar.setText(message);
602 // if (progressBarHandlers.contains(lId))
604 // progressBarHandlers.remove(lId);
606 // layout.setRows(layout.getRows() - 1);
610 // progressPanel = new JPanel(new BorderLayout(10, 5));
612 // JProgressBar progressBar = new JProgressBar();
613 // progressBar.setIndeterminate(true);
615 // progressPanel.add(new JLabel(message), BorderLayout.WEST);
616 // progressPanel.add(progressBar, BorderLayout.CENTER);
618 // layout.setRows(layout.getRows() + 1);
619 // statusPanel.add(progressPanel);
621 // progressBars.put(lId, progressPanel);
624 // // setMenusForViewport();
629 public void registerHandler(final long id,
630 final IProgressIndicatorHandler handler)
632 progressBar.registerHandler(id, handler);
633 // if (progressBarHandlers == null ||
634 // !progressBars.contains(Long.valueOf(id)))
637 // Error(MessageManager.getString("error.call_setprogressbar_before_registering_handler"));
639 // progressBarHandlers.put(Long.valueOf(id), handler);
640 // final JPanel progressPanel = (JPanel) progressBars.get(Long.valueOf(id));
641 // if (handler.canCancel())
643 // JButton cancel = new JButton(
644 // MessageManager.getString("action.cancel"));
645 // final IProgressIndicator us = this;
646 // cancel.addActionListener(new ActionListener()
650 // public void actionPerformed(ActionEvent e)
652 // handler.cancelActivity(id);
653 // us.setProgressBar(MessageManager.formatMessage("label.cancelled_params",
654 // new String[]{((JLabel) progressPanel.getComponent(0)).getText()}), id);
657 // progressPanel.add(cancel, BorderLayout.EAST);
663 * @return true if any progress bars are still active
666 public boolean operationInProgress()
668 return progressBar.operationInProgress();
672 protected void resetButton_actionPerformed()
675 setTop(0); // ugly - prevents dimensionChanged events from being processed
676 xCombobox.setSelectedIndex(0);
677 yCombobox.setSelectedIndex(1);
679 zCombobox.setSelectedIndex(2);
683 * Answers true if PCA calculation is in progress, else false
687 public boolean isWorking()
693 * Answers the selected checkbox item index for PCA dimension for the X, Y or
694 * Z axis of the display
699 public int getSelectedDimensionIndex(Axis axis)
704 return xCombobox.getSelectedIndex();
706 return yCombobox.getSelectedIndex();
708 return zCombobox.getSelectedIndex();
712 public void setShowLabels(boolean show)
714 showLabels.setSelected(show);
718 * Sets the input data used to calculate the PCA. This is provided for
719 * 'restore from project', which does not currently support this (AL-2647), so
720 * sets the value to null, and hides the menu option for "Input Data...". J
724 public void setInputData(AlignmentView data)
726 getPcaModel().setInputData(data);
727 originalSeqData.setVisible(data != null);
730 public AlignViewportI getAlignViewport()
735 public PCAModel getPcaModel()
740 public void setPcaModel(PCAModel pcaModel)
742 this.pcaModel = pcaModel;
745 public RotatableCanvas getRotatableCanvas()
750 public void setRotatableCanvas(RotatableCanvas rc)
760 public void setTop(int top)
766 * set the associated view for this PCA.
770 public void selectAssociatedView(AlignmentPanel panel)
772 getRotatableCanvas().setApplyToAllViews(false);
777 getRotatableCanvas().av = panel.av;
778 getRotatableCanvas().ap = panel;
779 PaintRefresher.Register(PCAPanel.this, panel.av.getSequenceSetId());