Clover icon

Coverage Report

  1. Project Clover database Wed Nov 13 2024 16:12:26 GMT
  2. Package jalview.gui

File RotatableCanvas.java

 

Coverage histogram

../../img/srcFileCovDistChart2.png
54% of files have more coverage

Code metrics

88
255
44
1
960
593
99
0.39
5.8
44
2.25

Classes

Class Line # Actions
RotatableCanvas 60 255 99
0.1886304918.9%
 

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