Clover icon

Coverage Report

  1. Project Clover database Wed Mar 4 2026 12:01:44 GMT
  2. Package jalview.gui

File RotatableCanvas.java

 

Coverage histogram

../../img/srcFileCovDistChart4.png
49% of files have more coverage

Code metrics

98
273
47
1
1,029
638
107
0.39
5.81
47
2.28

Classes

Class Line # Actions
RotatableCanvas 61 273 107
0.3684210536.8%
 

Contributing tests

This file is covered by 2 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
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.
11    *
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.
16    *
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.
20    */
21    package jalview.gui;
22   
23    import java.awt.Color;
24    import java.awt.Dimension;
25    import java.awt.Font;
26    import java.awt.Graphics;
27    import java.awt.Graphics2D;
28    import java.awt.Image;
29    import java.awt.RenderingHints;
30    import java.awt.event.InputEvent;
31    import java.awt.event.KeyEvent;
32    import java.awt.event.KeyListener;
33    import java.awt.event.MouseEvent;
34    import java.awt.event.MouseListener;
35    import java.awt.event.MouseMotionListener;
36    import java.awt.event.MouseWheelEvent;
37    import java.awt.event.MouseWheelListener;
38    import java.util.Arrays;
39    import java.util.Iterator;
40    import java.util.List;
41   
42    import javax.swing.JPanel;
43    import javax.swing.ToolTipManager;
44   
45    import jalview.api.RotatableCanvasI;
46    import jalview.datamodel.AlignmentAnnotation;
47    import jalview.datamodel.Point;
48    import jalview.datamodel.SequenceGroup;
49    import jalview.datamodel.SequenceI;
50    import jalview.datamodel.SequencePoint;
51    import jalview.math.RotatableMatrix;
52    import jalview.math.RotatableMatrix.Axis;
53    import jalview.util.ColorUtils;
54    import jalview.util.MessageManager;
55    import jalview.viewmodel.AlignmentViewport;
56   
57    /**
58    * Models a Panel on which a set of points, and optionally x/y/z axes, can be
59    * drawn, and rotated or zoomed with the mouse
60    */
 
