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.bin.Cache;
25 import jalview.datamodel.SequenceGroup;
26 import jalview.datamodel.SequenceI;
27 import jalview.datamodel.SequencePoint;
28 import jalview.math.RotatableMatrix;
29 import jalview.util.MessageManager;
30 import jalview.viewmodel.AlignmentViewport;
32 import java.awt.Color;
33 import java.awt.Dimension;
35 import java.awt.Graphics;
36 import java.awt.Graphics2D;
37 import java.awt.Image;
38 import java.awt.RenderingHints;
39 import java.awt.event.InputEvent;
40 import java.awt.event.KeyEvent;
41 import java.awt.event.KeyListener;
42 import java.awt.event.MouseEvent;
43 import java.awt.event.MouseListener;
44 import java.awt.event.MouseMotionListener;
45 import java.awt.event.MouseWheelEvent;
46 import java.awt.event.MouseWheelListener;
47 import java.util.Vector;
49 import javax.swing.JPanel;
50 import javax.swing.ToolTipManager;
58 public class RotatableCanvas extends JPanel implements MouseListener,
59 MouseMotionListener, KeyListener, RotatableCanvasI
61 RotatableMatrix idmat = new RotatableMatrix(3, 3);
63 RotatableMatrix objmat = new RotatableMatrix(3, 3);
65 RotatableMatrix rotmat = new RotatableMatrix(3, 3);
67 // RubberbandRectangle rubberband;
68 boolean drawAxes = true;
84 float[] centre = new float[3];
86 float[] width = new float[3];
88 float[] max = new float[3];
90 float[] min = new float[3];
98 Vector<SequencePoint> points;
120 float scalefactor = 1;
122 AlignmentViewport av;
126 boolean showLabels = false;
128 Color bgColour = Color.black;
130 boolean applyToAllViews = false;
132 boolean first = true;
134 public RotatableCanvas(AlignmentPanel ap)
139 addMouseWheelListener(new MouseWheelListener()
142 public void mouseWheelMoved(MouseWheelEvent e)
144 double wheelRotation = e.getPreciseWheelRotation();
145 if (wheelRotation > 0)
150 scale = (float) (scale * 1.1);
153 else if (wheelRotation < 0)
158 scale = (float) (scale * 0.9);
166 public void showLabels(boolean b)
173 public void setPoints(Vector<SequencePoint> points, int npoint)
175 this.points = points;
176 this.npoint = npoint;
179 ToolTipManager.sharedInstance().registerComponent(this);
180 ToolTipManager.sharedInstance().setInitialDelay(0);
181 ToolTipManager.sharedInstance().setDismissDelay(10000);
183 prefsize = getPreferredSize();
184 orig = new float[npoint][3];
186 for (int i = 0; i < npoint; i++)
188 SequencePoint sp = points.elementAt(i);
190 for (int j = 0; j < 3; j++)
192 orig[i][j] = sp.coord[j];
196 // Initialize the matrices to identity
197 for (int i = 0; i < 3; i++)
199 for (int j = 0; j < 3; j++)
203 idmat.addElement(i, j, 0);
204 objmat.addElement(i, j, 0);
205 rotmat.addElement(i, j, 0);
209 idmat.addElement(i, j, 0);
210 objmat.addElement(i, j, 0);
211 rotmat.addElement(i, j, 0);
216 axes = new float[3][3];
226 addMouseListener(this);
228 addMouseMotionListener(this);
233 public void initAxes()
235 for (int i = 0; i < 3; i++)
237 for (int j = 0; j < 3; j++)
254 public void findWidth()
259 max[0] = (float) -1e30;
260 max[1] = (float) -1e30;
261 max[2] = (float) -1e30;
263 min[0] = (float) 1e30;
264 min[1] = (float) 1e30;
265 min[2] = (float) 1e30;
267 for (int i = 0; i < 3; i++)
269 for (int j = 0; j < npoint; j++)
271 SequencePoint sp = points.elementAt(j);
273 if (sp.coord[i] >= max[i])
275 max[i] = sp.coord[i];
278 if (sp.coord[i] <= min[i])
280 min[i] = sp.coord[i];
285 // System.out.println("xmax " + max[0] + " min " + min[0]);
286 // System.out.println("ymax " + max[1] + " min " + min[1]);
287 // System.out.println("zmax " + max[2] + " min " + min[2]);
288 width[0] = Math.abs(max[0] - min[0]);
289 width[1] = Math.abs(max[1] - min[1]);
290 width[2] = Math.abs(max[2] - min[2]);
294 if (width[1] > width[0])
299 if (width[2] > width[1])
304 // System.out.println("Maxwidth = " + maxwidth);
310 * @return DOCUMENT ME!
312 public float findScale()
321 height = getHeight();
326 height = prefsize.height;
338 return (dim * scalefactor) / (2 * maxwidth);
344 public void findCentre()
346 // Find centre coordinate
349 centre[0] = (max[0] + min[0]) / 2;
350 centre[1] = (max[1] + min[1]) / 2;
351 centre[2] = (max[2] + min[2]) / 2;
353 // System.out.println("Centre x " + centre[0]);
354 // System.out.println("Centre y " + centre[1]);
355 // System.out.println("Centre z " + centre[2]);
361 * @return DOCUMENT ME!
364 public Dimension getPreferredSize()
366 if (prefsize != null)
372 return new Dimension(400, 400);
379 * @return DOCUMENT ME!
382 public Dimension getMinimumSize()
384 return getPreferredSize();
394 public void paintComponent(Graphics g1)
397 Graphics2D g = (Graphics2D) g1;
399 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
400 RenderingHints.VALUE_ANTIALIAS_ON);
403 g.setFont(new Font("Verdana", Font.PLAIN, 18));
405 MessageManager.getString("label.calculating_pca") + "....",
406 20, getHeight() / 2);
410 // Only create the image at the beginning -
411 if ((img == null) || (prefsize.width != getWidth())
412 || (prefsize.height != getHeight()))
414 prefsize.width = getWidth();
415 prefsize.height = getHeight();
419 // System.out.println("New scale = " + scale);
420 img = createImage(getWidth(), getHeight());
421 ig = img.getGraphics();
424 drawBackground(ig, bgColour);
432 g.drawImage(img, 0, 0, this);
442 public void drawAxes(Graphics g)
445 g.setColor(Color.yellow);
447 for (int i = 0; i < 3; i++)
449 g.drawLine(getWidth() / 2, getHeight() / 2,
450 (int) ((axes[i][0] * scale * max[0]) + (getWidth() / 2)),
451 (int) ((axes[i][1] * scale * max[1]) + (getHeight() / 2)));
463 public void drawBackground(Graphics g, Color col)
466 g.fillRect(0, 0, prefsize.width, prefsize.height);
475 public void drawScene(Graphics g1)
478 Graphics2D g = (Graphics2D) g1;
480 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
481 RenderingHints.VALUE_ANTIALIAS_ON);
483 int halfwidth = getWidth() / 2;
484 int halfheight = getHeight() / 2;
486 for (int i = 0; i < npoint; i++)
488 SequencePoint sp = points.elementAt(i);
489 int x = (int) ((sp.coord[0] - centre[0]) * scale) + halfwidth;
490 int y = (int) ((sp.coord[1] - centre[1]) * scale)
492 float z = sp.coord[1] - centre[2];
494 SequenceI sequence = sp.getSequence();
495 if (av.getSequenceColour(sequence) == Color.black)
497 g.setColor(Color.white);
501 g.setColor(av.getSequenceColour(sequence));
504 if (av.getSelectionGroup() != null)
506 if (av.getSelectionGroup().getSequences(null)
509 g.setColor(Color.gray);
515 g.setColor(g.getColor().darker());
518 g.fillRect(x - 3, y - 3, 6, 6);
521 g.setColor(Color.red);
522 g.drawString(sequence.getName(), x - 3, y - 4);
526 // //Now the rectangle
527 // if (rectx2 != -1 && recty2 != -1) {
528 // g.setColor(Color.white);
530 // g.drawRect(rectx1,recty1,rectx2-rectx1,recty2-recty1);
537 * @return DOCUMENT ME!
539 public Dimension minimumsize()
547 * @return DOCUMENT ME!
549 public Dimension preferredsize()
561 public void keyTyped(KeyEvent evt)
572 public void keyReleased(KeyEvent evt)
583 public void keyPressed(KeyEvent evt)
585 if (evt.getKeyCode() == KeyEvent.VK_UP)
587 scalefactor = (float) (scalefactor * 1.1);
590 else if (evt.getKeyCode() == KeyEvent.VK_DOWN)
592 scalefactor = (float) (scalefactor * 0.9);
595 else if (evt.getKeyChar() == 's')
597 Cache.log.warn("DEBUG: Rectangle selection");
598 // todo not yet enabled as rectx2, recty2 are always -1
599 // need to set them in mouseDragged
600 if ((rectx2 != -1) && (recty2 != -1))
602 rectSelect(rectx1, recty1, rectx2, recty2);
610 public void mouseClicked(MouseEvent evt)
615 public void mouseEntered(MouseEvent evt)
620 public void mouseExited(MouseEvent evt)
625 public void mouseReleased(MouseEvent evt)
630 * If the mouse press is at (within 2 pixels of) a sequence point, toggles
631 * (adds or removes) the corresponding sequence as a member of the viewport
632 * selection group. This supports configuring a group in the alignment by
633 * clicking on points in the PCA display.
636 public void mousePressed(MouseEvent evt)
656 SequenceI found = findSequenceAtPoint(x, y);
660 AlignmentPanel[] aps = getAssociatedPanels();
662 for (int a = 0; a < aps.length; a++)
664 if (aps[a].av.getSelectionGroup() != null)
666 aps[a].av.getSelectionGroup().addOrRemove(found, true);
670 aps[a].av.setSelectionGroup(new SequenceGroup());
671 aps[a].av.getSelectionGroup().addOrRemove(found, true);
672 aps[a].av.getSelectionGroup()
673 .setEndRes(aps[a].av.getAlignment().getWidth() - 1);
676 PaintRefresher.Refresh(this, av.getSequenceSetId());
677 // canonical selection is sent to other listeners
685 * Sets the tooltip to the name of the sequence within 2 pixels of the mouse
686 * position, or clears the tooltip if none found
689 public void mouseMoved(MouseEvent evt)
691 SequenceI found = findSequenceAtPoint(evt.getX(), evt.getY());
693 this.setToolTipText(found == null ? null : found.getName());
703 public void mouseDragged(MouseEvent evt)
708 // Check if this is a rectangle drawing drag
709 if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0)
711 // rectx2 = evt.getX();
712 // recty2 = evt.getY();
716 rotmat.setIdentity();
718 rotmat.rotate(my - omy, 'x');
719 rotmat.rotate(mx - omx, 'y');
721 for (int i = 0; i < npoint; i++)
723 SequencePoint sp = points.elementAt(i);
724 sp.coord[0] -= centre[0];
725 sp.coord[1] -= centre[1];
726 sp.coord[2] -= centre[2];
728 // Now apply the rotation matrix
729 sp.coord = rotmat.vectorMultiply(sp.coord);
731 // Now translate back again
732 sp.coord[0] += centre[0];
733 sp.coord[1] += centre[1];
734 sp.coord[2] += centre[2];
737 for (int i = 0; i < 3; i++)
739 axes[i] = rotmat.vectorMultiply(axes[i]);
745 paint(this.getGraphics());
750 * Adds any sequences whose displayed points are within the given rectangle to
751 * the viewport's current selection. Intended for key 's' after dragging to
752 * select a region of the PCA.
759 public void rectSelect(int x1, int y1, int x2, int y2)
761 for (int i = 0; i < npoint; i++)
763 SequencePoint sp = points.elementAt(i);
764 int tmp1 = (int) (((sp.coord[0] - centre[0]) * scale)
765 + (getWidth() / 2.0));
766 int tmp2 = (int) (((sp.coord[1] - centre[1]) * scale)
767 + (getHeight() / 2.0));
769 if ((tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2))
773 SequenceI sequence = sp.getSequence();
774 if (!av.getSelectionGroup().getSequences(null)
777 av.getSelectionGroup().addSequence(sequence, true);
785 * Answers the first sequence found whose point on the display is within 2
786 * pixels of the given coordinates, or null if none is found
793 public SequenceI findSequenceAtPoint(int x, int y)
795 int halfwidth = getWidth() / 2;
796 int halfheight = getHeight() / 2;
800 for (int i = 0; i < npoint; i++)
802 SequencePoint sp = points.elementAt(i);
803 int px = (int) ((sp.coord[0] - centre[0]) * scale)
805 int py = (int) ((sp.coord[1] - centre[1]) * scale)
808 if ((Math.abs(px - x) < 3) && (Math.abs(py - y) < 3))
816 return points.elementAt(found).getSequence();
824 AlignmentPanel[] getAssociatedPanels()
828 return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
832 return new AlignmentPanel[] { ap };
838 * @return x,y,z positions of point s (index into points) under current
841 public double[] getPointPosition(int s)
843 double[] pts = new double[3];
844 float[] p = points.elementAt(s).coord;