1 /* Plot.java
2  *
3  * created: Thu Dec 17 1998
4  *
5  * This file is part of Artemis
6  *
7  * Copyright (C) 1998,1999,2000  Genome Research Limited
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License
11  * as published by the Free Software Foundation; either version 2
12  * of the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22  *
23  * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/Plot.java,v 1.26 2009-08-18 09:01:44 tjc Exp $
24  **/
25 
26 package uk.ac.sanger.artemis.components;
27 
28 import uk.ac.sanger.artemis.Options;
29 import uk.ac.sanger.artemis.circular.TextFieldFloat;
30 import uk.ac.sanger.artemis.plot.*;
31 
32 import java.awt.BorderLayout;
33 import java.awt.Color;
34 import java.awt.FontMetrics;
35 import java.awt.Graphics;
36 import java.awt.GridLayout;
37 import java.awt.Image;
38 import java.awt.Scrollbar;
39 import java.awt.event.*;
40 import java.util.Vector;
41 
42 import javax.swing.JMenu;
43 import javax.swing.JPanel;
44 import javax.swing.JComponent;
45 import javax.swing.JLabel;
46 import javax.swing.JSplitPane;
47 import javax.swing.JTextField;
48 import javax.swing.JOptionPane;
49 import javax.swing.JCheckBoxMenuItem;
50 import javax.swing.JMenuItem;
51 import javax.swing.JScrollBar;
52 import javax.swing.JPopupMenu;
53 
54 import org.apache.batik.svggen.SVGGraphics2D;
55 
56 /**
57  *  This class implements a simple plot component.
58  *  @author Kim Rutherford
59  **/
60 
61 public abstract class Plot extends JPanel
62 {
63   private static final long serialVersionUID = 1L;
64 
65   /** scroll bar for changing the window size. */
66   private JScrollBar window_changer = null;
67 
68   /** height of the font used in this component. */
69   private int font_height;
70 
71   /** off screen image used for double buffering when drawing */
72   private Image offscreen;
73 
74   /**
75    *  The object that will generate the value we plot in this component.
76    **/
77   private Algorithm algorithm;
78 
79   /**
80    *  If true then a scale line will be drawn at the bottom of the graph when
81    *  drawScaleLine() is called.
82    **/
83   private boolean draw_scale;
84 
85   private final int SCROLL_NOB_SIZE = 10;
86 
87   /**
88    *  Set to true if drawMultiValueGraph() should call recalculateValues().
89    *  It is reset to false by recalculateValues().
90    **/
91   protected boolean recalculate_flag = true;
92 
93   private boolean showAverage = true;
94 
95   /**
96    *  The x position of the last click or -1 if the user hasn't clicked
97    *  anywhere yet or the user clicked outside the graph.
98    **/
99   private int cross_hair_position = -1;
100 
101   /**
102    *  The x position of the start of the last mouse drag or -1 if the user
103    *  hasn't clicked anywhere yet or the user clicked outside the graph.
104    **/
105   private int drag_start_position = -1;
106 
107   /**
108    *  A vector of those objects listening for PlotMouse events.
109    **/
110   final private Vector<PlotMouseListener> listener_list = new Vector<PlotMouseListener>();
111 
112   /**
113    *  Recalculate the values all the state that is used for drawing the plot
114    **/
recalculateValues()115   protected abstract void recalculateValues();
116 
117   /**
118    *  Get the position in the Feature or Sequence of the given x canvas
119    *  position.  This is the label used when the user clicks the mouse in on
120    *  the canvas (see drawCrossHair()).
121    **/
getPointPosition(final int canvas_x_position)122   protected abstract int getPointPosition(final int canvas_x_position);
123 
calculateFeatures(boolean fromPeak)124   protected abstract void calculateFeatures(boolean fromPeak);
125 
showAveragesForRange()126   protected abstract void showAveragesForRange();
127 
128   /** number of graph lines to be drawn */
129   private int numPlots;
130 
131   protected LineAttributes lines[];
132 
133   private int lastPaintHeight = getHeight();
134 
135   /** the minimum distance in pixels between the labels */
136   private final static int MINIMUM_LABEL_SPACING = 50;
137 
138   /**
139    *  Create a new plot component.
140    *  @param algorithm The object that will generate the values we plot in
141    *    this component.
142    *  @param draw_scale If true then a scale line will be drawn at the bottom
143    *    of the graph.
144    **/
Plot(Algorithm algorithm, boolean draw_scale)145   public Plot(Algorithm algorithm, boolean draw_scale)
146   {
147     super(new BorderLayout());
148     this.algorithm = algorithm;
149     this.draw_scale = draw_scale;
150 
151     setFont(Options.getOptions().getFont());
152     FontMetrics fm = getFontMetrics(getFont());
153     font_height = fm.getHeight();
154 
155     final int MAX_WINDOW;
156     if(getAlgorithm().getDefaultMaxWindowSize() != null)
157       MAX_WINDOW = getAlgorithm().getDefaultMaxWindowSize().intValue();
158     else
159       MAX_WINDOW = 500;
160 
161     final int MIN_WINDOW;
162     if(getAlgorithm().getDefaultMinWindowSize() != null)
163       MIN_WINDOW = getAlgorithm().getDefaultMinWindowSize().intValue();
164     else
165       MIN_WINDOW = 5;
166 
167     final int START_WINDOW;
168     if(getAlgorithm().getDefaultWindowSize() == null)
169       START_WINDOW = 10;
170     else
171       START_WINDOW = getAlgorithm().getDefaultWindowSize().intValue();
172 
173     window_changer = new JScrollBar(Scrollbar.VERTICAL);
174     window_changer.setValues(START_WINDOW, SCROLL_NOB_SIZE,
175                              MIN_WINDOW, MAX_WINDOW + SCROLL_NOB_SIZE);
176     if(MAX_WINDOW >= 50)
177       window_changer.setBlockIncrement(MAX_WINDOW/50);
178     else
179       window_changer.setBlockIncrement(1);
180 
181     window_changer.addAdjustmentListener(new AdjustmentListener()
182     {
183       public void adjustmentValueChanged(AdjustmentEvent e)
184       {
185         recalculate_flag = true;
186         repaint();
187       }
188     });
189 
190     addComponentListener(new ComponentAdapter()
191     {
192       public void componentShown(ComponentEvent e)
193       {
194         recalculate_flag = true;
195         repaint();
196       }
197     });
198 
199     add(window_changer, "East");
200     addMouseListener(mouse_listener);
201     addMouseMotionListener(mouse_motion_listener);
202   }
203 
204   /**
205    *  Return the algorithm that was passed to the constructor.
206    **/
getAlgorithm()207   public Algorithm getAlgorithm()
208   {
209     return algorithm;
210   }
211 
212   /**
213    *  Return the current value of the window size, as set by the
214    *  window_changer scrollbar.
215    **/
getWindowSize()216   public int getWindowSize()
217   {
218     return window_changer.getValue();
219   }
220 
221   final MouseListener mouse_listener = new MouseAdapter()
222   {
223     /**
224      *  Listen for mouse press popup menu and crosshair events.
225      **/
226     public void mousePressed(MouseEvent event)
227     {
228       if(event.isPopupTrigger() || event.isMetaDown())
229       {
230         final JComponent parent = (JComponent)event.getSource();
231         final JPopupMenu popup  = new JPopupMenu("Plot Options");
232 
233         // configure colours for multiple graph plots
234         final JMenuItem config = new JMenuItem("Configure...");
235         config.addActionListener(new ActionListener()
236         {
237           public void actionPerformed(ActionEvent actionEvent)
238           {
239             lines = LineAttributes.configurePlots(numPlots, lines, Plot.this);
240           }
241         });
242         popup.add(config);
243 
244 
245         final JMenuItem setScale = new JMenuItem("Set the Window Size...");
246         setScale.addActionListener(new ActionListener()
247         {
248           public void actionPerformed(ActionEvent actionEvent)
249           {
250             final JTextField newWinSize = new JTextField(Integer.toString(getWindowSize()));
251             String window_options[] = { "Set Window Size", "Cancel" };
252             int select = JOptionPane.showOptionDialog(null,
253                                         newWinSize,
254                                         "Set Window Size",
255                                          JOptionPane.DEFAULT_OPTION,
256                                          JOptionPane.QUESTION_MESSAGE,
257                                          null, window_options, window_options[0]);
258             final int value;
259             try
260             {
261               value = Integer.parseInt(newWinSize.getText().trim());
262             }
263             catch(NumberFormatException nfe)
264             {
265               return;
266             }
267             if(value > window_changer.getMaximum() ||
268                value < window_changer.getMinimum())
269             {
270               window_options[0] = "Continue";
271               select = JOptionPane.showOptionDialog(null,
272                                         "Value selected: " + value +
273                                         " is outside the range\n"+
274                                         " Min: "+window_changer.getMinimum() +
275                                         " Max: "+window_changer.getMaximum(),
276                                         "Set Window Size",
277                                          JOptionPane.DEFAULT_OPTION,
278                                          JOptionPane.WARNING_MESSAGE,
279                                          null, window_options, window_options[1]);
280               if(select == 1)
281                 return;
282 
283               if(value > window_changer.getMaximum())
284                 window_changer.setMaximum(value+10);
285               else
286                 window_changer.setMinimum(value);
287             }
288 
289             if(select == 0)
290             {
291               recalculate_flag = true;
292               window_changer.setValue(value);
293               repaint();
294             }
295           }
296         });
297         popup.add(setScale);
298 
299 
300         final JCheckBoxMenuItem scaling_toggle =
301           new JCheckBoxMenuItem("Scaling",getAlgorithm().scalingFlag());
302         scaling_toggle.addItemListener(new ItemListener()
303         {
304           public void itemStateChanged(ItemEvent actionEvent)
305           {
306             getAlgorithm().setScalingFlag(scaling_toggle.getState());
307             recalculate_flag = true;
308             repaint();
309           }
310         });
311         popup.add(scaling_toggle);
312 
313         final JCheckBoxMenuItem showAverageLn = new JCheckBoxMenuItem("Show average", showAverage);
314         showAverageLn.addItemListener(new ItemListener()
315         {
316           public void itemStateChanged(ItemEvent itemEvent)
317           {
318             showAverage = showAverageLn.isSelected();
319             repaint();
320           }
321         });
322         popup.add(showAverageLn);
323 
324         if(Plot.this instanceof BasePlot)
325         {
326           final JMenuItem showMinMaxValues =
327             new JMenuItem("Set Min/Max Values...");
328           popup.add(showMinMaxValues);
329           showMinMaxValues.addActionListener(new ActionListener()
330           {
331             public void actionPerformed(ActionEvent e)
332             {
333               JPanel gridPane = new JPanel(new GridLayout(2,2));
334               TextFieldFloat minValue = new TextFieldFloat();
335               minValue.setValue( ((BasePlot)Plot.this).getMin_value() );
336               gridPane.add(new JLabel("Min:"));
337               gridPane.add(minValue);
338 
339               TextFieldFloat maxValue = new TextFieldFloat();
340               maxValue.setValue( ((BasePlot)Plot.this).getMax_value() );
341               gridPane.add(new JLabel("Max:"));
342               gridPane.add(maxValue);
343 
344               String window_options[] = { "Set", "Cancel" };
345               int select = JOptionPane.showOptionDialog(null, gridPane,
346                   "Set Min/Max Plot Values", JOptionPane.DEFAULT_OPTION,
347                   JOptionPane.QUESTION_MESSAGE, null, window_options,
348                   window_options[0]);
349               if(select == 1)
350                 return;
351 
352               getAlgorithm().setUserMaxMin(true);
353               getAlgorithm().setUserMin((float) minValue.getValue());
354               getAlgorithm().setUserMax((float) maxValue.getValue());
355               ((BasePlot)Plot.this).setMin_value((float) minValue.getValue());
356               ((BasePlot)Plot.this).setMax_value((float) maxValue.getValue());
357               getAlgorithm().setScalingFlag(false);
358               repaint();
359             }
360           });
361         }
362 
363         popup.addSeparator();
364 
365         final JMenu max_window_size =
366               new JMenu("Maximum Window Size");
367         popup.add(max_window_size);
368 
369         final int[] window_sizes =
370         {
371           100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000,
372           200000, 500000, 1000000
373         };
374 
375         JMenuItem window_size_item;
376         for(int i = 0 ; i < window_sizes.length ; ++i)
377         {
378           final int size = i;
379           window_size_item = new JMenuItem(" " + window_sizes[i]);
380 
381           window_size_item.addActionListener(new ActionListener()
382           {
383             public void actionPerformed(ActionEvent actionEvent)
384             {
385               final int new_maximum = window_sizes[size];
386               if(new_maximum > window_changer.getMinimum())
387               {
388                 window_changer.setMaximum(new_maximum + SCROLL_NOB_SIZE);
389                 recalculate_flag = true;
390                 repaint();
391               }
392             }
393           });
394 
395           max_window_size.add(window_size_item);
396         }
397 
398         if(numPlots == 1 && getAlgorithm() instanceof BaseAlgorithm)
399         {
400           popup.addSeparator();
401 
402           final JMenu createFeaturesFrom = new JMenu("Create features from graph");
403           popup.add(createFeaturesFrom);
404 
405           final JMenuItem createFeaturesFromPeak =
406             new JMenuItem("peaks...");
407           createFeaturesFrom.add(createFeaturesFromPeak);
408 
409           createFeaturesFromPeak.addActionListener(new ActionListener()
410           {
411             public void actionPerformed(ActionEvent e)
412             {
413               calculateFeatures(true);
414             }
415           });
416 
417           final JMenuItem createFeaturesFromDip =
418             new JMenuItem("trough...");
419           createFeaturesFrom.add(createFeaturesFromDip);
420 
421           createFeaturesFromDip.addActionListener(new ActionListener()
422           {
423             public void actionPerformed(ActionEvent e)
424             {
425               calculateFeatures(false);
426             }
427           });
428         }
429 
430         ///
431         if(Plot.this instanceof BasePlot)
432         {
433           final JMenuItem showAverages =
434             new JMenuItem("Values and average(s) for selected range...");
435           popup.add(showAverages);
436 
437           showAverages.addActionListener(new ActionListener()
438           {
439             public void actionPerformed(ActionEvent e)
440             {
441               showAveragesForRange();
442             }
443           });
444         }
445 
446         final JSplitPane splitPane = getJSplitPane();
447         if(splitPane == null)
448         {
449           popup.addSeparator();
450           final JMenu graphHeight = new JMenu("Graph Height");
451           popup.add(graphHeight);
452           final JMenuItem smaller = new JMenuItem("smaller");
453           final JMenuItem larger = new JMenuItem("larger");
454           final JMenuItem setHeight = new JMenuItem("set...");
455 
456           graphHeight.add(smaller);
457           graphHeight.add(larger);
458           graphHeight.add(setHeight);
459 
460           smaller.addActionListener(new ActionListener()
461           {
462             public void actionPerformed(ActionEvent e)
463             {
464               rescale((int) (getSize().height * 0.9f));
465             }
466           });
467 
468           larger.addActionListener(new ActionListener()
469           {
470             public void actionPerformed(ActionEvent e)
471             {
472               rescale((int) (getSize().height * 1.1f));
473             }
474           });
475 
476           setHeight.addActionListener(new ActionListener()
477           {
478             public void actionPerformed(ActionEvent e)
479             {
480               final JTextField newGraphHgt = new JTextField(Integer
481                   .toString(getSize().height));
482               String window_options[] = { "Set Window Size", "Cancel" };
483               int select = JOptionPane.showOptionDialog(null, newGraphHgt,
484                   "Set Window Size", JOptionPane.DEFAULT_OPTION,
485                   JOptionPane.QUESTION_MESSAGE, null, window_options,
486                   window_options[0]);
487 
488               if(select == 1)
489                 return;
490               try
491               {
492                 rescale(Integer.parseInt(
493                     newGraphHgt.getText().trim()));
494               }
495               catch(NumberFormatException nfe)
496               {
497               }
498             }
499           });
500         }
501 
502         parent.add(popup);
503         popup.show(parent, event.getX(), event.getY());
504       }
505       else
506       {
507         final int point_x = event.getPoint().x;
508         final int point_y = event.getPoint().y;
509 
510         if(point_y > getLabelHeight())
511         {
512           cross_hair_position = point_x;
513           drag_start_position = point_x;
514         }
515         else
516           cancelCrossHairs();
517 
518         if(event.getClickCount() == 2)
519           fireDoubleClickEvent();
520         else
521           fireClickEvent();
522 
523         repaint();
524       }
525     }
526   };
527 
528   final MouseMotionListener mouse_motion_listener =
529       new MouseMotionAdapter()
530   {
531     public void mouseDragged(MouseEvent event)
532     {
533       if(isMenuTrigger(event))
534         return;
535 
536       final int point_x = event.getPoint().x;
537       final int point_y = event.getPoint().y;
538 
539       if(point_y > getLabelHeight())
540         cross_hair_position = point_x;
541       else
542         cancelCrossHairs();
543 
544       fireDragEvent();
545       repaint();
546     }
547   };
548 
549   /**
550    * Find JSplitPane parent container or null if it does not
551    * belong to one.
552    * @return
553    */
getJSplitPane()554   private JSplitPane getJSplitPane()
555   {
556     JComponent child = Plot.this;
557     JSplitPane splitPane = null;
558     int count = 0;
559     try
560     {
561       while(splitPane == null && count < 10)
562       {
563         JComponent plotParent = (JComponent) child.getParent();
564         if(plotParent instanceof JSplitPane)
565           splitPane = (JSplitPane) plotParent;
566         child = plotParent;
567         count++;
568       }
569     }
570     catch(Exception e){}
571     return splitPane;
572   }
573 
574   /**
575    * Reset graph height
576    * @param hgt
577    */
rescale(int hgt)578   private void rescale(int hgt)
579   {
580     setSize(getSize().width, hgt);
581 
582     if(Plot.this instanceof BasePlot)
583       BasePlot.HEIGHT = getSize().height;
584     else if(Plot.this instanceof FeaturePlot)
585       FeaturePlot.HEIGHT = getSize().height;
586 
587     offscreen = null;
588     revalidate();
589   }
590 
591   /**
592    *  Return true if and only if the given MouseEvent (a mouse press) should
593    *  pop up a JPopupMenu.
594    **/
isMenuTrigger(final MouseEvent event)595   private boolean isMenuTrigger(final MouseEvent event)
596   {
597     if(event.isPopupTrigger() ||
598       (event.getModifiers() & InputEvent.BUTTON3_MASK) != 0)
599       return true;
600     else
601       return false;
602   }
603 
604   /**
605    *  Call mouseClick() on each of the PlotMouseListener objects in the
606    *  listener list.
607    **/
fireClickEvent()608   private void fireClickEvent()
609   {
610     PlotMouseListener listener;
611     for(int i = 0; i < listener_list.size(); ++i)
612     {
613       listener = listener_list.elementAt(i);
614       listener.mouseClick(getPointPosition(cross_hair_position));
615     }
616   }
617 
618   /**
619    *  Call mouseDragged() on each of the PlotMouseListener objects in the
620    *  listener list.
621    **/
fireDragEvent()622   private void fireDragEvent()
623   {
624     PlotMouseListener listener;
625     for(int i = 0; i < listener_list.size(); ++i)
626     {
627       listener = listener_list.elementAt(i);
628       listener.mouseDrag(getPointPosition(drag_start_position),
629                          getPointPosition(cross_hair_position));
630     }
631   }
632 
633   /**
634    *  Call mouseDoubleClick() on each of the PlotMouseListener objects in the
635    *  listener list.
636    **/
fireDoubleClickEvent()637   private void fireDoubleClickEvent()
638   {
639     PlotMouseListener listener;
640     for(int i = 0; i < listener_list.size(); ++i)
641     {
642       listener = listener_list.elementAt(i);
643       listener.mouseDoubleClick(getPointPosition(cross_hair_position));
644     }
645   }
646 
647   /**
648    *  Adds the given listener to receive mouse events from this object.
649    *  @param l the listener.
650    **/
addPlotMouseListener(final PlotMouseListener listener)651   public void addPlotMouseListener(final PlotMouseListener listener)
652   {
653     listener_list.addElement(listener);
654   }
655 
656   /**
657    *  Removes the given listener from the list of those objects that receive
658    *  mouse events from this object.
659    *  @param l the listener.
660    **/
removePlotMouseListener(final PlotMouseListener listener)661   public void removePlotMouseListener(final PlotMouseListener listener)
662   {
663     listener_list.removeElement(listener);
664   }
665 
666   /**
667    *  The main paint function for the canvas.  An off screen image used for
668    *  double buffering when drawing the canvas.
669    *  @param g The Graphics object of the canvas.
670    **/
paintComponent(final Graphics g)671   protected void paintComponent(final Graphics g)
672   {
673     super.paintComponent(g);
674     if(!isVisible())
675       return;
676 
677     final int width  = getWidth() - window_changer.getWidth();
678     final int height = getHeight();
679 
680     // there is no point painting a zero width canvas
681     if(height <= 0 || width <= 0)
682       return;
683 
684     if(offscreen == null || lastPaintHeight != height)
685       offscreen = createImage(width, height);
686 
687     final Graphics og;
688     if(g instanceof SVGGraphics2D)
689       og = g;
690     else
691     {
692       og = offscreen.getGraphics();
693       og.setClip(0, 0, width, height);
694       og.setColor(Color.WHITE);
695       og.fillRect(0, 0, width, height);
696     }
697 
698     // Redraw the graph on the canvas using the algorithm from the
699     // constructor.
700 
701     if(lines == null && getAlgorithm() instanceof BaseAlgorithm)
702     {
703       final int get_values_return_count =
704        ((BaseAlgorithm)getAlgorithm()).getValueCount();
705 
706       if(getAlgorithm() instanceof UserDataAlgorithm)
707         lines = ((UserDataAlgorithm)getAlgorithm()).getLineAttributes();
708       if(lines == null)
709         lines = LineAttributes.init(get_values_return_count);
710     }
711 
712     numPlots = drawMultiValueGraph(og,lines);
713     drawLabels(og,numPlots);
714 
715     if( !(g instanceof SVGGraphics2D) )
716     {
717       g.drawImage(offscreen, 0, 0, null);
718       og.dispose();
719     }
720     lastPaintHeight = height;
721   }
722 
resetOffscreenImage()723   protected void resetOffscreenImage()
724   {
725     offscreen = null;
726   }
727 
728   /**
729    *  Return the canvas x position of the last click or -1 if the user hasn't
730    *  clicked anywhere yet.
731    **/
getCrossHairPosition()732   protected int getCrossHairPosition()
733   {
734     if(cross_hair_position >= getSize().width)
735       return -1;
736     else
737       return cross_hair_position;
738   }
739 
740   /**
741    *  Force this component to stop drawing crosshairs.
742    **/
cancelCrossHairs()743   protected void cancelCrossHairs()
744   {
745     cross_hair_position = -1;
746     drag_start_position = -1;
747   }
748 
749   /**
750    *  Draw the scale line at the bottom of the graph (used by FeaturePlot).
751    *  @param start The base on the left
752    *  @param end The base on the right
753    **/
drawScaleLine(final Graphics g, final int start, final int end)754   protected void drawScaleLine(final Graphics g,
755                                final int start, final int end)
756   {
757     final int hgt = getHeight();
758     final int scale_number_y_pos =  hgt - 1;
759     final float bases_per_pixel = 1.0F;
760 
761     final int possible_index_of_first_label = start / MINIMUM_LABEL_SPACING;
762     final int index_of_first_label;
763 
764     if(possible_index_of_first_label == 0)
765       index_of_first_label = 1;
766     else
767       index_of_first_label = possible_index_of_first_label;
768 
769     final int index_of_last_label = end / MINIMUM_LABEL_SPACING;
770     for(int i = index_of_first_label; i <= index_of_last_label; i++)
771     {
772       final int scale_number_x_pos =
773         (int)((i * MINIMUM_LABEL_SPACING - start) / bases_per_pixel);
774 
775       g.drawString(String.valueOf((int)(i * MINIMUM_LABEL_SPACING)),
776                    scale_number_x_pos + 2,
777                    scale_number_y_pos);
778 
779       g.drawLine(scale_number_x_pos, hgt - getScaleHeight() / 2,
780                  scale_number_x_pos, hgt - getScaleHeight());
781     }
782   }
783 
784   /**
785    *  Plot the given points onto a Graphics object.
786    *  @param min_value The minimum of the plot_values.
787    *  @param max_value The maximum of the plot_values.
788    *  @param step_size The current step size for this algorithm.  This is
789    *    never greater than window_size.
790    *  @param window_size The window size used in calculating plot_values.
791    *  @param total_unit_count The maximum number of residues/bases we can
792    *    show.  This is used to draw the scale line and to calculate the
793    *    distance (in pixels) between plot points.
794    *  @param start_position The distance from the edge of the canvas (measured
795    *    in residues/bases) to start drawing the plot.
796    *  @param plot_values The values to plot.
797    **/
drawPoints(final Graphics g, final float min_value, final float max_value, final int step_size, final int window_size, final int total_unit_count, final int start_position, final float [] plot_values, final int value_index, final int numberPlots, final boolean isWiggle, final boolean isBlast)798   protected void drawPoints(final Graphics g,
799                             final float min_value, final float max_value,
800                             final int step_size, final int window_size,
801                             final int total_unit_count,
802                             final int start_position,
803                             final float [] plot_values,
804                             final int value_index,
805                             final int numberPlots,
806                             final boolean isWiggle,
807                             final boolean isBlast)
808   {
809     final float residues_per_pixel =
810       (float) total_unit_count / getSize().width;
811 
812     // this is the height of the graph (slightly smaller than the canvas for
813     // ease of viewing).
814     final int graph_height = getSize().height -
815       getLabelHeight() -       // leave room for the algorithm name
816       getScaleHeight() -       // leave room for the scale
817       2;
818 
819     // too small to draw
820     if(graph_height < 5)
821       return;
822 
823     Color definedColours[] = null;
824     String plotType = null;
825     if(lines != null)
826     {
827       plotType = lines[value_index].getPlotType();
828 
829       int NUMBER_OF_SHADES = 254;
830       if(plotType.equals(LineAttributes.PLOT_TYPES[2]))
831       {
832         definedColours = makeColours(lines[value_index].getLineColour(),
833                                      NUMBER_OF_SHADES);
834       }
835     }
836 
837     final int number_of_values = plot_values.length;
838     int start_residue;
839     int end_residue;
840     int start_x;
841     int end_x;
842 
843     for(int i = 0; i<number_of_values - 1; ++i)
844     {
845       if( (isBlast || isWiggle) && plot_values[i] == 0)
846       {
847         if( !(isBlast && plotType.equals(LineAttributes.PLOT_TYPES[0])) )
848           continue;
849       }
850       start_residue = window_size / 2 + i * step_size + start_position;
851       start_x = (int)(start_residue / residues_per_pixel);
852 
853       if(isWiggle)
854       {
855         int span = ((UserDataAlgorithm)getAlgorithm()).getWiggleSpan(value_index);
856         end_residue   = start_residue + span;
857       }
858       else
859         end_residue   = start_residue + step_size;
860       end_x = (int)(end_residue / residues_per_pixel);
861 
862       // this is a number between 0.0 and 1.0
863       final float scaled_start_value =
864         (plot_values[i] - min_value) / (max_value - min_value);
865       final int start_y =
866         graph_height - (int)(scaled_start_value * graph_height) +
867         getLabelHeight() + 1;
868 
869       final float scaled_end_value =
870         (plot_values[i+1] - min_value) / (max_value - min_value);
871       final int end_y =
872         graph_height - (int)(scaled_end_value * graph_height) +
873         getLabelHeight() + 1;
874 
875       if(plotType == null ||
876          plotType.equals(LineAttributes.PLOT_TYPES[0]))
877       {
878         if(isWiggle)
879         {
880           g.drawLine(start_x, graph_height+getLabelHeight() + 1, start_x, start_y);
881           g.drawLine(start_x, start_y, end_x, start_y);
882           g.drawLine(end_x, graph_height+getLabelHeight() + 1, end_x, start_y);
883         }
884         else
885           g.drawLine(start_x, start_y, end_x, end_y);
886       }
887       else if(plotType.equals(LineAttributes.PLOT_TYPES[1]))
888       {
889         if(isWiggle)
890           g.fillRect(start_x, start_y, end_x-start_x, graph_height+getLabelHeight() + 1);
891         {
892           int xPoints[] = { start_x, end_x, end_x, start_x };
893           int yPoints[] = { start_y, end_y,
894               graph_height+getLabelHeight() + 1,
895               graph_height+getLabelHeight() + 1};
896           g.fillPolygon(xPoints, yPoints, 4);
897         }
898       }
899       else
900       {
901         int ytop = getLabelHeight() + 1 +
902                    (value_index*(graph_height/numberPlots));
903         int ybtm = (graph_height/numberPlots);
904 
905         // set color based on value
906         int colourIndex =
907           (int)(definedColours.length * 0.999 * scaled_start_value);
908 
909         if(colourIndex > definedColours.length - 1)
910           colourIndex = definedColours.length - 1;
911         else if (colourIndex < 0)
912           colourIndex = 0;
913 
914         g.setColor(definedColours[ colourIndex ]);
915         g.fillRect(start_x, ytop, end_x-start_x, ybtm);
916       }
917     }
918   }
919 
920   /**
921    * Generate the colours for heat map plots.
922    * @param col
923    * @param NUMBER_OF_SHADES
924    * @return
925    */
makeColours(Color col, int NUMBER_OF_SHADES)926   public static Color[] makeColours(Color col, int NUMBER_OF_SHADES)
927   {
928     Color definedColour[] = new Color[NUMBER_OF_SHADES];
929     for(int i = 0; i < NUMBER_OF_SHADES; ++i)
930     {
931       int R = col.getRed();
932       int G = col.getGreen();
933       int B = col.getBlue();
934       int scale = NUMBER_OF_SHADES-i;
935 
936       if((R+scale) <= 255)
937         R += scale;
938       else
939         R = 254;
940       if((G+scale) <= 255)
941         G += scale;
942       else
943         G = 254;
944       if((B+scale) <= 255)
945         B += scale;
946       else
947         B = 254;
948 
949       definedColour[i] = new Color(R,G,B);
950     }
951     return definedColour;
952   }
953 
954   /**
955    *  Redraw the graph on the canvas using the algorithm.
956    *  @param g The object to draw into.
957    **/
drawMultiValueGraph(final Graphics g, LineAttributes[] lines)958   protected abstract int drawMultiValueGraph(final Graphics g, LineAttributes[] lines);
959 
960   /**
961    *  Draw a line representing the average of the algorithm over the feature.
962    *  @param g The object to draw into.
963    *  @param min_value The minimum value of the function for the range we are
964    *    viewing
965    *  @param max_value The maximum value of the function for the range we are
966    *    viewing
967    **/
drawGlobalAverage(final Graphics g, final float min_value, final float max_value)968   protected void drawGlobalAverage(final Graphics g,
969                                     final float min_value,
970                                     final float max_value)
971   {
972     // if a heatmap do not show the average
973     if(!showAverage || (
974         lines != null && lines[0].getPlotType().equals(LineAttributes.PLOT_TYPES[2])))
975         return;
976 
977     final Float average = getAlgorithm().getAverage();
978 
979     if(average != null)
980     {
981       g.setColor(Color.gray);
982 
983       // this is the height of the graph (slightly smaller than the canvas for
984       // ease of viewing).
985       final int graph_height =
986         getSize().height - getFontHeight();
987 
988       // this is a number between 0.0 and 1.0
989       final float scaled_average =
990         (average.floatValue() - min_value) / (max_value - min_value);
991 
992       final int position =
993         graph_height -
994         (int)(scaled_average * graph_height) +
995         getFontHeight() + 1;
996 
997       g.drawLine(0, position,
998                  getSize().width, position);
999 
1000       final FontMetrics fm = g.getFontMetrics();
1001 
1002       final int width = getSize().width;
1003 
1004       final String average_string =
1005         String.valueOf(Math.round(average.floatValue() * 100.0) / 100.0);
1006 
1007       g.drawString(average_string,
1008                    width - fm.stringWidth(average_string) -
1009                    window_changer.getWidth() - 1,
1010                    position);
1011     }
1012   }
1013 
1014   /**
1015    *  Put the algorithm name in the top left corner of the canvas and the
1016    *  window size in the bottom left.
1017    *  @param g The object to draw into.
1018    **/
drawLabels(final Graphics g, final int numPlots)1019   private void drawLabels(final Graphics g, final int numPlots)
1020   {
1021     g.setColor(Color.black);
1022 
1023     String desc = getAlgorithm().getAlgorithmName() + "  Window size: " +
1024                   String.valueOf(window_changer.getValue());
1025 
1026     g.drawString(desc, 2, font_height);
1027 
1028     if(numPlots < 2 || numPlots > 10)
1029       return;
1030 
1031     final FontMetrics fm = g.getFontMetrics();
1032     int font_width = fm.stringWidth("2");
1033 
1034     int width = 0;
1035     for(LineAttributes ln : lines)
1036       width += ln.getLabelWidth(fm);
1037     width = getWidth() - window_changer.getWidth() - width;
1038 
1039     g.translate(width,0);
1040     ((BaseAlgorithm)getAlgorithm()).drawLegend(g,font_height,
1041                                                font_width,lines, numPlots);
1042     g.translate(-width,0);
1043   }
1044 
1045   /**
1046    *  The method converts the min_value and max_value to String objects and
1047    *  then draws them onto the canvas.  The min_value is drawn at the bottom
1048    *  right, max_value at the top right.
1049    **/
drawMinMax(final Graphics g, final float min_value, final float max_value)1050   protected void drawMinMax(final Graphics g,
1051                             final float min_value, final float max_value)
1052   {
1053     g.setColor(Color.black);
1054 
1055     final int width  = getWidth() - window_changer.getWidth();
1056     final int height = getHeight();
1057 
1058     g.drawLine(0, height - getScaleHeight(),
1059                width, height - getScaleHeight());
1060 
1061     g.drawLine(0, getLabelHeight(),
1062                width, getLabelHeight());
1063 
1064     final FontMetrics fm = g.getFontMetrics();
1065 
1066     final String min_string =
1067       String.valueOf(((int)(min_value * 100)) / 100.0);
1068 
1069     g.drawString(min_string,
1070                  width - fm.stringWidth(min_string) - 1,
1071                  height - 1 - getScaleHeight());
1072 
1073     final String max_string =
1074       String.valueOf(((int)(max_value * 100)) / 100.0);
1075 
1076     g.drawString(max_string,
1077                  width - fm.stringWidth(max_string) - 1,
1078                  1 + getFontHeight() * 2);
1079   }
1080 
1081   /**
1082    *  Draw a vertical line at the given position.
1083    *  @param label The label to use on the crosshair
1084    *  @param label_pos The position on the line at which the label should be
1085    *    drawn (0 is nearest the top).
1086    **/
drawCrossHair(final Graphics g, final int x_position, final String label, final int label_pos)1087   protected void drawCrossHair(final Graphics g, final int x_position,
1088                                final String label, final int label_pos)
1089   {
1090     if(x_position >= 0)
1091     {
1092       g.drawLine(x_position, getLabelHeight(),
1093                   x_position, getSize().height);
1094 
1095       g.drawString(label, x_position + 2,
1096                    getFontHeight() * (2 + label_pos) + 2);
1097     }
1098   }
1099 
1100   /**
1101    *  Return the amount of vertical space (in pixels) to use for the scale.
1102    **/
getScaleHeight()1103   protected int getScaleHeight()
1104   {
1105     if(draw_scale)
1106       return getFontHeight() + 2;
1107     else
1108       return 0;
1109   }
1110 
1111   /**
1112    *  Return the height in algorithm name and label line (returns the font
1113    *  height plus a small amount).
1114    **/
getLabelHeight()1115   protected int getLabelHeight()
1116   {
1117     return getFontHeight() + 2;
1118   }
1119 
1120   /**
1121    *  Return the height in pixels of the current font.
1122    **/
getFontHeight()1123   private int getFontHeight()
1124   {
1125     return font_height;
1126   }
1127 
1128   /**
1129    *  Used to get the Y coordinate for the tooltip text.
1130    *  @param step_size The current step size for this algorithm.  This is
1131    *    never greater than window_size.
1132    *  @param window_size The window size used in calculating plot_values.
1133    *  @param start_position The distance from the edge of the canvas (measured
1134    *    in residues/bases) to start drawing the plot.
1135    *  @param plot_values The values to plot.
1136    *  @param base_pos    The base (from getXCoordinate) position.
1137    **/
getYCoordinate( final int step_size, final int window_size, final int start_position, final float plot_values[], int base_pos)1138   protected float getYCoordinate(
1139       final int step_size, final int window_size,
1140       final int start_position,
1141       final float plot_values[], int base_pos)
1142   {
1143     int ypos = (int)((base_pos - start_position - (window_size/2))/step_size);
1144     if(ypos < 0)
1145       ypos = 0;
1146     else if(ypos > plot_values.length-1)
1147       ypos = plot_values.length-1;
1148 
1149     return plot_values[ypos];
1150   }
1151 }
1152