61    public class RotatableCanvas extends JPanel
62    implements MouseListener, MouseMotionListener, KeyListener,
63    RotatableCanvasI, MouseWheelListener
64    {
65    private static final float ZOOM_OUT = 0.9f;
66   
67    private static final float ZOOM_IN = 1.1f;
68   
69    /*
70    * pixels distance within which tooltip shows sequence name
71    */
72    private static final int NEARBY = 3;
73   
74    private static final List<String> AXES = Arrays.asList("x", "y", "z");
75   
76    private static final Color AXIS_COLOUR = Color.yellow;
77   
78    private static final int DIMS = 3;
79   
80    boolean drawAxes = true;
81   
82    int mouseX;
83   
84    int mouseY;
85   
86    Image img;
87   
88    Graphics ig;
89   
90    Dimension prefSize;
91   
92    /*
93    * the min-max [x, y, z] values of sequence points when the points
94    * were set on the object, or when the view is reset;
95    * x and y ranges are not recomputed as points are rotated, as this
96    * would make scaling (zoom) unstable, but z ranges are (for correct
97    * graduated colour brightness based on z-coordinate)
98    */
99    float[] seqMin;
100   
101    float[] seqMax;
102   
103    /*
104    * a scale factor used in drawing; when equal to 1, the points span
105    * half the available width or height (whichever is less); increase this
106    * factor to zoom in, decrease it to zoom out
107    */
108    private float scaleFactor;
109   
110    int npoint;
111   
112    /*
113    * sequences and their (x, y, z) PCA dimension values
114    */
115    List<SequencePoint> sequencePoints;
116   
117    /*
118    * x, y, z axis end points (PCA dimension values)
119    */
120    private Point[] axisEndPoints;
121   
122    // fields for 'select rectangle' (JAL-1124)
123    int rectx1;
124   
125    int recty1;
126   
127    int rectx2;
128   
129    int recty2;
130   
131    AlignmentViewport av;
132   
133    AlignmentPanel ap;
134   
135    private boolean showLabels;
136   
137    private Color bgColour;
138   
139    private boolean applyToAllViews;
140   
141    /**
142    * Constructor
143    *
144    * @param panel
145    */
 
146  4 toggle public RotatableCanvas(AlignmentPanel panel)
147    {
148  4 this.av = panel.av;
149  4 this.ap = panel;
150  4 setAxisEndPoints(new Point[DIMS]);
151  4 setShowLabels(false);
152  4 setApplyToAllViews(false);
153  4 setBgColour(Color.BLACK);
154  4 resetAxes();
155   
156  4 ToolTipManager.sharedInstance().registerComponent(this);
157   
158  4 addMouseListener(this);
159  4 addMouseMotionListener(this);
160  4 addMouseWheelListener(this);
161    }
162   
163    /**
164    * Refreshes the display with labels shown (or not)
165    *
166    * @param show
167    */
 
168  0 toggle public void showLabels(boolean show)
169    {
170  0 setShowLabels(show);
171  0 repaint();
172    }
173   
 
174  4 toggle @Override
175    public void setPoints(List<SequencePoint> points, int np)
176    {
177  4 this.sequencePoints = points;
178  4 this.npoint = np;
179  4 prefSize = getPreferredSize();
180   
181  4 findWidths();
182   
183  4 setScaleFactor(1f);
184    }
185   
186    /**
187    * Resets axes to the initial state: x-axis to the right, y-axis up, z-axis to
188    * back (so obscured in a 2-D display)
189    */
 
190  4 toggle protected void resetAxes()
191    {
192  4 getAxisEndPoints()[0] = new Point(1f, 0f, 0f);
193  4 getAxisEndPoints()[1] = new Point(0f, 1f, 0f);
194  4 getAxisEndPoints()[2] = new Point(0f, 0f, 1f);
195    }
196   
197    /**
198    * Computes and saves the min-max ranges of x/y/z positions of the sequence
199    * points
200    */
 
201  4 toggle protected void findWidths()
202    {
203  4 float[] max = new float[DIMS];
204  4 float[] min = new float[DIMS];
205   
206  4 max[0] = -Float.MAX_VALUE;
207  4 max[1] = -Float.MAX_VALUE;
208  4 max[2] = -Float.MAX_VALUE;
209   
210  4 min[0] = Float.MAX_VALUE;
211  4 min[1] = Float.MAX_VALUE;
212  4 min[2] = Float.MAX_VALUE;
213   
214  4 for (SequencePoint sp : sequencePoints)
215    {
216  74 max[0] = Math.max(max[0], sp.coord.x);
217  74 max[1] = Math.max(max[1], sp.coord.y);
218  74 max[2] = Math.max(max[2], sp.coord.z);
219  74 min[0] = Math.min(min[0], sp.coord.x);
220  74 min[1] = Math.min(min[1], sp.coord.y);
221  74 min[2] = Math.min(min[2], sp.coord.z);
222    }
223   
224  4 seqMin = min;
225  4 seqMax = max;
226    }
227   
228    /**
229    * Answers the preferred size if it has been set, else 400 x 400
230    *
231    * @return
232    */
 
233  4 toggle @Override
234    public Dimension getPreferredSize()
235    {
236  4 if (prefSize != null)
237    {
238  0 return prefSize;
239    }
240    else
241    {
242  4 return new Dimension(400, 400);
243    }
244    }
245   
246    /**
247    * Answers the preferred size
248    *
249    * @return
250    * @see RotatableCanvas#getPreferredSize()
251    */
 
252  0 toggle @Override
253    public Dimension getMinimumSize()
254    {
255  0 return getPreferredSize();
256    }
257   
258    /**
259    * Repaints the panel
260    *
261    * @param g
262    */
 
263  14 toggle @Override
264    public void paintComponent(Graphics g1)
265    {
266   
267  14 Graphics2D g = (Graphics2D) g1;
268   
269  14 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
270    RenderingHints.VALUE_ANTIALIAS_ON);
271  14 if (sequencePoints == null)
272    {
273  0 g.setFont(new Font("Verdana", Font.PLAIN, 18));
274  0 g.drawString(
275    MessageManager.getString("label.calculating_pca") + "....",
276    20, getHeight() / 2);
277    }
278    else
279    {
280    /*
281    * create the image at the beginning or after a resize
282    */
283  14 boolean resized = prefSize.width != getWidth()
284    || prefSize.height != getHeight();
285  14 if (img == null || resized)
286    {
287  4 prefSize.width = getWidth();
288  4 prefSize.height = getHeight();
289   
290  4 img = createImage(getWidth(), getHeight());
291  4 ig = img.getGraphics();
292    }
293   
294  14 drawBackground(ig);
295  14 drawScene(ig);
296   
297  14 if (drawAxes)
298    {
299  14 drawAxes(ig);
300    }
301   
302  14 g.drawImage(img, 0, 0, this);
303    }
304    }
305   
306    /**
307    * Resets the rotation and choice of axes to the initial state (without change
308    * of scale factor)
309    */
 
