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.
21 package jalview.appletgui;
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.MessageManager;
31 import jalview.viewmodel.AlignmentViewport;
33 import java.awt.Color;
34 import java.awt.Dimension;
36 import java.awt.Graphics;
37 import java.awt.Image;
38 import java.awt.Panel;
39 import java.awt.event.KeyEvent;
40 import java.awt.event.KeyListener;
41 import java.awt.event.MouseEvent;
42 import java.awt.event.MouseListener;
43 import java.awt.event.MouseMotionListener;
44 import java.util.List;
46 public class RotatableCanvas extends Panel implements MouseListener,
47 MouseMotionListener, KeyListener, RotatableCanvasI
49 private static final int DIMS = 3;
57 // RubberbandRectangle rubberband;
59 boolean drawAxes = true;
73 float[] width = new float[DIMS];
75 float[] max = new float[DIMS];
77 float[] min = new float[DIMS];
85 List<SequencePoint> points;
89 Point[] axisEndPoints;
107 float scalefactor = 1;
109 AlignmentViewport av;
111 boolean showLabels = false;
113 public RotatableCanvas(AlignmentViewport viewport)
116 axisEndPoints = new Point[DIMS];
119 public void showLabels(boolean b)
126 public void setPoints(List<SequencePoint> points, int npoint)
128 this.points = points;
129 this.npoint = npoint;
130 PaintRefresher.Register(this, av.getSequenceSetId());
132 prefsize = getPreferredSize();
133 orig = new Point[npoint];
135 for (int i = 0; i < npoint; i++)
137 SequencePoint sp = points.get(i);
148 // System.out.println("Scale factor = " + scale);
150 addMouseListener(this);
151 addKeyListener(this);
152 // if (getParent() != null) {
153 // getParent().addKeyListener(this);
155 addMouseMotionListener(this);
158 // rubberband = new RubberbandRectangle(this);
159 // rubberband.setActive(true);
160 // rubberband.addListener(this);
164 * public boolean handleSequenceSelectionEvent(SequenceSelectionEvent evt) {
165 * redrawneeded = true; repaint(); return true; }
167 * public void removeNotify() { controller.removeListener(this);
168 * super.removeNotify(); }
172 * Resets axes to the initial state: x-axis to the right, y-axis up, z-axis to
173 * back (so obscured in a 2-D display)
175 public void resetAxes()
177 axisEndPoints[0] = new Point(1f, 0f, 0f);
178 axisEndPoints[1] = new Point(0f, 1f, 0f);
179 axisEndPoints[2] = new Point(0f, 0f, 1f);
183 * Computes and saves the maximum and minimum (x, y, z) positions of any
184 * sequence point, and also the min-max range (width) for each dimension, and
185 * the maximum width for all dimensions
187 public void findWidth()
192 max[0] = Float.MIN_VALUE;
193 max[1] = Float.MIN_VALUE;
194 max[2] = Float.MIN_VALUE;
196 min[0] = Float.MAX_VALUE;
197 min[1] = Float.MAX_VALUE;
198 min[2] = Float.MAX_VALUE;
200 for (SequencePoint sp : points)
202 max[0] = Math.max(max[0], sp.coord.x);
203 max[1] = Math.max(max[1], sp.coord.y);
204 max[2] = Math.max(max[2], sp.coord.z);
205 min[0] = Math.min(min[0], sp.coord.x);
206 min[1] = Math.min(min[1], sp.coord.y);
207 min[2] = Math.min(min[2], sp.coord.z);
210 width[0] = Math.abs(max[0] - min[0]);
211 width[1] = Math.abs(max[1] - min[1]);
212 width[2] = Math.abs(max[2] - min[2]);
214 maxwidth = Math.max(width[0], Math.max(width[1], width[2]));
217 public float findScale()
220 if (getSize().width != 0)
223 height = getSize().height;
228 height = prefsize.height;
240 return dim * scalefactor / (2 * maxwidth);
244 * Computes and saves the position of the centre of the view
246 public void findCentre()
250 float x = (max[0] + min[0]) / 2;
251 float y = (max[1] + min[1]) / 2;
252 float z = (max[2] + min[2]) / 2;
254 centre = new Point(x, y, z);
258 public Dimension getPreferredSize()
260 if (prefsize != null)
266 return new Dimension(400, 400);
271 public Dimension getMinimumSize()
273 return getPreferredSize();
277 public void update(Graphics g)
283 public void paint(Graphics g)
287 g.setFont(new Font("Verdana", Font.PLAIN, 18));
289 MessageManager.getString("label.calculating_pca") + "....",
290 20, getSize().height / 2);
295 // Only create the image at the beginning -
296 if ((img == null) || (prefsize.width != getSize().width)
297 || (prefsize.height != getSize().height))
299 prefsize.width = getSize().width;
300 prefsize.height = getSize().height;
304 // System.out.println("New scale = " + scale);
305 img = createImage(getSize().width, getSize().height);
306 ig = img.getGraphics();
310 drawBackground(ig, Color.black);
319 ig.setColor(Color.red);
320 ig.drawString(tooltip, toolx, tooly);
323 g.drawImage(img, 0, 0, this);
327 public void drawAxes(Graphics g)
330 g.setColor(Color.yellow);
331 for (int i = 0; i < 3; i++)
333 g.drawLine(getSize().width / 2, getSize().height / 2,
334 (int) (axisEndPoints[i].x * scale * max[0] + getSize().width / 2),
335 (int) (axisEndPoints[i].y * scale * max[1] + getSize().height / 2));
339 public void drawBackground(Graphics g, Color col)
342 g.fillRect(0, 0, prefsize.width, prefsize.height);
345 public void drawScene(Graphics g)
347 for (int i = 0; i < npoint; i++)
349 SequencePoint sp = points.get(i);
350 SequenceI sequence = sp.getSequence();
351 Color sequenceColour = av.getSequenceColour(sequence);
353 sequenceColour == Color.black ? Color.white : sequenceColour);
354 if (av.getSelectionGroup() != null)
356 if (av.getSelectionGroup().getSequences(null)
359 g.setColor(Color.gray);
363 if (sp.coord.z < centre.z)
365 g.setColor(g.getColor().darker());
368 int halfwidth = getSize().width / 2;
369 int halfheight = getSize().height / 2;
370 int x = (int) ((sp.coord.x - centre.x) * scale) + halfwidth;
371 int y = (int) ((sp.coord.y - centre.y) * scale) + halfheight;
372 g.fillRect(x - 3, y - 3, 6, 6);
376 g.setColor(Color.red);
377 g.drawString(sequence.getName(), x - 3, y - 4);
383 public void keyTyped(KeyEvent evt)
388 public void keyReleased(KeyEvent evt)
393 public void keyPressed(KeyEvent evt)
395 boolean shiftDown = evt.isShiftDown();
396 int keyCode = evt.getKeyCode();
397 if (keyCode == KeyEvent.VK_UP)
408 else if (keyCode == KeyEvent.VK_DOWN)
419 else if (shiftDown && keyCode == KeyEvent.VK_LEFT)
423 else if (shiftDown && keyCode == KeyEvent.VK_RIGHT)
427 else if (evt.getKeyChar() == 's')
429 System.err.println("DEBUG: Rectangle selection"); // log.debug
430 if (rectx2 != -1 && recty2 != -1)
432 rectSelect(rectx1, recty1, rectx2, recty2);
440 public void mouseClicked(MouseEvent evt)
445 public void mouseEntered(MouseEvent evt)
450 public void mouseExited(MouseEvent evt)
455 public void mouseReleased(MouseEvent evt)
460 public void mousePressed(MouseEvent evt)
477 SequenceI found = findSequenceAtPoint(x, y);
481 // TODO: applet PCA is not associatable with multi-panels - only parent
483 if (av.getSelectionGroup() != null)
485 av.getSelectionGroup().addOrRemove(found, true);
486 av.getSelectionGroup().setEndRes(av.getAlignment().getWidth() - 1);
490 av.setSelectionGroup(new SequenceGroup());
491 av.getSelectionGroup().addOrRemove(found, true);
492 av.getSelectionGroup().setEndRes(av.getAlignment().getWidth() - 1);
495 PaintRefresher.Refresh(this, av.getSequenceSetId());
502 public void mouseMoved(MouseEvent evt)
504 SequenceI found = findSequenceAtPoint(evt.getX(), evt.getY());
511 tooltip = found.getName();
519 public void mouseDragged(MouseEvent evt)
521 int xPos = evt.getX();
522 int yPos = evt.getY();
524 if (xPos == mouseX && yPos == mouseY)
529 int xDelta = xPos - mouseX;
530 int yDelta = yPos - mouseY;
532 rotate(xDelta, yDelta);
536 public void rectSelect(int x1, int y1, int x2, int y2)
538 // boolean changedSel = false;
539 for (int i = 0; i < npoint; i++)
541 SequencePoint sp = points.get(i);
542 int tmp1 = (int) ((sp.coord.x - centre.x) * scale
543 + getSize().width / 2.0);
544 int tmp2 = (int) ((sp.coord.y - centre.y) * scale
545 + getSize().height / 2.0);
547 SequenceI sequence = sp.getSequence();
548 if (tmp1 > x1 && tmp1 < x2 && tmp2 > y1 && tmp2 < y2)
552 if (!av.getSelectionGroup().getSequences(null)
555 av.getSelectionGroup().addSequence(sequence, true);
563 * Answers the first sequence found whose point on the display is within 2
564 * pixels of the given coordinates, or null if none is found
571 public SequenceI findSequenceAtPoint(int x, int y)
573 int halfwidth = getSize().width / 2;
574 int halfheight = getSize().height / 2;
578 for (int i = 0; i < npoint; i++)
581 SequencePoint sp = points.get(i);
582 int px = (int) ((sp.coord.x - centre.x) * scale)
584 int py = (int) ((sp.coord.y - centre.y) * scale)
587 if (Math.abs(px - x) < 3 && Math.abs(py - y) < 3)
596 return points.get(found).getSequence();
605 * Resets the view to initial state (no rotation)
607 public void resetView()
614 public void zoom(float factor)
618 scalefactor *= factor;
624 public void rotate(float x, float y)
626 if (x == 0f && y == 0f)
632 * get the identity transformation...
634 RotatableMatrix rotmat = new RotatableMatrix();
637 * rotate around the X axis for change in Y
638 * (mouse movement up/down); note we are equating a
639 * number of pixels with degrees of rotation here!
643 rotmat.rotate(y, Axis.X);
647 * rotate around the Y axis for change in X
648 * (mouse movement left/right)
652 rotmat.rotate(x, Axis.Y);
656 * apply the composite transformation to sequence points
658 for (int i = 0; i < npoint; i++)
660 SequencePoint sp = points.get(i);
661 sp.translate(-centre.x, -centre.y, -centre.z);
663 // Now apply the rotation matrix
664 sp.coord = rotmat.vectorMultiply(sp.coord);
666 // Now translate back again
667 sp.translate(centre.x, centre.y, centre.z);
671 * rotate the x/y/z axis positions
673 for (int i = 0; i < DIMS; i++)
675 axisEndPoints[i] = rotmat.vectorMultiply(axisEndPoints[i]);