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.Vector;
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 Vector<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(Vector<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.elementAt(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.elementAt(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 if (evt.getKeyCode() == KeyEvent.VK_UP)
397 scalefactor = (float) (scalefactor * 1.1);
400 else if (evt.getKeyCode() == KeyEvent.VK_DOWN)
402 scalefactor = (float) (scalefactor * 0.9);
405 else if (evt.getKeyChar() == 's')
407 System.err.println("DEBUG: Rectangle selection"); // log.debug
408 if (rectx2 != -1 && recty2 != -1)
410 rectSelect(rectx1, recty1, rectx2, recty2);
418 public void mouseClicked(MouseEvent evt)
423 public void mouseEntered(MouseEvent evt)
428 public void mouseExited(MouseEvent evt)
433 public void mouseReleased(MouseEvent evt)
438 public void mousePressed(MouseEvent evt)
455 SequenceI found = findSequenceAtPoint(x, y);
459 // TODO: applet PCA is not associatable with multi-panels - only parent
461 if (av.getSelectionGroup() != null)
463 av.getSelectionGroup().addOrRemove(found, true);
464 av.getSelectionGroup().setEndRes(av.getAlignment().getWidth() - 1);
468 av.setSelectionGroup(new SequenceGroup());
469 av.getSelectionGroup().addOrRemove(found, true);
470 av.getSelectionGroup().setEndRes(av.getAlignment().getWidth() - 1);
473 PaintRefresher.Refresh(this, av.getSequenceSetId());
480 public void mouseMoved(MouseEvent evt)
482 SequenceI found = findSequenceAtPoint(evt.getX(), evt.getY());
489 tooltip = found.getName();
497 public void mouseDragged(MouseEvent evt)
499 int xPos = evt.getX();
500 int yPos = evt.getY();
502 RotatableMatrix rotmat = new RotatableMatrix();
504 rotmat.rotate(yPos - mouseY, Axis.X);
505 rotmat.rotate(xPos - mouseX, Axis.Y);
507 for (int i = 0; i < npoint; i++)
509 SequencePoint sp = points.elementAt(i);
510 sp.translateBack(centre);
512 // Now apply the rotation matrix
513 sp.coord = rotmat.vectorMultiply(sp.coord);
515 // Now translate back again
516 sp.translate(centre);
519 for (int i = 0; i < 3; i++)
521 axisEndPoints[i] = rotmat.vectorMultiply(axisEndPoints[i]);
526 paint(this.getGraphics());
529 public void rectSelect(int x1, int y1, int x2, int y2)
531 // boolean changedSel = false;
532 for (int i = 0; i < npoint; i++)
534 SequencePoint sp = points.elementAt(i);
535 int tmp1 = (int) ((sp.coord.x - centre.x) * scale
536 + getSize().width / 2.0);
537 int tmp2 = (int) ((sp.coord.y - centre.y) * scale
538 + getSize().height / 2.0);
540 SequenceI sequence = sp.getSequence();
541 if (tmp1 > x1 && tmp1 < x2 && tmp2 > y1 && tmp2 < y2)
545 if (!av.getSelectionGroup().getSequences(null)
548 av.getSelectionGroup().addSequence(sequence, true);
556 * Answers the first sequence found whose point on the display is within 2
557 * pixels of the given coordinates, or null if none is found
564 public SequenceI findSequenceAtPoint(int x, int y)
566 int halfwidth = getSize().width / 2;
567 int halfheight = getSize().height / 2;
571 for (int i = 0; i < npoint; i++)
574 SequencePoint sp = points.elementAt(i);
575 int px = (int) ((sp.coord.x - centre.x) * scale)
577 int py = (int) ((sp.coord.y - centre.y) * scale)
580 if (Math.abs(px - x) < 3 && Math.abs(py - y) < 3)
589 return points.elementAt(found).getSequence();
598 * Resets the view to initial state (no rotation)
600 public void resetView()