310  0 toggle public void resetView()
311    {
312  0 img = null;
313  0 findWidths();
314  0 resetAxes();
315  0 repaint();
316    }
317   
318    /**
319    * Draws lines for the x, y, z axes
320    *
321    * @param g
322    */
 
323  14 toggle public void drawAxes(Graphics g)
324    {
325  14 g.setColor(AXIS_COLOUR);
326   
327  14 int midX = getWidth() / 2;
328  14 int midY = getHeight() / 2;
329    // float maxWidth = Math.max(Math.abs(seqMax[0] - seqMin[0]),
330    // Math.abs(seqMax[1] - seqMin[1]));
331  14 int pix = Math.min(getWidth(), getHeight());
332  14 float scaleBy = pix * getScaleFactor() / (2f);
333   
334  56 for (int i = 0; i < DIMS; i++)
335    {
336  42 g.drawLine(midX, midY,
337    midX + (int) (getAxisEndPoints()[i].x * scaleBy * 0.25),
338    midY + (int) (getAxisEndPoints()[i].y * scaleBy * 0.25));
339    }
340    }
341   
342    /**
343    * Fills the background with the currently configured background colour
344    *
345    * @param g
346    */
 
347  14 toggle public void drawBackground(Graphics g)
348    {
349  14 g.setColor(getBgColour());
350  14 g.fillRect(0, 0, prefSize.width, prefSize.height);
351    }
352   
353    /**
354    * Draws points (6x6 squares) for the sequences of the PCA, and labels
355    * (sequence names) if configured to do so. The sequence points colours are
356    * taken from the sequence ids in the alignment (converting black to white).
357    * Sequences 'at the back' (z-coordinate is negative) are shaded slightly
358    * darker to help give a 3-D sensation.
359    *
360    * @param g
361    */
 
