Associate PCA with multiple views
[jalview.git] / src / jalview / gui / RotatableCanvas.java
1 /*\r
2  * Jalview - A Sequence Alignment Editor and Viewer\r
3  * Copyright (C) 2006 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle\r
4  *\r
5  * This program is free software; you can redistribute it and/or\r
6  * modify it under the terms of the GNU General Public License\r
7  * as published by the Free Software Foundation; either version 2\r
8  * of the License, or (at your option) any later version.\r
9  *\r
10  * This program is distributed in the hope that it will be useful,\r
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
13  * GNU General Public License for more details.\r
14  *\r
15  * You should have received a copy of the GNU General Public License\r
16  * along with this program; if not, write to the Free Software\r
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA\r
18  */\r
19 package jalview.gui;\r
20 \r
21 import jalview.datamodel.*;\r
22 \r
23 import jalview.math.*;\r
24 \r
25 import java.awt.*;\r
26 import java.awt.event.*;\r
27 \r
28 import java.util.*;\r
29 \r
30 import javax.swing.*;\r
31 \r
32 \r
33 /**\r
34  * DOCUMENT ME!\r
35  *\r
36  * @author $author$\r
37  * @version $Revision$\r
38  */\r
39 public class RotatableCanvas extends JPanel implements MouseListener,\r
40     MouseMotionListener, KeyListener\r
41 {\r
42     RotatableMatrix idmat = new RotatableMatrix(3, 3);\r
43     RotatableMatrix objmat = new RotatableMatrix(3, 3);\r
44     RotatableMatrix rotmat = new RotatableMatrix(3, 3);\r
45 \r
46     //RubberbandRectangle rubberband;\r
47     boolean drawAxes = true;\r
48     int omx = 0;\r
49     int mx = 0;\r
50     int omy = 0;\r
51     int my = 0;\r
52     Image img;\r
53     Graphics ig;\r
54     Dimension prefsize;\r
55     float[] centre = new float[3];\r
56     float[] width = new float[3];\r
57     float[] max = new float[3];\r
58     float[] min = new float[3];\r
59     float maxwidth;\r
60     float scale;\r
61     int npoint;\r
62     Vector points;\r
63     float[][] orig;\r
64     float[][] axes;\r
65     int startx;\r
66     int starty;\r
67     int lastx;\r
68     int lasty;\r
69     int rectx1;\r
70     int recty1;\r
71     int rectx2;\r
72     int recty2;\r
73     float scalefactor = 1;\r
74     AlignViewport av;\r
75     AlignmentPanel ap;\r
76     boolean showLabels = false;\r
77     Color bgColour = Color.black;\r
78     boolean applyToAllViews = false;\r
79 \r
80     //  Controller    controller;\r
81     public RotatableCanvas(AlignmentPanel ap)\r
82     {\r
83       this.av = ap.av;\r
84       this.ap = ap;\r
85 \r
86       addMouseWheelListener(new MouseWheelListener()\r
87       {\r
88         public void mouseWheelMoved(MouseWheelEvent e)\r
89         {\r
90           if (e.getWheelRotation() > 0)\r
91           {\r
92             scale = (float) (scale * 1.1);\r
93             repaint();\r
94           }\r
95 \r
96           else\r
97           {\r
98             scale = (float) (scale * 0.9);\r
99             repaint();\r
100           }\r
101         }\r
102       });\r
103 \r
104     }\r
105 \r
106     public void showLabels(boolean b)\r
107     {\r
108       showLabels = b;\r
109       repaint();\r
110     }\r
111 \r
112     public void setPoints(Vector points, int npoint)\r
113     {\r
114         this.points = points;\r
115         this.npoint = npoint;\r
116         ToolTipManager.sharedInstance().registerComponent(this);\r
117         ToolTipManager.sharedInstance().setInitialDelay(0);\r
118         ToolTipManager.sharedInstance().setDismissDelay(10000);\r
119 \r
120         prefsize = getPreferredSize();\r
121         orig = new float[npoint][3];\r
122 \r
123         for (int i = 0; i < npoint; i++)\r
124         {\r
125             SequencePoint sp = (SequencePoint) points.elementAt(i);\r
126 \r
127             for (int j = 0; j < 3; j++)\r
128             {\r
129                 orig[i][j] = sp.coord[j];\r
130             }\r
131         }\r
132 \r
133         //Initialize the matrices to identity\r
134         for (int i = 0; i < 3; i++)\r
135         {\r
136             for (int j = 0; j < 3; j++)\r
137             {\r
138                 if (i != j)\r
139                 {\r
140                     idmat.addElement(i, j, 0);\r
141                     objmat.addElement(i, j, 0);\r
142                     rotmat.addElement(i, j, 0);\r
143                 }\r
144                 else\r
145                 {\r
146                     idmat.addElement(i, j, 0);\r
147                     objmat.addElement(i, j, 0);\r
148                     rotmat.addElement(i, j, 0);\r
149                 }\r
150             }\r
151         }\r
152 \r
153         axes = new float[3][3];\r
154         initAxes();\r
155 \r
156         findCentre();\r
157         findWidth();\r
158 \r
159         scale = findScale();\r
160 \r
161         addMouseListener(this);\r
162 \r
163         addMouseMotionListener(this);\r
164 \r
165     }\r
166 \r
167     public void initAxes()\r
168     {\r
169         for (int i = 0; i < 3; i++)\r
170         {\r
171             for (int j = 0; j < 3; j++)\r
172             {\r
173                 if (i != j)\r
174                 {\r
175                     axes[i][j] = 0;\r
176                 }\r
177                 else\r
178                 {\r
179                     axes[i][j] = 1;\r
180                 }\r
181             }\r
182         }\r
183     }\r
184 \r
185     /**\r
186      * DOCUMENT ME!\r
187      */\r
188     public void findWidth()\r
189     {\r
190         max = new float[3];\r
191         min = new float[3];\r
192 \r
193         max[0] = (float) -1e30;\r
194         max[1] = (float) -1e30;\r
195         max[2] = (float) -1e30;\r
196 \r
197         min[0] = (float) 1e30;\r
198         min[1] = (float) 1e30;\r
199         min[2] = (float) 1e30;\r
200 \r
201         for (int i = 0; i < 3; i++)\r
202         {\r
203             for (int j = 0; j < npoint; j++)\r
204             {\r
205                 SequencePoint sp = (SequencePoint) points.elementAt(j);\r
206 \r
207                 if (sp.coord[i] >= max[i])\r
208                 {\r
209                     max[i] = sp.coord[i];\r
210                 }\r
211 \r
212                 if (sp.coord[i] <= min[i])\r
213                 {\r
214                     min[i] = sp.coord[i];\r
215                 }\r
216             }\r
217         }\r
218 \r
219         //    System.out.println("xmax " + max[0] + " min " + min[0]);\r
220         //System.out.println("ymax " + max[1] + " min " + min[1]);\r
221         //System.out.println("zmax " + max[2] + " min " + min[2]);\r
222         width[0] = Math.abs(max[0] - min[0]);\r
223         width[1] = Math.abs(max[1] - min[1]);\r
224         width[2] = Math.abs(max[2] - min[2]);\r
225 \r
226         maxwidth = width[0];\r
227 \r
228         if (width[1] > width[0])\r
229         {\r
230             maxwidth = width[1];\r
231         }\r
232 \r
233         if (width[2] > width[1])\r
234         {\r
235             maxwidth = width[2];\r
236         }\r
237 \r
238         //System.out.println("Maxwidth = " + maxwidth);\r
239     }\r
240 \r
241     /**\r
242      * DOCUMENT ME!\r
243      *\r
244      * @return DOCUMENT ME!\r
245      */\r
246     public float findScale()\r
247     {\r
248         int dim;\r
249         int width;\r
250         int height;\r
251 \r
252         if (getWidth() != 0)\r
253         {\r
254             width = getWidth();\r
255             height = getHeight();\r
256         }\r
257         else\r
258         {\r
259             width = prefsize.width;\r
260             height = prefsize.height;\r
261         }\r
262 \r
263         if (width < height)\r
264         {\r
265             dim = width;\r
266         }\r
267         else\r
268         {\r
269             dim = height;\r
270         }\r
271 \r
272         return (float) ((dim * scalefactor) / (2 * maxwidth));\r
273     }\r
274 \r
275     /**\r
276      * DOCUMENT ME!\r
277      */\r
278     public void findCentre()\r
279     {\r
280         //Find centre coordinate\r
281         findWidth();\r
282 \r
283         centre[0] = (max[0] + min[0]) / 2;\r
284         centre[1] = (max[1] + min[1]) / 2;\r
285         centre[2] = (max[2] + min[2]) / 2;\r
286 \r
287         //    System.out.println("Centre x " + centre[0]);\r
288         //System.out.println("Centre y " + centre[1]);\r
289         //System.out.println("Centre z " + centre[2]);\r
290     }\r
291 \r
292     /**\r
293      * DOCUMENT ME!\r
294      *\r
295      * @return DOCUMENT ME!\r
296      */\r
297     public Dimension getPreferredSize()\r
298     {\r
299         if (prefsize != null)\r
300         {\r
301             return prefsize;\r
302         }\r
303         else\r
304         {\r
305             return new Dimension(400, 400);\r
306         }\r
307     }\r
308 \r
309     /**\r
310      * DOCUMENT ME!\r
311      *\r
312      * @return DOCUMENT ME!\r
313      */\r
314     public Dimension getMinimumSize()\r
315     {\r
316         return getPreferredSize();\r
317     }\r
318 \r
319     /**\r
320      * DOCUMENT ME!\r
321      *\r
322      * @param g DOCUMENT ME!\r
323      */\r
324     public void paintComponent(Graphics g1)\r
325     {\r
326 \r
327       Graphics2D g = (Graphics2D) g1;\r
328 \r
329       g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,\r
330                          RenderingHints.VALUE_ANTIALIAS_ON);\r
331       if(points==null)\r
332       {\r
333         g.setFont(new Font("Verdana", Font.PLAIN, 18));\r
334         g.drawString("Calculating PCA....", 20, getHeight()/2);\r
335       }\r
336       else\r
337       {\r
338         //Only create the image at the beginning -\r
339         if ( (img == null) || (prefsize.width != getWidth()) ||\r
340             (prefsize.height != getHeight()))\r
341         {\r
342           prefsize.width = getWidth();\r
343           prefsize.height = getHeight();\r
344 \r
345           scale = findScale();\r
346 \r
347           //      System.out.println("New scale = " + scale);\r
348           img = createImage(getWidth(), getHeight());\r
349           ig = img.getGraphics();\r
350         }\r
351 \r
352 \r
353         drawBackground(ig, bgColour);\r
354         drawScene(ig);\r
355 \r
356         if (drawAxes == true)\r
357         {\r
358           drawAxes(ig);\r
359         }\r
360 \r
361         g.drawImage(img, 0, 0, this);\r
362       }\r
363     }\r
364 \r
365     /**\r
366      * DOCUMENT ME!\r
367      *\r
368      * @param g DOCUMENT ME!\r
369      */\r
370     public void drawAxes(Graphics g)\r
371     {\r
372 \r
373         g.setColor(Color.yellow);\r
374 \r
375         for (int i = 0; i < 3; i++)\r
376         {\r
377             g.drawLine(getWidth() / 2, getHeight() / 2,\r
378                 (int) ((axes[i][0] * scale * max[0]) + (getWidth() / 2)),\r
379                 (int) ((axes[i][1] * scale * max[1]) + (getHeight() / 2)));\r
380         }\r
381     }\r
382 \r
383     /**\r
384      * DOCUMENT ME!\r
385      *\r
386      * @param g DOCUMENT ME!\r
387      * @param col DOCUMENT ME!\r
388      */\r
389     public void drawBackground(Graphics g, Color col)\r
390     {\r
391         g.setColor(col);\r
392         g.fillRect(0, 0, prefsize.width, prefsize.height);\r
393     }\r
394 \r
395     /**\r
396      * DOCUMENT ME!\r
397      *\r
398      * @param g DOCUMENT ME!\r
399      */\r
400     public void drawScene(Graphics g1)\r
401     {\r
402 \r
403       Graphics2D g = (Graphics2D) g1;\r
404 \r
405       g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,\r
406                          RenderingHints.VALUE_ANTIALIAS_ON);\r
407 \r
408 \r
409         int halfwidth = getWidth() / 2;\r
410         int halfheight = getHeight() / 2;\r
411 \r
412         for (int i = 0; i < npoint; i++)\r
413         {\r
414             SequencePoint sp = (SequencePoint) points.elementAt(i);\r
415             int x = (int) ((float) (sp.coord[0] - centre[0]) * scale) +\r
416                 halfwidth;\r
417             int y = (int) ((float) (sp.coord[1] - centre[1]) * scale) +\r
418                 halfheight;\r
419             float z = sp.coord[1] - centre[2];\r
420 \r
421             if (av.getSequenceColour(sp.sequence) == Color.black)\r
422             {\r
423                 g.setColor(Color.white);\r
424             }\r
425             else\r
426             {\r
427                 g.setColor(av.getSequenceColour(sp.sequence));\r
428             }\r
429 \r
430             if (av.getSelectionGroup() != null)\r
431             {\r
432                 if (av.getSelectionGroup().getSequences(false).contains(\r
433                             ((SequencePoint) points.elementAt(i)).sequence))\r
434                 {\r
435                     g.setColor(Color.gray);\r
436                 }\r
437             }\r
438 \r
439             if (z < 0)\r
440             {\r
441                 g.setColor(g.getColor().darker());\r
442             }\r
443 \r
444             g.fillRect(x - 3, y - 3, 6, 6);\r
445             if(showLabels)\r
446             {\r
447               g.setColor(Color.red);\r
448               g.drawString( ( (SequencePoint) points.elementAt(i)).sequence.\r
449                            getName(),\r
450                            x - 3, y - 4);\r
451             }\r
452         }\r
453 \r
454         //    //Now the rectangle\r
455         //    if (rectx2 != -1 && recty2 != -1) {\r
456         //      g.setColor(Color.white);\r
457         //\r
458         //      g.drawRect(rectx1,recty1,rectx2-rectx1,recty2-recty1);\r
459         //    }\r
460     }\r
461 \r
462     /**\r
463      * DOCUMENT ME!\r
464      *\r
465      * @return DOCUMENT ME!\r
466      */\r
467     public Dimension minimumsize()\r
468     {\r
469         return prefsize;\r
470     }\r
471 \r
472     /**\r
473      * DOCUMENT ME!\r
474      *\r
475      * @return DOCUMENT ME!\r
476      */\r
477     public Dimension preferredsize()\r
478     {\r
479         return prefsize;\r
480     }\r
481 \r
482     /**\r
483      * DOCUMENT ME!\r
484      *\r
485      * @param evt DOCUMENT ME!\r
486      */\r
487     public void keyTyped(KeyEvent evt)\r
488     {\r
489     }\r
490 \r
491     /**\r
492      * DOCUMENT ME!\r
493      *\r
494      * @param evt DOCUMENT ME!\r
495      */\r
496     public void keyReleased(KeyEvent evt)\r
497     {\r
498     }\r
499 \r
500     /**\r
501      * DOCUMENT ME!\r
502      *\r
503      * @param evt DOCUMENT ME!\r
504      */\r
505     public void keyPressed(KeyEvent evt)\r
506     {\r
507         if (evt.getKeyCode() == KeyEvent.VK_UP)\r
508         {\r
509             scalefactor = (float) (scalefactor * 1.1);\r
510             scale = findScale();\r
511         }\r
512         else if (evt.getKeyCode() == KeyEvent.VK_DOWN)\r
513         {\r
514             scalefactor = (float) (scalefactor * 0.9);\r
515             scale = findScale();\r
516         }\r
517         else if (evt.getKeyChar() == 's')\r
518         {\r
519             System.err.println("DEBUG: Rectangle selection"); // log.debug\r
520 \r
521             if ((rectx2 != -1) && (recty2 != -1))\r
522             {\r
523                 rectSelect(rectx1, recty1, rectx2, recty2);\r
524             }\r
525         }\r
526 \r
527         repaint();\r
528     }\r
529 \r
530 \r
531     /**\r
532      * DOCUMENT ME!\r
533      *\r
534      * @param evt DOCUMENT ME!\r
535      */\r
536     public void mouseClicked(MouseEvent evt)\r
537     {\r
538     }\r
539 \r
540     /**\r
541      * DOCUMENT ME!\r
542      *\r
543      * @param evt DOCUMENT ME!\r
544      */\r
545     public void mouseEntered(MouseEvent evt)\r
546     {\r
547     }\r
548 \r
549     /**\r
550      * DOCUMENT ME!\r
551      *\r
552      * @param evt DOCUMENT ME!\r
553      */\r
554     public void mouseExited(MouseEvent evt)\r
555     {\r
556     }\r
557 \r
558     /**\r
559      * DOCUMENT ME!\r
560      *\r
561      * @param evt DOCUMENT ME!\r
562      */\r
563     public void mouseReleased(MouseEvent evt)\r
564     {\r
565     }\r
566 \r
567     /**\r
568      * DOCUMENT ME!\r
569      *\r
570      * @param evt DOCUMENT ME!\r
571      */\r
572     public void mousePressed(MouseEvent evt)\r
573     {\r
574         int x = evt.getX();\r
575         int y = evt.getY();\r
576 \r
577         mx = x;\r
578         my = y;\r
579 \r
580         omx = mx;\r
581         omy = my;\r
582 \r
583         startx = x;\r
584         starty = y;\r
585 \r
586         rectx1 = x;\r
587         recty1 = y;\r
588 \r
589         rectx2 = -1;\r
590         recty2 = -1;\r
591 \r
592         SequenceI found = findPoint(x, y);\r
593 \r
594         if (found != null)\r
595         {\r
596           AlignmentPanel[] aps = getAssociatedPanels();\r
597 \r
598           for (int a = 0; a < aps.length; a++)\r
599           {\r
600             if (aps[a].av.getSelectionGroup() != null)\r
601             {\r
602               aps[a].av.getSelectionGroup().addOrRemove(found, true);\r
603             }\r
604             else\r
605             {\r
606               aps[a].av.setSelectionGroup(new SequenceGroup());\r
607               aps[a].av.getSelectionGroup().addOrRemove(found, true);\r
608               aps[a].av.getSelectionGroup().setEndRes(\r
609                   aps[a].av.alignment.getWidth() - 1);\r
610             }\r
611           }\r
612 \r
613           PaintRefresher.Refresh(this, av.getSequenceSetId());\r
614         }\r
615 \r
616         repaint();\r
617     }\r
618 \r
619     // private void fireSequenceSelectionEvent(Selection sel) {\r
620     //   controller.handleSequenceSelectionEvent(new SequenceSelectionEvent(this,sel));\r
621     //}\r
622     public void mouseMoved(MouseEvent evt)\r
623     {\r
624         SequenceI found = findPoint(evt.getX(), evt.getY());\r
625 \r
626         if (found != null)\r
627         {\r
628             this.setToolTipText(found.getName());\r
629         }\r
630         else\r
631         {\r
632             this.setToolTipText(null);\r
633         }\r
634     }\r
635 \r
636     /**\r
637      * DOCUMENT ME!\r
638      *\r
639      * @param evt DOCUMENT ME!\r
640      */\r
641     public void mouseDragged(MouseEvent evt)\r
642     {\r
643         mx = evt.getX();\r
644         my = evt.getY();\r
645 \r
646         //Check if this is a rectangle drawing drag\r
647         if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) != 0)\r
648         {\r
649             //      rectx2 = evt.getX();\r
650             //      recty2 = evt.getY();\r
651         }\r
652         else\r
653         {\r
654             rotmat.setIdentity();\r
655 \r
656             rotmat.rotate((float) (my - omy), 'x');\r
657             rotmat.rotate((float) (mx - omx), 'y');\r
658 \r
659             for (int i = 0; i < npoint; i++)\r
660             {\r
661                 SequencePoint sp = (SequencePoint) points.elementAt(i);\r
662                 sp.coord[0] -= centre[0];\r
663                 sp.coord[1] -= centre[1];\r
664                 sp.coord[2] -= centre[2];\r
665 \r
666                 //Now apply the rotation matrix\r
667                 sp.coord = rotmat.vectorMultiply(sp.coord);\r
668 \r
669                 //Now translate back again\r
670                 sp.coord[0] += centre[0];\r
671                 sp.coord[1] += centre[1];\r
672                 sp.coord[2] += centre[2];\r
673             }\r
674 \r
675             for (int i = 0; i < 3; i++)\r
676             {\r
677                 axes[i] = rotmat.vectorMultiply(axes[i]);\r
678             }\r
679 \r
680             omx = mx;\r
681             omy = my;\r
682 \r
683             paint(this.getGraphics());\r
684         }\r
685     }\r
686 \r
687     /**\r
688      * DOCUMENT ME!\r
689      *\r
690      * @param x1 DOCUMENT ME!\r
691      * @param y1 DOCUMENT ME!\r
692      * @param x2 DOCUMENT ME!\r
693      * @param y2 DOCUMENT ME!\r
694      */\r
695     public void rectSelect(int x1, int y1, int x2, int y2)\r
696     {\r
697         for (int i = 0; i < npoint; i++)\r
698         {\r
699             SequencePoint sp = (SequencePoint) points.elementAt(i);\r
700             int tmp1 = (int) (((sp.coord[0] - centre[0]) * scale) +\r
701                 ((float) getWidth() / 2.0));\r
702             int tmp2 = (int) (((sp.coord[1] - centre[1]) * scale) +\r
703                 ((float) getHeight() / 2.0));\r
704 \r
705             if ((tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2))\r
706             {\r
707                 if (av != null)\r
708                 {\r
709                     if (!av.getSelectionGroup().getSequences(false).contains(sp.sequence))\r
710                     {\r
711                         av.getSelectionGroup().addSequence(sp.sequence, true);\r
712                     }\r
713                 }\r
714             }\r
715         }\r
716 \r
717         // if (changedSel) {\r
718         //    fireSequenceSelectionEvent(av.getSelection());\r
719         // }\r
720     }\r
721 \r
722     /**\r
723      * DOCUMENT ME!\r
724      *\r
725      * @param x DOCUMENT ME!\r
726      * @param y DOCUMENT ME!\r
727      *\r
728      * @return DOCUMENT ME!\r
729      */\r
730     public SequenceI findPoint(int x, int y)\r
731     {\r
732         int halfwidth = getWidth() / 2;\r
733         int halfheight = getHeight() / 2;\r
734 \r
735         int found = -1;\r
736 \r
737         for (int i = 0; i < npoint; i++)\r
738         {\r
739             SequencePoint sp = (SequencePoint) points.elementAt(i);\r
740             int px = (int) ((float) (sp.coord[0] - centre[0]) * scale) +\r
741                 halfwidth;\r
742             int py = (int) ((float) (sp.coord[1] - centre[1]) * scale) +\r
743                 halfheight;\r
744 \r
745             if ((Math.abs(px - x) < 3) && (Math.abs(py - y) < 3))\r
746             {\r
747                 found = i;\r
748             }\r
749         }\r
750 \r
751         if (found != -1)\r
752         {\r
753             return ((SequencePoint) points.elementAt(found)).sequence;\r
754         }\r
755         else\r
756         {\r
757             return null;\r
758         }\r
759     }\r
760 \r
761     AlignmentPanel[] getAssociatedPanels()\r
762     {\r
763       if (applyToAllViews)\r
764       {\r
765         return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());\r
766       }\r
767       else\r
768         return new AlignmentPanel[]\r
769             { ap };\r
770     }\r
771 }\r