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 // jalview.bin.Console.outPrintln("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 // jalview.bin.Console.outPrintln("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]
335 + getSize().width / 2),
336 (int) (axisEndPoints[i].y * scale * max[1]
337 + getSize().height / 2));
341 public void drawBackground(Graphics g, Color col)
344 g.fillRect(0, 0, prefsize.width, prefsize.height);
347 public void drawScene(Graphics g)
349 for (int i = 0; i < npoint; i++)
351 SequencePoint sp = points.get(i);
352 SequenceI sequence = sp.getSequence();
353 Color sequenceColour = av.getSequenceColour(sequence);
355 sequenceColour == Color.black ? Color.white : sequenceColour);
356 if (av.getSelectionGroup() != null)
358 if (av.getSelectionGroup().getSequences(null).contains(sequence))
360 g.setColor(Color.gray);
364 if (sp.coord.z < centre.z)
366 g.setColor(g.getColor().darker());
369 int halfwidth = getSize().width / 2;
370 int halfheight = getSize().height / 2;
371 int x = (int) ((sp.coord.x - centre.x) * scale) + halfwidth;
372 int y = (int) ((sp.coord.y - centre.y) * scale) + halfheight;
373 g.fillRect(x - 3, y - 3, 6, 6);
377 g.setColor(Color.red);
378 g.drawString(sequence.getName(), x - 3, y - 4);
384 public void keyTyped(KeyEvent evt)
389 public void keyReleased(KeyEvent evt)
394 public void keyPressed(KeyEvent evt)
396 boolean shiftDown = evt.isShiftDown();
397 int keyCode = evt.getKeyCode();
398 if (keyCode == KeyEvent.VK_UP)
409 else if (keyCode == KeyEvent.VK_DOWN)
420 else if (shiftDown && keyCode == KeyEvent.VK_LEFT)
424 else if (shiftDown && keyCode == KeyEvent.VK_RIGHT)
428 else if (evt.getKeyChar() == 's')
430 jalview.bin.Console.errPrintln("DEBUG: Rectangle selection"); // log.debug
431 if (rectx2 != -1 && recty2 != -1)
433 rectSelect(rectx1, recty1, rectx2, recty2);
441 public void mouseClicked(MouseEvent evt)
446 public void mouseEntered(MouseEvent evt)
451 public void mouseExited(MouseEvent evt)
456 public void mouseReleased(MouseEvent evt)
461 public void mousePressed(MouseEvent evt)
478 SequenceI found = findSequenceAtPoint(x, y);
482 // TODO: applet PCA is not associatable with multi-panels - only parent
484 if (av.getSelectionGroup() != null)
486 av.getSelectionGroup().addOrRemove(found, true);
487 av.getSelectionGroup().setEndRes(av.getAlignment().getWidth() - 1);
491 av.setSelectionGroup(new SequenceGroup());
492 av.getSelectionGroup().addOrRemove(found, true);
493 av.getSelectionGroup().setEndRes(av.getAlignment().getWidth() - 1);
496 PaintRefresher.Refresh(this, av.getSequenceSetId());
503 public void mouseMoved(MouseEvent evt)
505 SequenceI found = findSequenceAtPoint(evt.getX(), evt.getY());
512 tooltip = found.getName();
520 public void mouseDragged(MouseEvent evt)
522 int xPos = evt.getX();
523 int yPos = evt.getY();
525 if (xPos == mouseX && yPos == mouseY)
530 int xDelta = xPos - mouseX;
531 int yDelta = yPos - mouseY;
533 rotate(xDelta, yDelta);
537 public void rectSelect(int x1, int y1, int x2, int y2)
539 // boolean changedSel = false;
540 for (int i = 0; i < npoint; i++)
542 SequencePoint sp = points.get(i);
543 int tmp1 = (int) ((sp.coord.x - centre.x) * scale
544 + getSize().width / 2.0);
545 int tmp2 = (int) ((sp.coord.y - centre.y) * scale
546 + getSize().height / 2.0);
548 SequenceI sequence = sp.getSequence();
549 if (tmp1 > x1 && tmp1 < x2 && tmp2 > y1 && tmp2 < y2)
553 if (!av.getSelectionGroup().getSequences(null).contains(sequence))
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) + halfwidth;
583 int py = (int) ((sp.coord.y - centre.y) * scale) + halfheight;
585 if (Math.abs(px - x) < 3 && Math.abs(py - y) < 3)
594 return points.get(found).getSequence();
603 * Resets the view to initial state (no rotation)
605 public void resetView()
612 public void zoom(float factor)
616 scalefactor *= factor;
622 public void rotate(float x, float y)
624 if (x == 0f && y == 0f)
630 * get the identity transformation...
632 RotatableMatrix rotmat = new RotatableMatrix();
635 * rotate around the X axis for change in Y
636 * (mouse movement up/down); note we are equating a
637 * number of pixels with degrees of rotation here!
641 rotmat.rotate(y, Axis.X);
645 * rotate around the Y axis for change in X
646 * (mouse movement left/right)
650 rotmat.rotate(x, Axis.Y);
654 * apply the composite transformation to sequence points
656 for (int i = 0; i < npoint; i++)
658 SequencePoint sp = points.get(i);
659 sp.translate(-centre.x, -centre.y, -centre.z);
661 // Now apply the rotation matrix
662 sp.coord = rotmat.vectorMultiply(sp.coord);
664 // Now translate back again
665 sp.translate(centre.x, centre.y, centre.z);
669 * rotate the x/y/z axis positions
671 for (int i = 0; i < DIMS; i++)
673 axisEndPoints[i] = rotmat.vectorMultiply(axisEndPoints[i]);