362  14 toggle public void drawScene(Graphics g1)
363    {
364  14 Graphics2D g = (Graphics2D) g1;
365   
366  14 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
367    RenderingHints.VALUE_ANTIALIAS_ON);
368  14 int pix = Math.min(getWidth(), getHeight());
369  14 float xWidth = Math.abs(seqMax[0] - seqMin[0]);
370  14 float yWidth = Math.abs(seqMax[1] - seqMin[1]);
371  14 float maxWidth = Math.max(xWidth, yWidth);
372  14 float scaleBy = pix * getScaleFactor() / (2f * maxWidth);
373   
374  14 float[] centre = getCentre();
375   
376  301 for (int i = 0; i < npoint; i++)
377    {
378    /*
379    * sequence point colour as sequence id, but
380    * gray if sequence is currently selected
381    */
382  287 SequencePoint sp = sequencePoints.get(i);
383  287 Color sequenceColour = getSequencePointColour(sp);
384  287 g.setColor(sequenceColour);
385   
386  287 int halfwidth = getWidth() / 2;
387  287 int halfheight = getHeight() / 2;
388  287 int x = (int) ((sp.coord.x - centre[0]) * scaleBy) + halfwidth;
389  287 int y = (int) ((sp.coord.y - centre[1]) * scaleBy) + halfheight;
390  287 g.fillRect(x - 3, y - 3, 6, 6);
391   
392  287 if (isShowLabels())
393    {
394  0 g.setColor(Color.red);
395  0 g.drawString(sp.getDisplayName(), x - 3, y - 4);
396    }
397    }
398  14 if (isShowLabels())
399    {
400  0 g.setColor(AXIS_COLOUR);
401  0 int midX = getWidth() / 2;
402  0 int midY = getHeight() / 2;
403  0 Iterator<String> axes = AXES.iterator();
404  0 for (Point p : getAxisEndPoints())
405    {
406  0 int x = midX + (int) (p.x * scaleBy * seqMax[0]);
407  0 int y = midY + (int) (p.y * scaleBy * seqMax[1]);
408  0 g.drawString(axes.next(), x - 3, y - 4);
409    }
410    }
411    // //Now the rectangle
412  14 if (rectx2 != -1 && recty2 != -1)
413    {
414  14 g.setColor(Color.white);
415   
416  14 g.drawRect(rectx1, recty1, rectx2 - rectx1, recty2 - recty1);
417    }
418    }
419   
420    /**
421    * Determines the colour to use when drawing a sequence point. The colour is
422    * taken from the sequence id, with black converted to white, and then
423    * graduated from darker (at the back) to brighter (at the front) based on the
424    * z-axis coordinate of the point.
425    *
426    * @param sp
427    * @return
428    */
 
429  287 toggle protected Color getSequencePointColour(SequencePoint sp)
430    {
431  287 SequenceI sequence = sp.getSequence();
432  287 Color sequenceColour = av.getSequenceColour(sequence);
433  287 if (sequenceColour == Color.black)
434    {
435  0 sequenceColour = Color.white;
436    }
437  287 if (av.getSelectionGroup() != null)
438    {
439  0 if (av.getSelectionGroup().getSequences(null).contains(sequence))
440    {
441  0 sequenceColour = Color.gray;
442    }
443    }
444   
445    /*
446    * graduate brighter for point in front of centre, darker if behind centre
447    */
448  287 float zCentre = (seqMin[2] + seqMax[2]) / 2f;
449  287 if (sp.coord.z > zCentre)
450    {
451  29 sequenceColour = ColorUtils.getGraduatedColour(sp.coord.z, 0,
452    sequenceColour, seqMax[2], sequenceColour.brighter());
453    }
454  258 else if (sp.coord.z < zCentre)
455    {
456  258 sequenceColour = ColorUtils.getGraduatedColour(sp.coord.z, seqMin[2],
457    sequenceColour.darker(), 0, sequenceColour);
458    }
459   
460  287 return sequenceColour;
461    }
462   
 
463  0 toggle @Override
464    public void keyTyped(KeyEvent evt)
465    {
466    }
467   
 
468  0 toggle @Override
469    public void keyReleased(KeyEvent evt)
470    {
471    }
472   
473    /**
474    * Responds to up or down arrow key by zooming in or out, respectively
475    *
476    * @param evt
477    */
 
478  0 toggle @Override
479    public void keyPressed(KeyEvent evt)
480    {
481  0 int keyCode = evt.getKeyCode();
482  0 boolean shiftDown = evt.isShiftDown();
483   
484  0 if (keyCode == KeyEvent.VK_UP)
485    {
486  0 if (shiftDown)
487    {
488  0 rotate(0f, -1f);
489    }
490    else
491    {
492  0 zoom(ZOOM_IN);
493    }
494    }
495  0 else if (keyCode == KeyEvent.VK_DOWN)
496    {
497  0 if (shiftDown)
498    {
499  0 rotate(0f, 1f);
500    }
501    else
502    {
503  0 zoom(ZOOM_OUT);
504    }
505    }
506  0 else if (shiftDown && keyCode == KeyEvent.VK_LEFT)
507    {
508  0 rotate(1f, 0f);
509    }
510  0 else if (shiftDown && keyCode == KeyEvent.VK_RIGHT)
511    {
512  0 rotate(-1f, 0f);
513    }
514    /*
515    else if (evt.getKeyChar() == 's')
516    {
517    // Cache.warn("DEBUG: Rectangle selection");
518    // todo not yet enabled as rectx2, recty2 are always -1
519    // need to set them in mouseDragged; JAL-1124
520    if ((rectx2 != -1) && (recty2 != -1))
521    {
522    rectSelect(rectx1, recty1, rectx2, recty2);
523    }
524    }
525    */
526   
527  0 repaint();
528    }
529   
 
