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 jalview.api.RotatableCanvasI;
24 import jalview.datamodel.Point;
25 import jalview.datamodel.SequenceGroup;
26 import jalview.datamodel.SequenceI;
27 import jalview.datamodel.SequencePoint;
28 import jalview.math.RotatableMatrix;
29 import jalview.math.RotatableMatrix.Axis;
30 import jalview.util.ColorUtils;
31 import jalview.util.MessageManager;
32 import jalview.viewmodel.AlignmentViewport;
34 import java.awt.Color;
35 import java.awt.Dimension;
37 import java.awt.Graphics;
38 import java.awt.Graphics2D;
39 import java.awt.Image;
40 import java.awt.RenderingHints;
41 import java.awt.event.InputEvent;
42 import java.awt.event.KeyEvent;
43 import java.awt.event.KeyListener;
44 import java.awt.event.MouseEvent;
45 import java.awt.event.MouseListener;
46 import java.awt.event.MouseMotionListener;
47 import java.awt.event.MouseWheelEvent;
48 import java.awt.event.MouseWheelListener;
49 import java.util.Vector;
51 import javax.swing.JPanel;
52 import javax.swing.ToolTipManager;
55 * Models a Panel on which a set of points, and optionally x/y/z axes, can be
56 * drawn, and rotated or zoomed with the mouse
58 public class RotatableCanvas extends JPanel implements MouseListener,
59 MouseMotionListener, KeyListener, RotatableCanvasI
61 private static final int DIMS = 3;
63 // RubberbandRectangle rubberband;
64 boolean drawAxes = true;
78 float[] width = new float[DIMS];
80 float[] max = new float[DIMS];
82 float[] min = new float[DIMS];
90 Vector<SequencePoint> points;
94 Point[] axisEndPoints;
112 float scalefactor = 1;
114 AlignmentViewport av;
118 boolean showLabels = false;
120 Color bgColour = Color.black;
122 boolean applyToAllViews = false;
124 boolean first = true;
131 public RotatableCanvas(AlignmentPanel panel)
135 axisEndPoints = new Point[DIMS];
137 addMouseWheelListener(new MouseWheelListener()
140 public void mouseWheelMoved(MouseWheelEvent e)
142 double wheelRotation = e.getPreciseWheelRotation();
143 if (wheelRotation > 0)
148 scale = (float) (scale * 1.1);
151 else if (wheelRotation < 0)
156 scale = (float) (scale * 0.9);
165 * Refreshes the display with labels shown (or not)
169 public void showLabels(boolean show)
176 public void setPoints(Vector<SequencePoint> points, int npoint)
178 this.points = points;
179 this.npoint = npoint;
182 ToolTipManager.sharedInstance().registerComponent(this);
183 ToolTipManager.sharedInstance().setInitialDelay(0);
184 ToolTipManager.sharedInstance().setDismissDelay(10000);
186 prefsize = getPreferredSize();
187 orig = new Point[npoint];
189 for (int i = 0; i < npoint; i++)
191 SequencePoint sp = points.elementAt(i);
203 addMouseListener(this);
204 addMouseMotionListener(this);
210 * Resets axes to the initial state: x-axis to the right, y-axis up, z-axis to
211 * back (so obscured in a 2-D display)
213 protected void resetAxes()
215 axisEndPoints[0] = new Point(1f, 0f, 0f);
216 axisEndPoints[1] = new Point(0f, 1f, 0f);
217 axisEndPoints[2] = new Point(0f, 0f, 1f);
221 * Computes and saves the maximum and minimum (x, y, z) positions of any
222 * sequence point, and also the min-max range (width) for each dimension, and
223 * the maximum width for all dimensions
225 protected void findWidth()
227 max = new float[DIMS];
228 min = new float[DIMS];
230 max[0] = Float.MIN_VALUE;
231 max[1] = Float.MIN_VALUE;
232 max[2] = Float.MIN_VALUE;
234 min[0] = Float.MAX_VALUE;
235 min[1] = Float.MAX_VALUE;
236 min[2] = Float.MAX_VALUE;
238 for (SequencePoint sp : points)
240 max[0] = Math.max(max[0], sp.coord.x);
241 max[1] = Math.max(max[1], sp.coord.y);
242 max[2] = Math.max(max[2], sp.coord.z);
243 min[0] = Math.min(min[0], sp.coord.x);
244 min[1] = Math.min(min[1], sp.coord.y);
245 min[2] = Math.min(min[2], sp.coord.z);
248 width[0] = Math.abs(max[0] - min[0]);
249 width[1] = Math.abs(max[1] - min[1]);
250 width[2] = Math.abs(max[2] - min[2]);
252 maxwidth = Math.max(width[0], Math.max(width[1], width[2]));
258 * @return DOCUMENT ME!
260 protected float findScale()
269 height = getHeight();
274 height = prefsize.height;
286 return (dim * scalefactor) / (2 * maxwidth);
290 * Computes and saves the position of the centre of the view
292 protected void findCentre()
296 float x = (max[0] + min[0]) / 2;
297 float y = (max[1] + min[1]) / 2;
298 float z = (max[2] + min[2]) / 2;
300 centre = new Point(x, y, z);
306 * @return DOCUMENT ME!
309 public Dimension getPreferredSize()
311 if (prefsize != null)
317 return new Dimension(400, 400);
324 * @return DOCUMENT ME!
327 public Dimension getMinimumSize()
329 return getPreferredSize();
339 public void paintComponent(Graphics g1)
342 Graphics2D g = (Graphics2D) g1;
344 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
345 RenderingHints.VALUE_ANTIALIAS_ON);
348 g.setFont(new Font("Verdana", Font.PLAIN, 18));
350 MessageManager.getString("label.calculating_pca") + "....",
351 20, getHeight() / 2);
355 // Only create the image at the beginning -
356 if ((img == null) || (prefsize.width != getWidth())
357 || (prefsize.height != getHeight()))
359 prefsize.width = getWidth();
360 prefsize.height = getHeight();
364 // System.out.println("New scale = " + scale);
365 img = createImage(getWidth(), getHeight());
366 ig = img.getGraphics();
369 drawBackground(ig, bgColour);
377 g.drawImage(img, 0, 0, this);
382 * Resets the view to initial state (no rotation)
384 public void resetView()
391 * Draws lines for the x, y, z axes
395 public void drawAxes(Graphics g)
398 g.setColor(Color.yellow);
400 for (int i = 0; i < DIMS; i++)
402 g.drawLine(getWidth() / 2, getHeight() / 2,
403 (int) ((axisEndPoints[i].x * scale * max[0]) + (getWidth() / 2)),
404 (int) ((axisEndPoints[i].y * scale * max[1]) + (getHeight() / 2)));
409 * Fills the background with the specified colour
414 public void drawBackground(Graphics g, Color col)
417 g.fillRect(0, 0, prefsize.width, prefsize.height);
421 * Draws points (6x6 squares) for the sequences of the PCA, and labels
422 * (sequence names) if configured to do so. The sequence points colours are
423 * taken from the sequence ids in the alignment (converting black to white).
424 * Sequences 'at the back' (z-coordinate is negative) are shaded slightly
425 * darker to help give a 3-D sensation.
429 public void drawScene(Graphics g1)
431 Graphics2D g = (Graphics2D) g1;
433 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
434 RenderingHints.VALUE_ANTIALIAS_ON);
436 for (int i = 0; i < npoint; i++)
439 * sequence point colour as sequence id, but
440 * gray if sequence is currently selected
442 SequencePoint sp = points.elementAt(i);
443 Color sequenceColour = getSequencePointColour(sp);
444 g.setColor(sequenceColour);
446 int halfwidth = getWidth() / 2;
447 int halfheight = getHeight() / 2;
448 int x = (int) ((sp.coord.x - centre.x) * scale) + halfwidth;
449 int y = (int) ((sp.coord.y - centre.y) * scale) + halfheight;
450 g.fillRect(x - 3, y - 3, 6, 6);
454 g.setColor(Color.red);
455 g.drawString(sp.getSequence().getName(), x - 3, y - 4);
459 // //Now the rectangle
460 // if (rectx2 != -1 && recty2 != -1) {
461 // g.setColor(Color.white);
463 // g.drawRect(rectx1,recty1,rectx2-rectx1,recty2-recty1);
468 * Determines the colour to use when drawing a sequence point. The colour is
469 * taken from the sequence id, with black converted to white, and then
470 * graduated from darker (at the back) to brighter (at the front) based on the
471 * z-axis coordinate of the point.
476 protected Color getSequencePointColour(SequencePoint sp)
478 SequenceI sequence = sp.getSequence();
479 Color sequenceColour = av.getSequenceColour(sequence);
480 if (sequenceColour == Color.black)
482 sequenceColour = Color.white;
484 if (av.getSelectionGroup() != null)
486 if (av.getSelectionGroup().getSequences(null).contains(sequence))
488 sequenceColour = Color.gray;
493 * graduate from front (brighter) to back (darker)
495 sequenceColour = ColorUtils.getGraduatedColour(sp.coord.z, min[2],
496 max[2], sequenceColour);
498 return sequenceColour;
502 public void keyTyped(KeyEvent evt)
507 public void keyReleased(KeyEvent evt)
512 * Responds to up or down arrow key by zooming in or out, respectively
517 public void keyPressed(KeyEvent evt)
519 if (evt.getKeyCode() == KeyEvent.VK_UP)
521 scalefactor = (float) (scalefactor * 1.1);
524 else if (evt.getKeyCode() == KeyEvent.VK_DOWN)
526 scalefactor = (float) (scalefactor * 0.9);
529 else if (evt.getKeyChar() == 's')
531 // Cache.log.warn("DEBUG: Rectangle selection");
532 // todo not yet enabled as rectx2, recty2 are always -1
533 // need to set them in mouseDragged
534 if ((rectx2 != -1) && (recty2 != -1))
536 rectSelect(rectx1, recty1, rectx2, recty2);
544 public void mouseClicked(MouseEvent evt)
549 public void mouseEntered(MouseEvent evt)
554 public void mouseExited(MouseEvent evt)
559 public void mouseReleased(MouseEvent evt)
564 * If the mouse press is at (within 2 pixels of) a sequence point, toggles
565 * (adds or removes) the corresponding sequence as a member of the viewport
566 * selection group. This supports configuring a group in the alignment by
567 * clicking on points in the PCA display.
570 public void mousePressed(MouseEvent evt)
587 SequenceI found = findSequenceAtPoint(x, y);
591 AlignmentPanel[] aps = getAssociatedPanels();
593 for (int a = 0; a < aps.length; a++)
595 if (aps[a].av.getSelectionGroup() != null)
597 aps[a].av.getSelectionGroup().addOrRemove(found, true);
601 aps[a].av.setSelectionGroup(new SequenceGroup());
602 aps[a].av.getSelectionGroup().addOrRemove(found, true);
603 aps[a].av.getSelectionGroup()
604 .setEndRes(aps[a].av.getAlignment().getWidth() - 1);
607 PaintRefresher.Refresh(this, av.getSequenceSetId());
608 // canonical selection is sent to other listeners
616 * Sets the tooltip to the name of the sequence within 2 pixels of the mouse
617 * position, or clears the tooltip if none found
620 public void mouseMoved(MouseEvent evt)
622 SequenceI found = findSequenceAtPoint(evt.getX(), evt.getY());
624 this.setToolTipText(found == null ? null : found.getName());
628 * Action handler for a mouse drag. Rotates the display around the X axis (for
629 * up/down mouse movement) and/or the Y axis (for left/right mouse movement).
634 public void mouseDragged(MouseEvent evt)
636 int xPos = evt.getX();
637 int yPos = evt.getY();
639 if (xPos == mouseX && yPos == mouseY)
644 // Check if this is a rectangle drawing drag
645 if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0)
647 // rectx2 = evt.getX();
648 // recty2 = evt.getY();
653 * get the identity transformation...
655 RotatableMatrix rotmat = new RotatableMatrix();
658 * rotate around the X axis for change in Y
659 * (mouse movement up/down); note we are equating a
660 * number of pixels with degrees of rotation here!
664 rotmat.rotate(yPos - mouseY, Axis.X);
668 * rotate around the Y axis for change in X
669 * (mouse movement left/right)
673 rotmat.rotate(xPos - mouseX, Axis.Y);
677 * apply the composite transformation to sequence points
679 for (int i = 0; i < npoint; i++)
681 SequencePoint sp = points.elementAt(i);
682 sp.translateBack(centre);
684 // Now apply the rotation matrix
685 sp.coord = rotmat.vectorMultiply(sp.coord);
687 // Now translate back again
688 sp.translate(centre);
692 * rotate the x/y/z axis positions
694 for (int i = 0; i < DIMS; i++)
696 axisEndPoints[i] = rotmat.vectorMultiply(axisEndPoints[i]);
702 paint(this.getGraphics());
707 * Adds any sequences whose displayed points are within the given rectangle to
708 * the viewport's current selection. Intended for key 's' after dragging to
709 * select a region of the PCA.
716 protected void rectSelect(int x1, int y1, int x2, int y2)
718 for (int i = 0; i < npoint; i++)
720 SequencePoint sp = points.elementAt(i);
721 int tmp1 = (int) (((sp.coord.x - centre.x) * scale)
722 + (getWidth() / 2.0));
723 int tmp2 = (int) (((sp.coord.y - centre.y) * scale)
724 + (getHeight() / 2.0));
726 if ((tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2))
730 SequenceI sequence = sp.getSequence();
731 if (!av.getSelectionGroup().getSequences(null)
734 av.getSelectionGroup().addSequence(sequence, true);
742 * Answers the first sequence found whose point on the display is within 2
743 * pixels of the given coordinates, or null if none is found
750 protected SequenceI findSequenceAtPoint(int x, int y)
752 int halfwidth = getWidth() / 2;
753 int halfheight = getHeight() / 2;
757 for (int i = 0; i < npoint; i++)
759 SequencePoint sp = points.elementAt(i);
760 int px = (int) ((sp.coord.x - centre.x) * scale)
762 int py = (int) ((sp.coord.y - centre.y) * scale)
765 if ((Math.abs(px - x) < 3) && (Math.abs(py - y) < 3))
774 return points.elementAt(found).getSequence();
783 * Answers the panel the PCA is associated with (all panels for this alignment
784 * if 'associate with all panels' is selected).
788 AlignmentPanel[] getAssociatedPanels()
792 return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
796 return new AlignmentPanel[] { ap };