From: gmungoc Date: Fri, 4 May 2018 14:31:07 +0000 (+0100) Subject: JAL-1767 refactorings to enable faithful restore of PCA from project X-Git-Tag: Release_2_11_1_0~78^2~9^2~8 X-Git-Url: http://source.jalview.org/gitweb/?p=jalview.git;a=commitdiff_plain;h=b04c79d1a830aa7ae7b88b2a5ddd07c9216c826f JAL-1767 refactorings to enable faithful restore of PCA from project --- diff --git a/src/jalview/analysis/PCA.java b/src/jalview/analysis/PCA.java index 1cf21fd..b3268df 100755 --- a/src/jalview/analysis/PCA.java +++ b/src/jalview/analysis/PCA.java @@ -45,7 +45,9 @@ public class PCA implements Runnable /* * outputs */ - private MatrixI symm; + private MatrixI pairwiseScores; + + private MatrixI afterTred; private MatrixI eigenvector; @@ -143,9 +145,9 @@ public class PCA implements Runnable { double out = 0.0; - for (int i = 0; i < symm.width(); i++) + for (int i = 0; i < pairwiseScores.width(); i++) { - out += (symm.getValue(row, i) * eigenvector.getValue(i, n)); + out += (pairwiseScores.getValue(row, i) * eigenvector.getValue(i, n)); } return out / eigenvector.getD()[n]; @@ -160,6 +162,31 @@ public class PCA implements Runnable public String getDetails() { return details; + /* + StringBuilder sb = new StringBuilder(1024); + sb.append("PCA calculation using ").append(scoreModel.getName()) + .append(" sequence similarity matrix\n========\n\n"); + PrintStream ps = wrapOutputBuffer(sb); + + sb.append(" --- OrigT * Orig ---- \n"); + pairwiseScores.print(ps, "%8.2f"); + + sb.append(" ---Tridiag transform matrix ---\n"); + sb.append(" --- D vector ---\n"); + afterTred.printD(ps, "%15.4e"); + ps.println(); + sb.append("--- E vector ---\n"); + afterTred.printE(ps, "%15.4e"); + ps.println(); + + sb.append(" --- New diagonalization matrix ---\n"); + eigenvector.print(ps, "%8.2f"); + sb.append(" --- Eigenvalues ---\n"); + eigenvector.printD(ps, "%15.4e"); + ps.println(); + + return sb.toString(); + */ } /** @@ -183,16 +210,18 @@ public class PCA implements Runnable sb.append(" --- OrigT * Orig ---- \n"); eigenvector.print(ps, "%8.2f"); - symm = eigenvector.copy(); + pairwiseScores = eigenvector.copy(); eigenvector.tred(); + afterTred = eigenvector.copy(); + sb.append(" ---Tridiag transform matrix ---\n"); sb.append(" --- D vector ---\n"); - eigenvector.printD(ps, "%15.4e"); + afterTred.printD(ps, "%15.4e"); ps.println(); sb.append("--- E vector ---\n"); - eigenvector.printE(ps, "%15.4e"); + afterTred.printE(ps, "%15.4e"); ps.println(); // Now produce the diagonalization matrix @@ -250,6 +279,37 @@ public class PCA implements Runnable public int getHeight() { // TODO can any of seqs[] be null? - return seqs.getSequences().length; + return pairwiseScores.height();// seqs.getSequences().length; + } + + /** + * Answers the sequence pairwise similarity scores which were the first step + * of the PCA calculation + * + * @return + */ + public MatrixI getPairwiseScores() + { + return pairwiseScores; + } + + public void setPairwiseScores(MatrixI m) + { + pairwiseScores = m; + } + + public MatrixI getEigenmatrix() + { + return eigenvector; + } + + public void setEigenmatrix(MatrixI m) + { + eigenvector = m; + } + + public void setDetails(String d) + { + details = d; } } diff --git a/src/jalview/api/RotatableCanvasI.java b/src/jalview/api/RotatableCanvasI.java index a57bcdb..c6eb6de 100644 --- a/src/jalview/api/RotatableCanvasI.java +++ b/src/jalview/api/RotatableCanvasI.java @@ -1,6 +1,6 @@ /* - * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) - * Copyright (C) $$Year-Rel$$ The Jalview Authors + * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2b1) + * Copyright (C) 2014 The Jalview Authors * * This file is part of Jalview. * @@ -22,7 +22,7 @@ package jalview.api; import jalview.datamodel.SequencePoint; -import java.util.Vector; +import java.util.List; /** * interface implemented by RotatatableCanvas GUI elements (such as point clouds @@ -33,7 +33,23 @@ import java.util.Vector; */ public interface RotatableCanvasI { + void setPoints(List points, int rows); - void setPoints(Vector points, int rows); + /** + * Zoom the view in (or out) by the given factor, which should be >= 0. A + * factor greater than 1 zooms in (expands the display), a factor less than 1 + * zooms out (shrinks the display). + * + * @param factor + */ + void zoom(float factor); + /** + * Rotates the view by the specified number of degrees about the x and/or y + * axis + * + * @param x + * @param y + */ + void rotate(float x, float y); } diff --git a/src/jalview/appletgui/RotatableCanvas.java b/src/jalview/appletgui/RotatableCanvas.java index 3000363..2a49d0b 100755 --- a/src/jalview/appletgui/RotatableCanvas.java +++ b/src/jalview/appletgui/RotatableCanvas.java @@ -41,7 +41,7 @@ import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; -import java.util.Vector; +import java.util.List; public class RotatableCanvas extends Panel implements MouseListener, MouseMotionListener, KeyListener, RotatableCanvasI @@ -82,7 +82,7 @@ public class RotatableCanvas extends Panel implements MouseListener, int npoint; - Vector points; + List points; Point[] orig; @@ -123,7 +123,7 @@ public class RotatableCanvas extends Panel implements MouseListener, } @Override - public void setPoints(Vector points, int npoint) + public void setPoints(List points, int npoint) { this.points = points; this.npoint = npoint; @@ -134,7 +134,7 @@ public class RotatableCanvas extends Panel implements MouseListener, for (int i = 0; i < npoint; i++) { - SequencePoint sp = points.elementAt(i); + SequencePoint sp = points.get(i); orig[i] = sp.coord; } @@ -346,7 +346,7 @@ public class RotatableCanvas extends Panel implements MouseListener, { for (int i = 0; i < npoint; i++) { - SequencePoint sp = points.elementAt(i); + SequencePoint sp = points.get(i); SequenceI sequence = sp.getSequence(); Color sequenceColour = av.getSequenceColour(sequence); g.setColor( @@ -394,13 +394,11 @@ public class RotatableCanvas extends Panel implements MouseListener, { if (evt.getKeyCode() == KeyEvent.VK_UP) { - scalefactor = (float) (scalefactor * 1.1); - scale = findScale(); + zoom(1.1f); } else if (evt.getKeyCode() == KeyEvent.VK_DOWN) { - scalefactor = (float) (scalefactor * 0.9); - scale = findScale(); + zoom(0.9f); } else if (evt.getKeyChar() == 's') { @@ -499,31 +497,16 @@ public class RotatableCanvas extends Panel implements MouseListener, int xPos = evt.getX(); int yPos = evt.getY(); - RotatableMatrix rotmat = new RotatableMatrix(); - - rotmat.rotate(yPos - mouseY, Axis.X); - rotmat.rotate(xPos - mouseX, Axis.Y); - - for (int i = 0; i < npoint; i++) + if (xPos == mouseX && yPos == mouseY) { - SequencePoint sp = points.elementAt(i); - sp.translateBack(centre); - - // Now apply the rotation matrix - sp.coord = rotmat.vectorMultiply(sp.coord); - - // Now translate back again - sp.translate(centre); + return; } - for (int i = 0; i < 3; i++) - { - axisEndPoints[i] = rotmat.vectorMultiply(axisEndPoints[i]); - } - mouseX = xPos; - mouseY = yPos; + int xDelta = xPos - mouseX; + int yDelta = yPos - mouseY; - paint(this.getGraphics()); + rotate(xDelta, yDelta); + repaint(); } public void rectSelect(int x1, int y1, int x2, int y2) @@ -531,7 +514,7 @@ public class RotatableCanvas extends Panel implements MouseListener, // boolean changedSel = false; for (int i = 0; i < npoint; i++) { - SequencePoint sp = points.elementAt(i); + SequencePoint sp = points.get(i); int tmp1 = (int) ((sp.coord.x - centre.x) * scale + getSize().width / 2.0); int tmp2 = (int) ((sp.coord.y - centre.y) * scale @@ -571,7 +554,7 @@ public class RotatableCanvas extends Panel implements MouseListener, for (int i = 0; i < npoint; i++) { - SequencePoint sp = points.elementAt(i); + SequencePoint sp = points.get(i); int px = (int) ((sp.coord.x - centre.x) * scale) + halfwidth; int py = (int) ((sp.coord.y - centre.y) * scale) @@ -586,7 +569,7 @@ public class RotatableCanvas extends Panel implements MouseListener, if (found != -1) { - return points.elementAt(found).getSequence(); + return points.get(found).getSequence(); } else { @@ -603,4 +586,70 @@ public class RotatableCanvas extends Panel implements MouseListener, resetAxes(); } + @Override + public void zoom(float factor) + { + if (factor > 0f) + { + scalefactor *= factor; + } + scale = findScale(); + } + + @Override + public void rotate(float x, float y) + { + if (x == 0f && y == 0f) + { + return; + } + + /* + * get the identity transformation... + */ + RotatableMatrix rotmat = new RotatableMatrix(); + + /* + * rotate around the X axis for change in Y + * (mouse movement up/down); note we are equating a + * number of pixels with degrees of rotation here! + */ + if (y != 0) + { + rotmat.rotate(y, Axis.X); + } + + /* + * rotate around the Y axis for change in X + * (mouse movement left/right) + */ + if (x != 0) + { + rotmat.rotate(x, Axis.Y); + } + + /* + * apply the composite transformation to sequence points + */ + for (int i = 0; i < npoint; i++) + { + SequencePoint sp = points.get(i); + sp.translate(-centre.x, -centre.y, -centre.z); + + // Now apply the rotation matrix + sp.coord = rotmat.vectorMultiply(sp.coord); + + // Now translate back again + sp.translate(centre.x, centre.y, centre.z); + } + + /* + * rotate the x/y/z axis positions + */ + for (int i = 0; i < DIMS; i++) + { + axisEndPoints[i] = rotmat.vectorMultiply(axisEndPoints[i]); + } + } + } diff --git a/src/jalview/datamodel/Point.java b/src/jalview/datamodel/Point.java index af6a634..f1de8a1 100644 --- a/src/jalview/datamodel/Point.java +++ b/src/jalview/datamodel/Point.java @@ -17,4 +17,13 @@ public final class Point y = yVal; z = zVal; } + + /** + * toString for convenience of inspection in debugging or logging + */ + @Override + public String toString() + { + return String.format("[%f, %f, %f]", x, y, z); + } } diff --git a/src/jalview/datamodel/SequencePoint.java b/src/jalview/datamodel/SequencePoint.java index 7fcb713..3db7cee 100755 --- a/src/jalview/datamodel/SequencePoint.java +++ b/src/jalview/datamodel/SequencePoint.java @@ -21,8 +21,10 @@ package jalview.datamodel; /** - * A bean that models a point with (x, y, z) coordinates and a reference to a - * sequence + * A bean that models a set of (x, y, z) values and a reference to a sequence. + * As used in Principal Component Analysis, the (x, y, z) values are the + * sequence's score for the currently selected first, second and third + * dimensions of the PCA. */ public class SequencePoint { @@ -32,7 +34,7 @@ public class SequencePoint private final SequenceI sequence; /* - * x, y, z position in 3-D space + * x, y, z values */ public Point coord; @@ -67,30 +69,21 @@ public class SequencePoint } /** - * Applies a negative translation of the 'shift' (x, y, z) coordinates + * Applies a translation to the (x, y, z) coordinates * * @param centre */ - public void translateBack(Point shift) + public void translate(float x, float y, float z) { - float x = coord.x - shift.x; - float y = coord.y - shift.y; - float z = coord.z - shift.z; - - coord = new Point(x, y, z); + coord = new Point(coord.x + x, coord.y + y, coord.z + z); } /** - * Applies a positive translation of the 'shift' (x, y, z) coordinates - * - * @param centre + * string representation for ease of inspection in debugging or logging only */ - public void translate(Point shift) + @Override + public String toString() { - float x = coord.x + shift.x; - float y = coord.y + shift.y; - float z = coord.z + shift.z; - - coord = new Point(x, y, z); + return sequence.getName() + " " + coord.toString(); } } diff --git a/src/jalview/gui/CalculationChooser.java b/src/jalview/gui/CalculationChooser.java index f0d9521..a41a998 100644 --- a/src/jalview/gui/CalculationChooser.java +++ b/src/jalview/gui/CalculationChooser.java @@ -584,7 +584,13 @@ public class CalculationChooser extends JPanel JvOptionPane.WARNING_MESSAGE); return; } + + /* + * construct the panel and kick off its calculation thread + */ pcaPanel = new PCAPanel(af.alignPanel, modelName, params); + new Thread(pcaPanel).start(); + } /** diff --git a/src/jalview/gui/PCAPanel.java b/src/jalview/gui/PCAPanel.java index f5e33cd..2ecdb82 100644 --- a/src/jalview/gui/PCAPanel.java +++ b/src/jalview/gui/PCAPanel.java @@ -23,12 +23,14 @@ package jalview.gui; import jalview.analysis.scoremodels.ScoreModels; import jalview.api.analysis.ScoreModelI; import jalview.api.analysis.SimilarityParamsI; +import jalview.bin.Cache; import jalview.datamodel.Alignment; import jalview.datamodel.AlignmentI; import jalview.datamodel.AlignmentView; import jalview.datamodel.HiddenColumns; import jalview.datamodel.SequenceI; import jalview.jbgui.GPCAPanel; +import jalview.math.RotatableMatrix.Axis; import jalview.util.ImageMaker; import jalview.util.MessageManager; import jalview.viewmodel.AlignmentViewport; @@ -53,11 +55,15 @@ import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; /** - * The panel holding the Principal Component Analysis 3-d visualisation + * The panel holding the Principal Component Analysis 3-D visualisation */ public class PCAPanel extends GPCAPanel implements Runnable, IProgressIndicator { + private static final int MIN_WIDTH = 470; + + private static final int MIN_HEIGHT = 250; + RotatableCanvas rc; AlignmentPanel ap; @@ -68,10 +74,6 @@ public class PCAPanel extends GPCAPanel int top = 0; - private static final int MIN_WIDTH = 470; - - private static final int MIN_HEIGHT = 250; - private IProgressIndicator progressBar; private boolean working; @@ -125,11 +127,10 @@ public class PCAPanel extends GPCAPanel rc = new RotatableCanvas(alignPanel); this.getContentPane().add(rc, BorderLayout.CENTER); - /* - * perform calculation in a new Thread - */ - Thread worker = new Thread(this); - worker.start(); + addKeyListener(rc); + validate(); + + this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT)); } /** @@ -174,7 +175,7 @@ public class PCAPanel extends GPCAPanel try { pcaModel.calculate(); - // //////////////// + xCombobox.setSelectedIndex(0); yCombobox.setSelectedIndex(1); zCombobox.setSelectedIndex(2); @@ -196,12 +197,10 @@ public class PCAPanel extends GPCAPanel repaint(); if (getParent() == null) { - addKeyListener(rc); Desktop.addInternalFrame(this, MessageManager.formatMessage("label.calc_title", "PCA", pcaModel.getScoreModelName()), 475, 450); - this.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT)); } working = false; } @@ -223,7 +222,30 @@ public class PCAPanel extends GPCAPanel int dim3 = top - zCombobox.getSelectedIndex(); pcaModel.updateRcView(dim1, dim2, dim3); rc.resetView(); - rc.paint(rc.getGraphics()); + } + + /** + * Sets the selected checkbox item index for PCA dimension (1, 2, 3...) for + * the given axis (X/Y/Z) + * + * @param index + * @param axis + */ + public void setSelectedDimensionIndex(int index, Axis axis) + { + switch (axis) + { + case X: + xCombobox.setSelectedIndex(index); + break; + case Y: + yCombobox.setSelectedIndex(index); + break; + case Z: + zCombobox.setSelectedIndex(index); + break; + default: + } } @Override @@ -262,7 +284,7 @@ public class PCAPanel extends GPCAPanel // make this an abstract function of all jalview analysis windows if (pcaModel.getSeqtrings() == null) { - jalview.bin.Cache.log.info( + Cache.log.info( "Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action."); return; } @@ -679,4 +701,29 @@ public class PCAPanel extends GPCAPanel { return working; } + + /** + * Answers the selected checkbox item index for PCA dimension for the X, Y or + * Z axis of the display + * + * @param axis + * @return + */ + public int getSelectedDimensionIndex(Axis axis) + { + switch (axis) + { + case X: + return xCombobox.getSelectedIndex(); + case Y: + return yCombobox.getSelectedIndex(); + default: + return zCombobox.getSelectedIndex(); + } + } + + public void setShowLabels(boolean show) + { + showLabels.setSelected(show); + } } diff --git a/src/jalview/gui/RotatableCanvas.java b/src/jalview/gui/RotatableCanvas.java index 615b403..2053f94 100755 --- a/src/jalview/gui/RotatableCanvas.java +++ b/src/jalview/gui/RotatableCanvas.java @@ -27,7 +27,6 @@ import jalview.datamodel.SequenceI; import jalview.datamodel.SequencePoint; import jalview.math.RotatableMatrix; import jalview.math.RotatableMatrix.Axis; -import jalview.util.ColorUtils; import jalview.util.MessageManager; import jalview.viewmodel.AlignmentViewport; @@ -46,7 +45,9 @@ import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; -import java.util.Vector; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; import javax.swing.JPanel; import javax.swing.ToolTipManager; @@ -56,72 +57,81 @@ import javax.swing.ToolTipManager; * drawn, and rotated or zoomed with the mouse */ public class RotatableCanvas extends JPanel implements MouseListener, - MouseMotionListener, KeyListener, RotatableCanvasI + MouseMotionListener, KeyListener, RotatableCanvasI, + MouseWheelListener { + private static final float ZOOM_OUT = 0.9f; + + private static final float ZOOM_IN = 1.1f; + + /* + * pixels distance within which tooltip shows sequence name + */ + private static final int NEARBY = 3; + + private static final List AXES = Arrays.asList("x", "y", "z"); + + private static final Color AXIS_COLOUR = Color.yellow; + private static final int DIMS = 3; - // RubberbandRectangle rubberband; boolean drawAxes = true; - int mouseX = 0; + int mouseX; - int mouseY = 0; + int mouseY; Image img; Graphics ig; - Dimension prefsize; - - Point centre; + Dimension prefSize; - float[] width = new float[DIMS]; - - float[] max = new float[DIMS]; - - float[] min = new float[DIMS]; + /* + * the min-max [x, y, z] values of sequence points when the points + * were set on the object, or when the view is reset; + * x and y ranges are not recomputed as points are rotated, as this + * would make scaling (zoom) unstable, but z ranges are (for correct + * graduated colour brightness based on z-coordinate) + */ + float[] seqMin; - float maxwidth; + float[] seqMax; - float scale; + /* + * a scale factor used in drawing; when equal to 1, the points span + * half the available width or height (whichever is less); increase this + * factor to zoom in, decrease it to zoom out + */ + float scaleFactor; int npoint; - Vector points; - - Point[] orig; + /* + * sequences and their (x, y, z) PCA dimension values + */ + List sequencePoints; + /* + * x, y, z axis end points (PCA dimension values) + */ Point[] axisEndPoints; - int startx; - - int starty; - - int lastx; - - int lasty; - - int rectx1; - - int recty1; - - int rectx2; - - int recty2; - - float scalefactor = 1; + // fields for 'select rectangle' (JAL-1124) + // int rectx1; + // int recty1; + // int rectx2; + // int recty2; AlignmentViewport av; AlignmentPanel ap; - boolean showLabels = false; + boolean showLabels; - Color bgColour = Color.black; + Color bgColour; - boolean applyToAllViews = false; - - boolean first = true; + boolean applyToAllViews; /** * Constructor @@ -133,32 +143,16 @@ public class RotatableCanvas extends JPanel implements MouseListener, this.av = panel.av; this.ap = panel; axisEndPoints = new Point[DIMS]; + showLabels = false; + applyToAllViews = false; + bgColour = Color.BLACK; + resetAxes(); - addMouseWheelListener(new MouseWheelListener() - { - @Override - public void mouseWheelMoved(MouseWheelEvent e) - { - double wheelRotation = e.getPreciseWheelRotation(); - if (wheelRotation > 0) - { - /* - * zoom in - */ - scale = (float) (scale * 1.1); - repaint(); - } - else if (wheelRotation < 0) - { - /* - * zoom out - */ - scale = (float) (scale * 0.9); - repaint(); - } - } - }); + ToolTipManager.sharedInstance().registerComponent(this); + addMouseListener(this); + addMouseMotionListener(this); + addMouseWheelListener(this); } /** @@ -173,37 +167,15 @@ public class RotatableCanvas extends JPanel implements MouseListener, } @Override - public void setPoints(Vector points, int npoint) + public void setPoints(List points, int np) { - this.points = points; - this.npoint = npoint; - if (first) - { - ToolTipManager.sharedInstance().registerComponent(this); - ToolTipManager.sharedInstance().setInitialDelay(0); - ToolTipManager.sharedInstance().setDismissDelay(10000); - } - prefsize = getPreferredSize(); - orig = new Point[npoint]; - - for (int i = 0; i < npoint; i++) - { - SequencePoint sp = points.elementAt(i); - orig[i] = sp.coord; - } + this.sequencePoints = points; + this.npoint = np; + prefSize = getPreferredSize(); - resetAxes(); + findWidths(); - findCentre(); - findWidth(); - - scale = findScale(); - if (first) - { - addMouseListener(this); - addMouseMotionListener(this); - } - first = false; + scaleFactor = 1f; } /** @@ -218,24 +190,23 @@ public class RotatableCanvas extends JPanel implements MouseListener, } /** - * Computes and saves the maximum and minimum (x, y, z) positions of any - * sequence point, and also the min-max range (width) for each dimension, and - * the maximum width for all dimensions + * Computes and saves the min-max ranges of x/y/z positions of the sequence + * points */ - protected void findWidth() + protected void findWidths() { - max = new float[DIMS]; - min = new float[DIMS]; - - max[0] = Float.MIN_VALUE; - max[1] = Float.MIN_VALUE; - max[2] = Float.MIN_VALUE; - + float[] max = new float[DIMS]; + float[] min = new float[DIMS]; + + max[0] = -Float.MAX_VALUE; + max[1] = -Float.MAX_VALUE; + max[2] = -Float.MAX_VALUE; + min[0] = Float.MAX_VALUE; min[1] = Float.MAX_VALUE; min[2] = Float.MAX_VALUE; - - for (SequencePoint sp : points) + + for (SequencePoint sp : sequencePoints) { max[0] = Math.max(max[0], sp.coord.x); max[1] = Math.max(max[1], sp.coord.y); @@ -244,73 +215,22 @@ public class RotatableCanvas extends JPanel implements MouseListener, min[1] = Math.min(min[1], sp.coord.y); min[2] = Math.min(min[2], sp.coord.z); } - - width[0] = Math.abs(max[0] - min[0]); - width[1] = Math.abs(max[1] - min[1]); - width[2] = Math.abs(max[2] - min[2]); - - maxwidth = Math.max(width[0], Math.max(width[1], width[2])); + + seqMin = min; + seqMax = max; } /** - * DOCUMENT ME! + * Answers the preferred size if it has been set, else 400 x 400 * - * @return DOCUMENT ME! - */ - protected float findScale() - { - int dim; - int w; - int height; - - if (getWidth() != 0) - { - w = getWidth(); - height = getHeight(); - } - else - { - w = prefsize.width; - height = prefsize.height; - } - - if (w < height) - { - dim = w; - } - else - { - dim = height; - } - - return (dim * scalefactor) / (2 * maxwidth); - } - - /** - * Computes and saves the position of the centre of the view - */ - protected void findCentre() - { - findWidth(); - - float x = (max[0] + min[0]) / 2; - float y = (max[1] + min[1]) / 2; - float z = (max[2] + min[2]) / 2; - - centre = new Point(x, y, z); - } - - /** - * DOCUMENT ME! - * - * @return DOCUMENT ME! + * @return */ @Override public Dimension getPreferredSize() { - if (prefsize != null) + if (prefSize != null) { - return prefsize; + return prefSize; } else { @@ -319,9 +239,10 @@ public class RotatableCanvas extends JPanel implements MouseListener, } /** - * DOCUMENT ME! + * Answers the preferred size * - * @return DOCUMENT ME! + * @return + * @see RotatableCanvas#getPreferredSize() */ @Override public Dimension getMinimumSize() @@ -330,10 +251,9 @@ public class RotatableCanvas extends JPanel implements MouseListener, } /** - * DOCUMENT ME! + * Repaints the panel * * @param g - * DOCUMENT ME! */ @Override public void paintComponent(Graphics g1) @@ -343,7 +263,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - if (points == null) + if (sequencePoints == null) { g.setFont(new Font("Verdana", Font.PLAIN, 18)); g.drawString( @@ -352,16 +272,16 @@ public class RotatableCanvas extends JPanel implements MouseListener, } else { - // Only create the image at the beginning - - if ((img == null) || (prefsize.width != getWidth()) - || (prefsize.height != getHeight())) + /* + * create the image at the beginning or after a resize + */ + boolean resized = prefSize.width != getWidth() + || prefSize.height != getHeight(); + if (img == null || resized) { - prefsize.width = getWidth(); - prefsize.height = getHeight(); - - scale = findScale(); + prefSize.width = getWidth(); + prefSize.height = getHeight(); - // System.out.println("New scale = " + scale); img = createImage(getWidth(), getHeight()); ig = img.getGraphics(); } @@ -379,12 +299,15 @@ public class RotatableCanvas extends JPanel implements MouseListener, } /** - * Resets the view to initial state (no rotation) + * Resets the rotation and choice of axes to the initial state (without change + * of scale factor) */ public void resetView() { img = null; + findWidths(); resetAxes(); + repaint(); } /** @@ -394,14 +317,20 @@ public class RotatableCanvas extends JPanel implements MouseListener, */ public void drawAxes(Graphics g) { + g.setColor(AXIS_COLOUR); - g.setColor(Color.yellow); + int midX = getWidth() / 2; + int midY = getHeight() / 2; + float maxWidth = Math.max(Math.abs(seqMax[0] - seqMin[0]), + Math.abs(seqMax[1] - seqMin[1])); + int pix = Math.min(getWidth(), getHeight()); + float scaleBy = pix * scaleFactor / (2f * maxWidth); for (int i = 0; i < DIMS; i++) { - g.drawLine(getWidth() / 2, getHeight() / 2, - (int) ((axisEndPoints[i].x * scale * max[0]) + (getWidth() / 2)), - (int) ((axisEndPoints[i].y * scale * max[1]) + (getHeight() / 2))); + g.drawLine(midX, midY, + midX + (int) (axisEndPoints[i].x * scaleBy * seqMax[0]), + midY + (int) (axisEndPoints[i].y * scaleBy * seqMax[1])); } } @@ -414,7 +343,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, public void drawBackground(Graphics g, Color col) { g.setColor(col); - g.fillRect(0, 0, prefsize.width, prefsize.height); + g.fillRect(0, 0, prefSize.width, prefSize.height); } /** @@ -432,6 +361,13 @@ public class RotatableCanvas extends JPanel implements MouseListener, g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + int pix = Math.min(getWidth(), getHeight()); + float xWidth = Math.abs(seqMax[0] - seqMin[0]); + float yWidth = Math.abs(seqMax[1] - seqMin[1]); + float maxWidth = Math.max(xWidth, yWidth); + float scaleBy = pix * scaleFactor / (2f * maxWidth); + + float[] centre = getCentre(); for (int i = 0; i < npoint; i++) { @@ -439,14 +375,14 @@ public class RotatableCanvas extends JPanel implements MouseListener, * sequence point colour as sequence id, but * gray if sequence is currently selected */ - SequencePoint sp = points.elementAt(i); + SequencePoint sp = sequencePoints.get(i); Color sequenceColour = getSequencePointColour(sp); g.setColor(sequenceColour); int halfwidth = getWidth() / 2; int halfheight = getHeight() / 2; - int x = (int) ((sp.coord.x - centre.x) * scale) + halfwidth; - int y = (int) ((sp.coord.y - centre.y) * scale) + halfheight; + int x = (int) ((sp.coord.x - centre[0]) * scaleBy) + halfwidth; + int y = (int) ((sp.coord.y - centre[1]) * scaleBy) + halfheight; g.fillRect(x - 3, y - 3, 6, 6); if (showLabels) @@ -455,7 +391,19 @@ public class RotatableCanvas extends JPanel implements MouseListener, g.drawString(sp.getSequence().getName(), x - 3, y - 4); } } - + if (showLabels) + { + g.setColor(AXIS_COLOUR); + int midX = getWidth() / 2; + int midY = getHeight() / 2; + Iterator axes = AXES.iterator(); + for (Point p : axisEndPoints) + { + int x = midX + (int) (p.x * scaleBy * seqMax[0]); + int y = midY + (int) (p.y * scaleBy * seqMax[1]); + g.drawString(axes.next(), x - 3, y - 4); + } + } // //Now the rectangle // if (rectx2 != -1 && recty2 != -1) { // g.setColor(Color.white); @@ -488,12 +436,10 @@ public class RotatableCanvas extends JPanel implements MouseListener, sequenceColour = Color.gray; } } - - /* - * graduate from front (brighter) to back (darker) - */ - sequenceColour = ColorUtils.getGraduatedColour(sp.coord.z, min[2], - max[2], sequenceColour); + if (sp.coord.z < 0f) + { + sequenceColour = sequenceColour.darker(); + } return sequenceColour; } @@ -516,31 +462,40 @@ public class RotatableCanvas extends JPanel implements MouseListener, @Override public void keyPressed(KeyEvent evt) { - if (evt.getKeyCode() == KeyEvent.VK_UP) + int keyCode = evt.getKeyCode(); + + if (keyCode == KeyEvent.VK_UP) { - scalefactor = (float) (scalefactor * 1.1); - scale = findScale(); + zoom(ZOOM_IN); } - else if (evt.getKeyCode() == KeyEvent.VK_DOWN) + else if (keyCode == KeyEvent.VK_DOWN) { - scalefactor = (float) (scalefactor * 0.9); - scale = findScale(); + zoom(ZOOM_OUT); } else if (evt.getKeyChar() == 's') { // Cache.log.warn("DEBUG: Rectangle selection"); // todo not yet enabled as rectx2, recty2 are always -1 - // need to set them in mouseDragged - if ((rectx2 != -1) && (recty2 != -1)) - { - rectSelect(rectx1, recty1, rectx2, recty2); - } + // need to set them in mouseDragged; JAL-1124 + // if ((rectx2 != -1) && (recty2 != -1)) + // { + // rectSelect(rectx1, recty1, rectx2, recty2); + // } } repaint(); } @Override + public void zoom(float factor) + { + if (factor > 0f) + { + scaleFactor *= factor; + } + } + + @Override public void mouseClicked(MouseEvent evt) { } @@ -575,14 +530,10 @@ public class RotatableCanvas extends JPanel implements MouseListener, mouseX = x; mouseY = y; - startx = x; - starty = y; - - rectx1 = x; - recty1 = y; - - rectx2 = -1; - recty2 = -1; + // rectx1 = x; + // recty1 = y; + // rectx2 = -1; + // recty2 = -1; SequenceI found = findSequenceAtPoint(x, y); @@ -641,6 +592,9 @@ public class RotatableCanvas extends JPanel implements MouseListener, return; } + int xDelta = xPos - mouseX; + int yDelta = yPos - mouseY; + // Check if this is a rectangle drawing drag if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0) { @@ -649,61 +603,101 @@ public class RotatableCanvas extends JPanel implements MouseListener, } else { - /* - * get the identity transformation... - */ - RotatableMatrix rotmat = new RotatableMatrix(); + rotate(xDelta, yDelta); - /* - * rotate around the X axis for change in Y - * (mouse movement up/down); note we are equating a - * number of pixels with degrees of rotation here! - */ - if (yPos != mouseY) - { - rotmat.rotate(yPos - mouseY, Axis.X); - } + mouseX = xPos; + mouseY = yPos; - /* - * rotate around the Y axis for change in X - * (mouse movement left/right) - */ - if (xPos != mouseX) - { - rotmat.rotate(xPos - mouseX, Axis.Y); - } + // findWidths(); - /* - * apply the composite transformation to sequence points - */ - for (int i = 0; i < npoint; i++) - { - SequencePoint sp = points.elementAt(i); - sp.translateBack(centre); + repaint(); + } + } - // Now apply the rotation matrix - sp.coord = rotmat.vectorMultiply(sp.coord); + @Override + public void rotate(float x, float y) + { + if (x == 0f && y == 0f) + { + return; + } - // Now translate back again - sp.translate(centre); - } + /* + * get the identity transformation... + */ + RotatableMatrix rotmat = new RotatableMatrix(); - /* - * rotate the x/y/z axis positions - */ - for (int i = 0; i < DIMS; i++) - { - axisEndPoints[i] = rotmat.vectorMultiply(axisEndPoints[i]); - } + /* + * rotate around the X axis for change in Y + * (mouse movement up/down); note we are equating a + * number of pixels with degrees of rotation here! + */ + if (y != 0) + { + rotmat.rotate(y, Axis.X); + } - mouseX = xPos; - mouseY = yPos; + /* + * rotate around the Y axis for change in X + * (mouse movement left/right) + */ + if (x != 0) + { + rotmat.rotate(x, Axis.Y); + } + + /* + * apply the composite transformation to sequence points; + * update z min-max range (affects colour graduation), but not + * x or y min-max (as this would affect axis scaling) + */ + float[] centre = getCentre(); + float zMin = Float.MAX_VALUE; + float zMax = -Float.MAX_VALUE; + + for (int i = 0; i < npoint; i++) + { + SequencePoint sp = sequencePoints.get(i); + sp.translate(-centre[0], -centre[1], -centre[2]); + + // Now apply the rotation matrix + sp.coord = rotmat.vectorMultiply(sp.coord); + + // Now translate back again + sp.translate(centre[0], centre[1], centre[2]); + + zMin = Math.min(zMin, sp.coord.z); + zMax = Math.max(zMax, sp.coord.z); + } + + seqMin[2] = zMin; + seqMax[2] = zMax; - paint(this.getGraphics()); + /* + * rotate the x/y/z axis positions + */ + for (int i = 0; i < DIMS; i++) + { + axisEndPoints[i] = rotmat.vectorMultiply(axisEndPoints[i]); } } /** + * Answers the x/y/z coordinates that are midway between the maximum and + * minimum sequence point values + * + * @return + */ + private float[] getCentre() + { + float xCentre = (seqMin[0] + seqMax[0]) / 2f; + float yCentre = (seqMin[1] + seqMax[1]) / 2f; + float zCentre = (seqMin[2] + seqMax[2]) / 2f; + + return new float[] { xCentre, yCentre, zCentre }; + } + + /** * Adds any sequences whose displayed points are within the given rectangle to * the viewport's current selection. Intended for key 's' after dragging to * select a region of the PCA. @@ -715,12 +709,14 @@ public class RotatableCanvas extends JPanel implements MouseListener, */ protected void rectSelect(int x1, int y1, int x2, int y2) { + float[] centre = getCentre(); + for (int i = 0; i < npoint; i++) { - SequencePoint sp = points.elementAt(i); - int tmp1 = (int) (((sp.coord.x - centre.x) * scale) + SequencePoint sp = sequencePoints.get(i); + int tmp1 = (int) (((sp.coord.x - centre[0]) * scaleFactor) + (getWidth() / 2.0)); - int tmp2 = (int) (((sp.coord.y - centre.y) * scale) + int tmp2 = (int) (((sp.coord.y - centre[1]) * scaleFactor) + (getHeight() / 2.0)); if ((tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2)) @@ -753,16 +749,23 @@ public class RotatableCanvas extends JPanel implements MouseListener, int halfheight = getHeight() / 2; int found = -1; + int pix = Math.min(getWidth(), getHeight()); + float xWidth = Math.abs(seqMax[0] - seqMin[0]); + float yWidth = Math.abs(seqMax[1] - seqMin[1]); + float maxWidth = Math.max(xWidth, yWidth); + float scaleBy = pix * scaleFactor / (2f * maxWidth); + + float[] centre = getCentre(); for (int i = 0; i < npoint; i++) { - SequencePoint sp = points.elementAt(i); - int px = (int) ((sp.coord.x - centre.x) * scale) + SequencePoint sp = sequencePoints.get(i); + int px = (int) ((sp.coord.x - centre[0]) * scaleBy) + halfwidth; - int py = (int) ((sp.coord.y - centre.y) * scale) + int py = (int) ((sp.coord.y - centre[1]) * scaleBy) + halfheight; - if ((Math.abs(px - x) < 3) && (Math.abs(py - y) < 3)) + if ((Math.abs(px - x) < NEARBY) && (Math.abs(py - y) < NEARBY)) { found = i; break; @@ -771,7 +774,7 @@ public class RotatableCanvas extends JPanel implements MouseListener, if (found != -1) { - return points.elementAt(found).getSequence(); + return sequencePoints.get(found).getSequence(); } else { @@ -796,4 +799,65 @@ public class RotatableCanvas extends JPanel implements MouseListener, return new AlignmentPanel[] { ap }; } } + + public Color getBackgroundColour() + { + return bgColour; + } + + /** + * Zooms in or out in response to mouse wheel movement + */ + @Override + public void mouseWheelMoved(MouseWheelEvent e) + { + double wheelRotation = e.getPreciseWheelRotation(); + if (wheelRotation > 0) + { + zoom(ZOOM_IN); + repaint(); + } + else if (wheelRotation < 0) + { + zoom(ZOOM_OUT); + repaint(); + } + } + + /** + * Answers the sequence point minimum [x, y, z] values. Note these are derived + * when sequence points are set, but x and y values are not updated on + * rotation (because this would result in changes to scaling). + * + * @return + */ + public float[] getSeqMin() + { + return seqMin; + } + + /** + * Answers the sequence point maximum [x, y, z] values. Note these are derived + * when sequence points are set, but x and y values are not updated on + * rotation (because this would result in changes to scaling). + * + * @return + */ + public float[] getSeqMax() + { + return seqMax; + } + + /** + * Sets the minimum and maximum [x, y, z] positions for sequence points. For + * use when restoring a saved PCA from state data. + * + * @param min + * @param max + */ + public void setSeqMinMax(float[] min, float[] max) + { + seqMin = min; + seqMax = max; + } } diff --git a/src/jalview/viewmodel/PCAModel.java b/src/jalview/viewmodel/PCAModel.java index 3403887..a4f57be 100644 --- a/src/jalview/viewmodel/PCAModel.java +++ b/src/jalview/viewmodel/PCAModel.java @@ -29,6 +29,7 @@ import jalview.datamodel.Point; import jalview.datamodel.SequenceI; import jalview.datamodel.SequencePoint; +import java.util.List; import java.util.Vector; public class PCAModel @@ -56,7 +57,7 @@ public class PCAModel int top; - private Vector points; + private List points; /** * Constructor given sequence data, score model and score calculation @@ -105,7 +106,7 @@ public class PCAModel for (int i = 0; i < height; i++) { SequencePoint sp = new SequencePoint(seqs[i], scores[i]); - points.addElement(sp); + points.add(sp); } } @@ -125,17 +126,22 @@ public class PCAModel } /** + * Answers the index of the principal dimension of the PCA * - * - * @return index of principle dimension of PCA + * @return */ public int getTop() { return top; } + public void setTop(int t) + { + top = t; + } + /** - * update the 2d coordinates for the list of points to the given dimensions + * Updates the 3D coordinates for the list of points to the given dimensions. * Principal dimension is getTop(). Next greatest eigenvector is getTop()-1. * Note - pca.getComponents starts counting the spectrum from rank-2 to zero, * rather than rank-1, so getComponents(dimN ...) == updateRcView(dimN+1 ..) @@ -151,7 +157,7 @@ public class PCAModel for (int i = 0; i < pca.getHeight(); i++) { - points.elementAt(i).coord = scores[i]; + points.get(i).coord = scores[i]; } } @@ -203,7 +209,7 @@ public class PCAModel } else { - Point p = points.elementAt(s).coord; + Point p = points.get(s).coord; csv.append(",").append(p.x); csv.append(",").append(p.y); csv.append(",").append(p.z); @@ -213,29 +219,48 @@ public class PCAModel return csv.toString(); } + public String getScoreModelName() + { + return scoreModel == null ? "" : scoreModel.getName(); + } + + public void setScoreModel(ScoreModelI sm) + { + this.scoreModel = sm; + } + /** + * Answers the parameters configured for pairwise similarity calculations * - * @return x,y,z positions of point s (index into points) under current - * transform. + * @return */ - public double[] getPointPosition(int s) + public SimilarityParamsI getSimilarityParameters() { - double pts[] = new double[3]; - Point p = points.elementAt(s).coord; - pts[0] = p.x; - pts[1] = p.y; - pts[2] = p.z; - return pts; + return similarityParams; } - public String getScoreModelName() + public List getSequencePoints() { - return scoreModel == null ? "" : scoreModel.getName(); + return points; } - public void setScoreModel(ScoreModelI sm) + public void setSequencePoints(List sp) { - this.scoreModel = sm; + points = sp; } + /** + * Answers the object holding the values of the computed PCA + * + * @return + */ + public PCA getPcaData() + { + return pca; + } + + public void setPCA(PCA data) + { + pca = data; + } }