530  0 toggle @Override
531    public void zoom(float factor)
532    {
533  0 if (factor > 0f)
534    {
535  0 setScaleFactor(getScaleFactor() * factor);
536    }
537    }
538   
 
539  0 toggle @Override
540    public void mouseClicked(MouseEvent evt)
541    {
542    }
543   
 
544  0 toggle @Override
545    public void mouseEntered(MouseEvent evt)
546    {
547    }
548   
 
549  0 toggle @Override
550    public void mouseExited(MouseEvent evt)
551    {
552    }
553   
 
554  0 toggle @Override
555    public void mouseReleased(MouseEvent evt)
556    {
557    }
558   
559    /**
560    * If the mouse press is at (within 2 pixels of) a sequence point, toggles
561    * (adds or removes) the corresponding sequence as a member of the viewport
562    * selection group. This supports configuring a group in the alignment by
563    * clicking on points in the PCA display.
564    */
 
565  0 toggle @Override
566    public void mousePressed(MouseEvent evt)
567    {
568  0 int x = evt.getX();
569  0 int y = evt.getY();
570   
571  0 mouseX = x;
572  0 mouseY = y;
573   
574  0 rectx1 = x;
575  0 recty1 = y;
576  0 rectx2 = -1;
577  0 recty2 = -1;
578   
579  0 SequenceI found = findSequenceAtPoint(x, y);
580  0 AlignmentAnnotation foundAnnot = this.findMappedAnnotationAtPoint(x, y);
581  0 boolean annotationSelected = false;
582   
583  0 if (found != null)
584    {
585  0 AlignmentPanel[] aps = getAssociatedPanels();
586   
587  0 for (int a = 0; a < aps.length; a++)
588    {
589  0 if (aps[a].av.getSelectionGroup() != null)
590    {
591  0 if (foundAnnot != null)
592    {
593  0 annotationSelected = aps[a].av.getSelectionGroup().addOrRemoveAnnotation(foundAnnot);
594    }
595  0 if(annotationSelected) {
596  0 aps[a].av.getSelectionGroup().addSequence(found, true);
597    }
598    else {
599  0 aps[a].av.getSelectionGroup().addOrRemove(found, true);
600    }
601    }
602    else
603    {
604  0 aps[a].av.setSelectionGroup(new SequenceGroup());
605  0 aps[a].av.getSelectionGroup().addOrRemove(found, true);
606  0 if (foundAnnot != null)
607    {
608  0 annotationSelected = aps[a].av.getSelectionGroup().addOrRemoveAnnotation(foundAnnot);
609    }
610  0 aps[a].av.getSelectionGroup()
611    .setEndRes(aps[a].av.getAlignment().getWidth() - 1);
612    }
613    }
614  0 PaintRefresher.Refresh(this, av.getSequenceSetId());
615    // canonical selection is sent to other listeners
616  0 av.sendSelection();
617    }
618   
619  0 repaint();
620    }
621   
622    /**
623    * Sets the tooltip to the name of the sequence within 2 pixels of the mouse
624    * position, or clears the tooltip if none found
625    */
 
626  0 toggle @Override
627    public void mouseMoved(MouseEvent evt)
628    {
629  0 String displayName = findDisplayNameAtPoint(evt.getX(), evt.getY());
630   
631  0 this.setToolTipText(displayName == null ? null : displayName);
632    }
633   
634    /**
635    * Action handler for a mouse drag. Rotates the display around the X axis (for
636    * up/down mouse movement) and/or the Y axis (for left/right mouse movement).
637    *
638    * @param evt
639    */
 
