JAL-1767 refactorings to enable faithful restore of PCA from project
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 4 May 2018 14:31:07 +0000 (15:31 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Fri, 4 May 2018 14:31:07 +0000 (15:31 +0100)
src/jalview/analysis/PCA.java
src/jalview/api/RotatableCanvasI.java
src/jalview/appletgui/RotatableCanvas.java
src/jalview/datamodel/Point.java
src/jalview/datamodel/SequencePoint.java
src/jalview/gui/CalculationChooser.java
src/jalview/gui/PCAPanel.java
src/jalview/gui/RotatableCanvas.java
src/jalview/viewmodel/PCAModel.java

index 1cf21fd..b3268df 100755 (executable)
@@ -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;
   }
 }
index a57bcdb..c6eb6de 100644 (file)
@@ -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<SequencePoint> points, int rows);
 
-  void setPoints(Vector<SequencePoint> 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);
 }
index 3000363..2a49d0b 100755 (executable)
@@ -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<SequencePoint> points;
+  List<SequencePoint> points;
 
   Point[] orig;
 
@@ -123,7 +123,7 @@ public class RotatableCanvas extends Panel implements MouseListener,
   }
 
   @Override
-  public void setPoints(Vector<SequencePoint> points, int npoint)
+  public void setPoints(List<SequencePoint> 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]);
+    }
+  }
+
 }
index af6a634..f1de8a1 100644 (file)
@@ -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);
+  }
 }
index 7fcb713..3db7cee 100755 (executable)
 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();
   }
 }
index f0d9521..a41a998 100644 (file)
@@ -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();
+
   }
 
   /**
index f5e33cd..2ecdb82 100644 (file)
@@ -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);
+  }
 }
index 615b403..2053f94 100755 (executable)
@@ -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<String> 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<SequencePoint> points;
-
-  Point[] orig;
+  /*
+   * sequences and their (x, y, z) PCA dimension values
+   */
+  List<SequencePoint> 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<SequencePoint> points, int npoint)
+  public void setPoints(List<SequencePoint> 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<String> 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;
+  }
 }
index 3403887..a4f57be 100644 (file)
@@ -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<SequencePoint> points;
+  private List<SequencePoint> 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<SequencePoint> getSequencePoints()
   {
-    return scoreModel == null ? "" : scoreModel.getName();
+    return points;
   }
 
-  public void setScoreModel(ScoreModelI sm)
+  public void setSequencePoints(List<SequencePoint> 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;
+  }
 }