Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.gui

File RotatableCanvas.java

 

Coverage histogram

../../img/srcFileCovDistChart5.png
39% of files have more coverage

Code metrics

84
240
44
1
947
574
95
0.4
5.45
44
2.16

Classes

Class Line # Actions
RotatableCanvas 60 240 95
0.410326141%
 

Contributing tests

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