640  0 toggle @Override
641    public void mouseDragged(MouseEvent evt)
642    {
643  0 int xPos = evt.getX();
644  0 int yPos = evt.getY();
645   
646  0 if (xPos == mouseX && yPos == mouseY)
647    {
648  0 return;
649    }
650   
651  0 int xDelta = xPos - mouseX;
652  0 int yDelta = yPos - mouseY;
653   
654    // Check if this is a rectangle drawing drag
655  0 if ((evt.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0)
656    {
657  0 rectx2 = evt.getX();
658  0 recty2 = evt.getY();
659  0 if ((rectx2 != -1) && (recty2 != -1))
660    {
661  0 rectSelect(rectx1, recty1, rectx2, recty2);
662    }
663    }
664    else
665    {
666  0 rotate(xDelta, yDelta);
667   
668  0 mouseX = xPos;
669  0 mouseY = yPos;
670    // findWidths();
671    }
672  0 repaint();
673    }
674   
 
675  0 toggle @Override
676    public void rotate(float x, float y)
677    {
678  0 if (x == 0f && y == 0f)
679    {
680  0 return;
681    }
682   
683    /*
684    * get the identity transformation...
685    */
686  0 RotatableMatrix rotmat = new RotatableMatrix();
687   
688    /*
689    * rotate around the X axis for change in Y
690    * (mouse movement up/down); note we are equating a
691    * number of pixels with degrees of rotation here!
692    */
693  0 if (y != 0)
694    {
695  0 rotmat.rotate(y, Axis.X);
696    }
697   
698    /*
699    * rotate around the Y axis for change in X
700    * (mouse movement left/right)
701    */
702  0 if (x != 0)
703    {
704  0 rotmat.rotate(x, Axis.Y);
705    }
706   
707    /*
708    * apply the composite transformation to sequence points;
709    * update z min-max range (affects colour graduation), but not
710    * x or y min-max (as this would affect axis scaling)
711    */
712  0 float[] centre = getCentre();
713  0 float zMin = Float.MAX_VALUE;
714  0 float zMax = -Float.MAX_VALUE;
715   
716  0 for (int i = 0; i < npoint; i++)
717    {
718  0 SequencePoint sp = sequencePoints.get(i);
719  0 sp.translate(-centre[0], -centre[1], -centre[2]);
720   
721    // Now apply the rotation matrix
722  0 sp.coord = rotmat.vectorMultiply(sp.coord);
723   
724    // Now translate back again
725  0 sp.translate(centre[0], centre[1], centre[2]);
726   
727  0 zMin = Math.min(zMin, sp.coord.z);
728  0 zMax = Math.max(zMax, sp.coord.z);
729    }
730   
731  0 seqMin[2] = zMin;
732  0 seqMax[2] = zMax;
733   
734    /*
735    * rotate the x/y/z axis positions
736    */
737  0 for (int i = 0; i < DIMS; i++)
738    {
739  0 getAxisEndPoints()[i] = rotmat.vectorMultiply(getAxisEndPoints()[i]);
740    }
741    }
742   
743    /**
744    * Answers the x/y/z coordinates that are midway between the maximum and
745    * minimum sequence point values
746    *
747    * @return
748    */
 
749  14 toggle private float[] getCentre()
750    {
751  14 float xCentre = (seqMin[0] + seqMax[0]) / 2f;
752  14 float yCentre = (seqMin[1] + seqMax[1]) / 2f;
753  14 float zCentre = (seqMin[2] + seqMax[2]) / 2f;
754   
755  14 return new float[] { xCentre, yCentre, zCentre };
756    }
757   
758    /**
759    * Adds any sequences whose displayed points are within the given rectangle to
760    * the viewport's current selection. Intended for key 's' after dragging to
761    * select a region of the PCA.
762    *
763    * @param x1
764    * @param y1
765    * @param x2
766    * @param y2
767    */
 
768  0 toggle protected void rectSelect(int x1, int y1, int x2, int y2)
769    {
770  0 float[] centre = getCentre();
771   
772  0 for (int i = 0; i < npoint; i++)
773    {
774  0 SequencePoint sp = sequencePoints.get(i);
775  0 int tmp1 = (int) (((sp.coord.x - centre[0]) * getScaleFactor())
776    * (getWidth() / 3.15) + (getWidth() / 2.0));
777  0 float pre1 = ((sp.coord.x - centre[0]) * getScaleFactor());
778  0 int tmp2 = (int) (((sp.coord.y - centre[1]) * getScaleFactor())
779    * (getHeight() / 1.70) + (getHeight() / 2.0));
780  0 float pre2 = ((sp.coord.y - centre[1]) * getScaleFactor());
781   
782  0 if ((tmp1 > x1) && (tmp1 < x2) && (tmp2 > y1) && (tmp2 < y2))
783    {
784  0 if (av != null)
785    {
786  0 SequenceI sequence = sp.getSequence();
787  0 if (av.getSelectionGroup() == null)
788    {
789  0 SequenceGroup sg = new SequenceGroup();
790  0 sg.setEndRes(av.getAlignment().getWidth() - 1);
791  0 av.setSelectionGroup(sg);
792    }
793  0 if (!av.getSelectionGroup().getSequences(null).contains(sequence))
794    {
795  0 av.getSelectionGroup().addSequence(sequence, true);
796    }
797    }
798    }
799    }
800    }
801   
802    /**
803    * Return the index of the sequence point found whose point on the display is within 2
804    * pixels of the given coordinates, or null if none is found
805    *
806    * @param x
807    * @param y
808    *
809    * @return
810    */
 
811  0 toggle protected int findSequencePointIndex(int x, int y)
812    {
813  0 int halfwidth = getWidth() / 2;
814  0 int halfheight = getHeight() / 2;
815   
816  0 int found = -1;
817  0 int pix = Math.min(getWidth(), getHeight());
818  0 float xWidth = Math.abs(seqMax[0] - seqMin[0]);
819  0 float yWidth = Math.abs(seqMax[1] - seqMin[1]);
820  0 float maxWidth = Math.max(xWidth, yWidth);
821  0 float scaleBy = pix * getScaleFactor() / (2f * maxWidth);
822   
823  0 float[] centre = getCentre();
824   
825  0 for (int i = 0; i < npoint; i++)
826    {
827  0 SequencePoint sp = sequencePoints.get(i);
828  0 int px = (int) ((sp.coord.x - centre[0]) * scaleBy) + halfwidth;
829  0 int py = (int) ((sp.coord.y - centre[1]) * scaleBy) + halfheight;
830   
831  0 if ((Math.abs(px - x) < NEARBY) && (Math.abs(py - y) < NEARBY))
832    {
833  0 found = i;
834  0 break;
835    }
836    }
837  0 return found;
838    }
839   
840    /**
841    * Answers the first sequence found whose point on the display is within 2
842    * pixels of the given coordinates, or null if none is found
843    *
844    * @param x
845    * @param y
846    *
847    * @return
848    */
 
849  0 toggle protected SequenceI findSequenceAtPoint(int x, int y)
850    {
851  0 int found = findSequencePointIndex(x, y);
852   
853  0 if (found != -1)
854    {
855  0 return sequencePoints.get(found).getSequence();
856    }
857    else
858    {
859  0 return null;
860    }
861    }
862   
 
863  0 toggle protected AlignmentAnnotation findMappedAnnotationAtPoint(int x, int y)
864    {
865  0 int found = findSequencePointIndex(x, y);
866   
867  0 if (found != -1)
868    {
869  0 return sequencePoints.get(found).getAlignmentAnnotation();
870    }
871    else
872    {
873  0 return null;
874    }
875    }
876   
877    /**
878    * Returns the display name of the point on the display is within 2
879    * pixels of the given coordinates, or null if none is found
880    *
881    * @param x
882    * @param y
883    *
884    * @return
885    */
 
886  0 toggle public String findDisplayNameAtPoint(int x, int y)
887    {
888   
889  0 int found = findSequencePointIndex(x, y);
890   
891  0 if (found != -1)
892    {
893  0 return sequencePoints.get(found).getDisplayName();
894    }
895    else
896    {
897  0 return null;
898    }
899    }
900   
901    /**
902    * Answers the panel the PCA is associated with (all panels for this alignment
903    * if 'associate with all panels' is selected).
904    *
905    * @return
906    */
 
907  0 toggle AlignmentPanel[] getAssociatedPanels()
908    {
909  0 if (isApplyToAllViews())
910    {
911  0 return PaintRefresher.getAssociatedPanels(av.getSequenceSetId());
912    }
913    else
914    {
915  0 return new AlignmentPanel[] { ap };
916    }
917    }
918   
 
919  2 toggle public Color getBackgroundColour()
920    {
921  2 return getBgColour();
922    }
923   
924    /**
925    * Zooms in or out in response to mouse wheel movement
926    */
 
927  0 toggle @Override
928    public void mouseWheelMoved(MouseWheelEvent e)
929    {
930  0 double wheelRotation = e.getPreciseWheelRotation();
931  0 if (wheelRotation > 0)
932    {
933  0 zoom(ZOOM_IN);
934  0 repaint();
935    }
936  0 else if (wheelRotation < 0)
937    {
938  0 zoom(ZOOM_OUT);
939  0 repaint();
940    }
941    }
942   
943    /**
944    * Answers the sequence point minimum [x, y, z] values. Note these are derived
945    * when sequence points are set, but x and y values are not updated on
946    * rotation (because this would result in changes to scaling).
947    *
948    * @return
949    */
 
950  2 toggle public float[] getSeqMin()
951    {
952  2 return seqMin;
953    }
954   
955    /**
956    * Answers the sequence point maximum [x, y, z] values. Note these are derived
957    * when sequence points are set, but x and y values are not updated on
958    * rotation (because this would result in changes to scaling).
959    *
960    * @return
961    */
 
962  2 toggle public float[] getSeqMax()
963    {
964  2 return seqMax;
965    }
966   
967    /**
968    * Sets the minimum and maximum [x, y, z] positions for sequence points. For
969    * use when restoring a saved PCA from state data.
970    *
971    * @param min
972    * @param max
973    */
 
974  2 toggle public void setSeqMinMax(float[] min, float[] max)
975    {
976  2 seqMin = min;
977  2 seqMax = max;
978    }
979   
 
980  30 toggle public float getScaleFactor()
981    {
982  30 return scaleFactor;
983    }
984   
 
985  6 toggle public void setScaleFactor(float scaleFactor)
986    {
987  6 this.scaleFactor = scaleFactor;
988    }
989   
 
990  303 toggle public boolean isShowLabels()
991    {
992  303 return showLabels;
993    }
994   
 
995  6 toggle public void setShowLabels(boolean showLabels)
996    {
997  6 this.showLabels = showLabels;
998    }
999   
 
1000  2 toggle public boolean isApplyToAllViews()
1001    {
1002  2 return applyToAllViews;
1003    }
1004   
 
1005  8 toggle public void setApplyToAllViews(boolean applyToAllViews)
1006    {
1007  8 this.applyToAllViews = applyToAllViews;
1008    }
1009   
 
1010  104 toggle public Point[] getAxisEndPoints()
1011    {
1012  104 return axisEndPoints;
1013    }
1014   
 
1015  8 toggle public void setAxisEndPoints(Point[] axisEndPoints)
1016    {
1017  8 this.axisEndPoints = axisEndPoints;
1018    }
1019   
 
1020  16 toggle public Color getBgColour()
1021    {
1022  16 return bgColour;
1023    }
1024   
 
1025  6 toggle public void setBgColour(Color bgColour)
1026    {
1027  6 this.bgColour = bgColour;
1028    }
1029    }