Skip to content
Snippets Groups Projects
Plot.java 34.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • tjc's avatar
    tjc committed
    /* Plot.java
     *
     * created: Thu Dec 17 1998
     *
     * This file is part of Artemis
     *
     * Copyright (C) 1998,1999,2000  Genome Research Limited
     *
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of the GNU General Public License
     * as published by the Free Software Foundation; either version 2
     * of the License, or (at your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     *
    
     * $Header: //tmp/pathsoft/artemis/uk/ac/sanger/artemis/components/Plot.java,v 1.26 2009-08-18 09:01:44 tjc Exp $
    
    tjc's avatar
    tjc committed
     **/
    
    package uk.ac.sanger.artemis.components;
    
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.Options;
    
    import uk.ac.sanger.artemis.circular.TextFieldFloat;
    
    tjc's avatar
    tjc committed
    import uk.ac.sanger.artemis.plot.*;
    
    import java.awt.*;
    import java.awt.event.*;
    
    
    tjc's avatar
    tjc committed
    import javax.swing.JMenu;
    
    tjc's avatar
    tjc committed
    import javax.swing.JPanel;
    import javax.swing.JComponent;
    
    import javax.swing.JLabel;
    
    import javax.swing.JSplitPane;
    
    tjc's avatar
    tjc committed
    import javax.swing.JTextField;
    import javax.swing.JOptionPane;
    
    tjc's avatar
    tjc committed
    import javax.swing.JCheckBoxMenuItem;
    import javax.swing.JMenuItem;
    import javax.swing.JScrollBar;
    import javax.swing.JPopupMenu;
    
    tjc's avatar
    tjc committed
    
    /**
     *  This class implements a simple plot component.
     *
     *  @author Kim Rutherford
    
     *  @version $Id: Plot.java,v 1.26 2009-08-18 09:01:44 tjc Exp $
    
    tjc's avatar
    tjc committed
     **/
    
    
    tjc's avatar
    tjc committed
    public abstract class Plot extends JPanel 
    {
    
    
    tjc's avatar
    tjc committed
      /** scroll bar for changing the window size. */
    
    tjc's avatar
    tjc committed
      private JScrollBar window_changer = null;
    
    
    tjc's avatar
    tjc committed
      /** height of the font used in this component. */
    
    tjc's avatar
    tjc committed
      private int font_height;
    
    
    tjc's avatar
    tjc committed
      /** off screen image used for double buffering when drawing */
    
    tjc's avatar
    tjc committed
      private Image offscreen;
    
      /**
       *  The object that will generate the value we plot in this component.
       **/
      private Algorithm algorithm;
    
      /**
       *  If true then a scale line will be drawn at the bottom of the graph when
    
    tjc's avatar
    tjc committed
       *  drawScaleLine() is called.
    
    tjc's avatar
    tjc committed
       **/
      private boolean draw_scale;
    
      /**
    
    tjc's avatar
    tjc committed
       *  Set to true if drawMultiValueGraph() should call recalculateValues().
       *  It is reset to false by recalculateValues().
    
    tjc's avatar
    tjc committed
       **/
      protected boolean recalculate_flag = true;
    
      /**
       *  The x position of the last click or -1 if the user hasn't clicked
       *  anywhere yet or the user clicked outside the graph.
       **/
      private int cross_hair_position = -1;
    
      /**
       *  The x position of the start of the last mouse drag or -1 if the user
       *  hasn't clicked anywhere yet or the user clicked outside the graph.
       **/
      private int drag_start_position = -1;
    
      /**
       *  A vector of those objects listening for PlotMouse events.
       **/
    
    tjc's avatar
    tjc committed
      final private java.util.Vector listener_list = new java.util.Vector();
    
      /**
       *  Recalculate the values all the state that is used for drawing the plot
       **/
      protected abstract void recalculateValues();
    
      /**
       *  Get the position in the Feature or Sequence of the given x canvas
       *  position.  This is the label used when the user clicks the mouse in on
       *  the canvas (see drawCrossHair()).
       **/
      protected abstract int getPointPosition(final int canvas_x_position);
    
      protected abstract void calculateFeatures(boolean fromPeak);  
    
    tjc's avatar
    tjc committed
    
    
      protected abstract void showAveragesForRange();
      
    
      /** number of graph lines to be drawn */
      private int numPlots;
    
      /** colour array for graph drawing */
    
      protected LineAttributes lines[];
    
      
      private int lastPaintHeight = getHeight();
    
    tjc's avatar
    tjc committed
      /**
       *  Create a new plot component.
       *  @param algorithm The object that will generate the values we plot in
       *    this component.
       *  @param draw_scale If true then a scale line will be drawn at the bottom
       *    of the graph.
       **/
    
    tjc's avatar
    tjc committed
      public Plot(Algorithm algorithm, boolean draw_scale) 
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        this.algorithm = algorithm;
        this.draw_scale = draw_scale;
    
    
    tjc's avatar
    tjc committed
        final Font font = Options.getOptions().getFont();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        setFont(font);
        FontMetrics fm = getFontMetrics(font);
    
    tjc's avatar
    tjc committed
        font_height = fm.getHeight();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        setLayout(new BorderLayout());
    
    tjc's avatar
    tjc committed
    
        final int MAX_WINDOW;
    
    
    tjc's avatar
    tjc committed
        if(getAlgorithm().getDefaultMaxWindowSize() != null) 
    
    tjc's avatar
    tjc committed
          MAX_WINDOW = getAlgorithm().getDefaultMaxWindowSize().intValue();
        else 
    
    tjc's avatar
    tjc committed
          MAX_WINDOW = 500;
    
        final int MIN_WINDOW;
    
    
    tjc's avatar
    tjc committed
        if(getAlgorithm().getDefaultMinWindowSize() != null) 
          MIN_WINDOW = getAlgorithm().getDefaultMinWindowSize().intValue();
    
    tjc's avatar
    tjc committed
        else 
    
    tjc's avatar
    tjc committed
          MIN_WINDOW = 5;
    
        final int START_WINDOW;
    
    
    tjc's avatar
    tjc committed
        if(getAlgorithm().getDefaultWindowSize() == null) 
    
    tjc's avatar
    tjc committed
          START_WINDOW = 10;
    
    tjc's avatar
    tjc committed
        else 
          START_WINDOW = getAlgorithm().getDefaultWindowSize().intValue();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        window_changer = new JScrollBar(Scrollbar.VERTICAL);
        window_changer.setValues(START_WINDOW, SCROLL_NOB_SIZE,
                                 MIN_WINDOW, MAX_WINDOW + SCROLL_NOB_SIZE);
        if(MAX_WINDOW >= 50) 
          window_changer.setBlockIncrement(MAX_WINDOW/50);
    
    tjc's avatar
    tjc committed
        else 
    
    tjc's avatar
    tjc committed
          window_changer.setBlockIncrement(1);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        window_changer.addAdjustmentListener(new AdjustmentListener()
        {
          public void adjustmentValueChanged(AdjustmentEvent e) 
          {
    
    tjc's avatar
    tjc committed
            recalculate_flag = true;
    
    tjc's avatar
    tjc committed
            repaint();
    
    tjc's avatar
    tjc committed
          }
        });
    
    
    tjc's avatar
    tjc committed
        addComponentListener(new ComponentAdapter() 
        {
          public void componentShown(ComponentEvent e) 
          {
    
    tjc's avatar
    tjc committed
            recalculate_flag = true;
    
    tjc's avatar
    tjc committed
            repaint();
    
    tjc's avatar
    tjc committed
          }
        });
    
    
    tjc's avatar
    tjc committed
    //  setBackground(Color.white);
    
        add(window_changer, "East");
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        addMouseListener(mouse_listener);
        addMouseMotionListener(mouse_motion_listener);
    
    tjc's avatar
    tjc committed
      }
    
      final int SCROLL_NOB_SIZE = 10;
    
      /**
       *  Return the algorithm that was passed to the constructor.
       **/
    
    tjc's avatar
    tjc committed
      public Algorithm getAlgorithm() 
      {
    
    tjc's avatar
    tjc committed
        return algorithm;
      }
    
      /**
       *  Return the current value of the window size, as set by the
       *  window_changer scrollbar.
       **/
    
    tjc's avatar
    tjc committed
      public int getWindowSize()
      {
    
    tjc's avatar
    tjc committed
        return window_changer.getValue();
    
    tjc's avatar
    tjc committed
      }
    
    
    tjc's avatar
    tjc committed
      final MouseListener mouse_listener = new MouseAdapter()
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        /**
         *  Listen for mouse press events so that we can do a popup menu and a
         *  crosshair.
         **/
        public void mousePressed(MouseEvent event) 
    
    tjc's avatar
    tjc committed
        {
    
    tjc's avatar
    tjc committed
          if(event.isPopupTrigger() || event.isMetaDown()) 
    
    tjc's avatar
    tjc committed
          {
    
    tjc's avatar
    tjc committed
            final JComponent parent = (JComponent)event.getSource();
            final JPopupMenu popup  = new JPopupMenu("Plot Options");
    
    tjc's avatar
    tjc committed
    
    
            // configure colours for multiple graph plots
    
            final JMenuItem config = new JMenuItem("Configure...");
            config.addActionListener(new ActionListener()
    
              public void actionPerformed(ActionEvent _)
    
                lines = LineAttributes.configurePlots(numPlots, lines, Plot.this);
              }
            });
            popup.add(config);
    
    
    
            final JMenuItem setScale = new JMenuItem("Set the Window Size...");
    
    tjc's avatar
    tjc committed
            setScale.addActionListener(new ActionListener()
            {
              public void actionPerformed(ActionEvent _)
              {
                final JTextField newWinSize = new JTextField(Integer.toString(getWindowSize()));
                String window_options[] = { "Set Window Size", "Cancel" };
                int select = JOptionPane.showOptionDialog(null,
                                            newWinSize,
                                            "Set Window Size",
                                             JOptionPane.DEFAULT_OPTION,
                                             JOptionPane.QUESTION_MESSAGE,
                                             null, window_options, window_options[0]);
                final int value;
                try
                {
                  value = Integer.parseInt(newWinSize.getText().trim());
                }
                catch(NumberFormatException nfe)
                {
                  return;
                }
                if(value > window_changer.getMaximum() ||
                   value < window_changer.getMinimum())
                {
                  window_options[0] = "Continue";
                  select = JOptionPane.showOptionDialog(null,
                                            "Value selected: " + value +
                                            " is outside the range\n"+
                                            " Min: "+window_changer.getMinimum() +
                                            " Max: "+window_changer.getMaximum(),
                                            "Set Window Size",
                                             JOptionPane.DEFAULT_OPTION,
                                             JOptionPane.WARNING_MESSAGE,
                                             null, window_options, window_options[1]);
                  if(select == 1)
                    return;
    
                  if(value > window_changer.getMaximum())
                    window_changer.setMaximum(value+10);
                  else
                    window_changer.setMinimum(value);
                }
    
                if(select == 0)
                {
                  recalculate_flag = true;
                  window_changer.setValue(value);
                  repaint();
                }
              }
            });
            popup.add(setScale);
    
    
    tjc's avatar
    tjc committed
            final JCheckBoxMenuItem scaling_toggle =
              new JCheckBoxMenuItem("Scaling");
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            scaling_toggle.setState(getAlgorithm().scalingFlag());
            scaling_toggle.addItemListener(new ItemListener() 
    
    tjc's avatar
    tjc committed
            {
    
    tjc's avatar
    tjc committed
              public void itemStateChanged(ItemEvent _) 
    
    tjc's avatar
    tjc committed
              {
    
    tjc's avatar
    tjc committed
                getAlgorithm().setScalingFlag(scaling_toggle.getState());
                recalculate_flag = true;
                repaint();
              }
            });
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            popup.add(scaling_toggle);
    
            
            
            if(Plot.this instanceof BasePlot)
            {
              final JMenuItem showMinMaxValues =
                new JMenuItem("Set Min/Max Values...");
              popup.add(showMinMaxValues);
              showMinMaxValues.addActionListener(new ActionListener() 
              {
                public void actionPerformed(ActionEvent e) 
                {
                  JPanel gridPane = new JPanel(new GridLayout(2,2));
                  TextFieldFloat minValue = new TextFieldFloat();
                  minValue.setValue( ((BasePlot)Plot.this).getMin_value() );
                  gridPane.add(new JLabel("Min:"));
                  gridPane.add(minValue);
                  
                  TextFieldFloat maxValue = new TextFieldFloat();
                  maxValue.setValue( ((BasePlot)Plot.this).getMax_value() );
                  gridPane.add(new JLabel("Max:"));
                  gridPane.add(maxValue);
    
                  String window_options[] = { "Set", "Cancel" };
                  int select = JOptionPane.showOptionDialog(null, gridPane,
                      "Set Min/Max Plot Values", JOptionPane.DEFAULT_OPTION,
                      JOptionPane.QUESTION_MESSAGE, null, window_options,
                      window_options[0]);
                  if(select == 1)
                    return;
                  
                  getAlgorithm().setUserMaxMin(true);
                  getAlgorithm().setUserMin((float) minValue.getValue());
                  getAlgorithm().setUserMax((float) maxValue.getValue());
                  ((BasePlot)Plot.this).setMin_value((float) minValue.getValue());
                  ((BasePlot)Plot.this).setMax_value((float) maxValue.getValue());
                  getAlgorithm().setScalingFlag(false);
                  repaint();
                }
              });
            }
            
            
    
    tjc's avatar
    tjc committed
            popup.addSeparator();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            final JMenu max_window_size =
                  new JMenu("Maximum Window Size");
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            popup.add(max_window_size);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            final int[] window_sizes = 
            {
              100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000,
              200000, 500000, 1000000
            };
    
    tjc's avatar
    tjc committed
    
    
    
    tjc's avatar
    tjc committed
            JMenuItem window_size_item;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            for(int i = 0 ; i < window_sizes.length ; ++i) 
            {
              final int size = i;
              window_size_item = new JMenuItem(" " + window_sizes[i]);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
              window_size_item.addActionListener(new ActionListener() 
              {
                public void actionPerformed(ActionEvent _) 
                {
                  final int new_maximum = window_sizes[size];
                  if(new_maximum > window_changer.getMinimum()) 
    
    tjc's avatar
    tjc committed
                  {
    
    tjc's avatar
    tjc committed
                    window_changer.setMaximum(new_maximum + SCROLL_NOB_SIZE);
                    recalculate_flag = true;
                    repaint();
                  }
    
    tjc's avatar
    tjc committed
                }
    
    tjc's avatar
    tjc committed
              });
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
              max_window_size.add(window_size_item);
            }
            
            if(numPlots == 1 && getAlgorithm() instanceof BaseAlgorithm)
            {
              popup.addSeparator();
              
    
              final JMenu createFeaturesFrom = new JMenu("Create features from graph");
              popup.add(createFeaturesFrom);
              
              final JMenuItem createFeaturesFromPeak =
                new JMenuItem("peaks...");
              createFeaturesFrom.add(createFeaturesFromPeak);
              
              createFeaturesFromPeak.addActionListener(new ActionListener() 
    
    tjc's avatar
    tjc committed
              {
                public void actionPerformed(ActionEvent e) 
                {
    
                  calculateFeatures(true);
                }
              });
              
              final JMenuItem createFeaturesFromDip =
                new JMenuItem("trough...");
              createFeaturesFrom.add(createFeaturesFromDip);
              
              createFeaturesFromDip.addActionListener(new ActionListener() 
              {
                public void actionPerformed(ActionEvent e) 
                {
                  calculateFeatures(false);
    
    tjc's avatar
    tjc committed
            }
    
            if(Plot.this instanceof BasePlot)
    
            { 
              final JMenuItem showAverages =
                new JMenuItem("Values and average(s) for selected range...");
    
              popup.add(showAverages);
            
    
              showAverages.addActionListener(new ActionListener() 
    
                public void actionPerformed(ActionEvent e) 
                {
                  showAveragesForRange();
                }
              });
            }
    
    tjc's avatar
    tjc committed
    
    
            final JSplitPane splitPane = getJSplitPane();
    
            if(splitPane == null)
    
              popup.addSeparator();
              final JMenu graphHeight = new JMenu("Graph Height");
              popup.add(graphHeight);
              final JMenuItem smaller = new JMenuItem("smaller");
              final JMenuItem larger = new JMenuItem("larger");
              final JMenuItem setHeight = new JMenuItem("set...");
    
              graphHeight.add(smaller);
              graphHeight.add(larger);
              graphHeight.add(setHeight);
    
              smaller.addActionListener(new ActionListener()
    
                public void actionPerformed(ActionEvent e)
                {
                  Dimension d = getSize();
                  rescale((int) (d.height * 0.9f));
                }
              });
    
              larger.addActionListener(new ActionListener()
              {
                public void actionPerformed(ActionEvent e)
                {
                  Dimension d = getSize();
                  rescale((int) (d.height * 1.1f));
                }
              });
    
              setHeight.addActionListener(new ActionListener()
    
                public void actionPerformed(ActionEvent e)
    
                  final JTextField newGraphHgt = new JTextField(Integer
                      .toString(getSize().height));
                  String window_options[] = { "Set Window Size", "Cancel" };
                  int select = JOptionPane.showOptionDialog(null, newGraphHgt,
                      "Set Window Size", JOptionPane.DEFAULT_OPTION,
                      JOptionPane.QUESTION_MESSAGE, null, window_options,
                      window_options[0]);
    
                  if(select == 1)
                    return;
    
                  try
                  {
                    final int value = Integer
                        .parseInt(newGraphHgt.getText().trim());
                    rescale(value);
                  }
                  catch(NumberFormatException nfe)
                  {
                  }
    
    tjc's avatar
    tjc committed
            parent.add(popup);
            popup.show(parent, event.getX(), event.getY());
          } 
          else 
    
    tjc's avatar
    tjc committed
          {
            final int point_x = event.getPoint().x;
            final int point_y = event.getPoint().y;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            if(point_y > getLabelHeight()) 
    
    tjc's avatar
    tjc committed
            {
    
    tjc's avatar
    tjc committed
              cross_hair_position = point_x;
    
    tjc's avatar
    tjc committed
              drag_start_position = point_x;
            } 
            else
    
    tjc's avatar
    tjc committed
              cancelCrossHairs();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
            if(event.getClickCount() == 2) 
              fireDoubleClickEvent();
            else
              fireClickEvent();
    
            repaint();
    
    tjc's avatar
    tjc committed
          }
    
    tjc's avatar
    tjc committed
        }
      };
    
      final MouseMotionListener mouse_motion_listener =
          new MouseMotionAdapter() 
      {
        public void mouseDragged(MouseEvent event) 
        {
          if(isMenuTrigger(event))
            return;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          final int point_x = event.getPoint().x;
          final int point_y = event.getPoint().y;
    
          if(point_y > getLabelHeight()) 
            cross_hair_position = point_x;
          else 
            cancelCrossHairs();
    
          fireDragEvent();
          repaint();
        }
      };
    
    tjc's avatar
    tjc committed
    
    
      /**
       * Find JSplitPane parent container or null if it does not
       * belong to one.
       * @return
       */
      private JSplitPane getJSplitPane()
      {
        JComponent child = Plot.this;
        JSplitPane splitPane = null;
        int count = 0;
        try
        {
          while(splitPane == null && count < 10)
          {
            JComponent plotParent = (JComponent) child.getParent();
            if(plotParent instanceof JSplitPane)
              splitPane = (JSplitPane) plotParent;
            child = plotParent;
            count++;
          }
        }
        catch(Exception e){}
        return splitPane;
      }
    
      /**
       * Reset graph height
       * @param hgt
       */
    
    tjc's avatar
    tjc committed
      private void rescale(int hgt)
      {
        setSize(getSize().width, hgt);
        
        if(Plot.this instanceof BasePlot)
          BasePlot.HEIGHT = getSize().height;
        else if(Plot.this instanceof FeaturePlot)
          FeaturePlot.HEIGHT = getSize().height;
        
        offscreen = null;
        revalidate();
      }
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
      /**
       *  Return true if and only if the given MouseEvent (a mouse press) should
       *  pop up a JPopupMenu.
       **/
      private boolean isMenuTrigger(final MouseEvent event) 
      {
        if(event.isPopupTrigger() ||
          (event.getModifiers() & InputEvent.BUTTON3_MASK) != 0) 
          return true;
        else 
          return false;
      }
    
    
    tjc's avatar
    tjc committed
      /**
    
    tjc's avatar
    tjc committed
       *  Call mouseClick() on each of the PlotMouseListener objects in the
    
    tjc's avatar
    tjc committed
       *  listener list.
       **/
    
    tjc's avatar
    tjc committed
      private void fireClickEvent() 
      {
    
    tjc's avatar
    tjc committed
        PlotMouseListener listener;
        for(int i = 0; i < listener_list.size(); ++i)
    
    tjc's avatar
    tjc committed
        {
    
    tjc's avatar
    tjc committed
          listener = (PlotMouseListener)listener_list.elementAt(i);
    
    tjc's avatar
    tjc committed
          listener.mouseClick(getPointPosition(cross_hair_position));
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
    
    tjc's avatar
    tjc committed
       *  Call mouseDragged() on each of the PlotMouseListener objects in the
    
    tjc's avatar
    tjc committed
       *  listener list.
       **/
    
    tjc's avatar
    tjc committed
      private void fireDragEvent()
      {
    
    tjc's avatar
    tjc committed
        PlotMouseListener listener;
    
    tjc's avatar
    tjc committed
        for(int i = 0; i < listener_list.size(); ++i) 
        {
    
    tjc's avatar
    tjc committed
          listener = (PlotMouseListener)listener_list.elementAt(i);
    
    tjc's avatar
    tjc committed
          listener.mouseDrag(getPointPosition(drag_start_position),
                             getPointPosition(cross_hair_position));
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
    
    tjc's avatar
    tjc committed
       *  Call mouseDoubleClick() on each of the PlotMouseListener objects in the
    
    tjc's avatar
    tjc committed
       *  listener list.
       **/
    
    tjc's avatar
    tjc committed
      private void fireDoubleClickEvent() 
      {
    
    tjc's avatar
    tjc committed
        PlotMouseListener listener;
    
    tjc's avatar
    tjc committed
        for(int i = 0; i < listener_list.size(); ++i) 
        {
    
    tjc's avatar
    tjc committed
          listener = (PlotMouseListener)listener_list.elementAt(i);
    
    tjc's avatar
    tjc committed
          listener.mouseDoubleClick(getPointPosition(cross_hair_position));
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Adds the given listener to receive mouse events from this object.
       *  @param l the listener.
       **/
    
    tjc's avatar
    tjc committed
      public void addPlotMouseListener(final PlotMouseListener listener) 
      {
        listener_list.addElement(listener);
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Removes the given listener from the list of those objects that receive
       *  mouse events from this object.
       *  @param l the listener.
       **/
    
    tjc's avatar
    tjc committed
      public void removePlotMouseListener(final PlotMouseListener listener) 
      {
        listener_list.removeElement(listener);
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  The main paint function for the canvas.  An off screen image used for
       *  double buffering when drawing the canvas.
       *  @param g The Graphics object of the canvas.
       **/
    
    tjc's avatar
    tjc committed
      protected void paintComponent(final Graphics g) 
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        super.paintComponent(g);
    
    tjc's avatar
    tjc committed
        if(!isVisible()) 
    
    tjc's avatar
    tjc committed
          return;
    
    
    tjc's avatar
    tjc committed
        final int width  = getWidth() - window_changer.getWidth();
        final int height = getHeight();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        // there is no point painting a zero width canvas
        if(height <= 0 || width <= 0) 
    
    tjc's avatar
    tjc committed
          return;
    
    
        if(offscreen == null || lastPaintHeight != height)
    
    tjc's avatar
    tjc committed
          offscreen = createImage(width, height);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        Graphics og = offscreen.getGraphics();
        og.setClip(0, 0, width, height);
    
        og.setColor(Color.WHITE);
    
    tjc's avatar
    tjc committed
        og.fillRect(0, 0, width, height);
    
    tjc's avatar
    tjc committed
    
        // Redraw the graph on the canvas using the algorithm from the
        // constructor.
    
    
        if(lines == null && getAlgorithm() instanceof BaseAlgorithm)
        {
          final int get_values_return_count =
           ((BaseAlgorithm)getAlgorithm()).getValueCount();
    
          
          if(getAlgorithm() instanceof UserDataAlgorithm)
            lines = ((UserDataAlgorithm)getAlgorithm()).getLineAttributes();
          if(lines == null)
            lines = LineAttributes.init(get_values_return_count);
    
        }
        
        numPlots = drawMultiValueGraph(og,lines);
    
        drawLabels(og,numPlots);
    
    tjc's avatar
    tjc committed
        g.drawImage(offscreen, 0, 0, null);
        og.dispose();
    
        lastPaintHeight = height;
    
    tjc's avatar
    tjc committed
      }
    
    
      protected void resetOffscreenImage()
      {
        offscreen = null;
      }
    
    
    tjc's avatar
    tjc committed
      /**
       *  Return the canvas x position of the last click or -1 if the user hasn't
       *  clicked anywhere yet.
       **/
    
    tjc's avatar
    tjc committed
      protected int getCrossHairPosition() 
      {
    
    tjc's avatar
    tjc committed
        if(cross_hair_position >= getSize().width) 
    
    tjc's avatar
    tjc committed
          return -1;
    
    tjc's avatar
    tjc committed
        else 
    
    tjc's avatar
    tjc committed
          return cross_hair_position;
      }
    
      /**
       *  Force this component to stop drawing crosshairs.
       **/
    
    tjc's avatar
    tjc committed
      protected void cancelCrossHairs() 
      {
    
    tjc's avatar
    tjc committed
        cross_hair_position = -1;
        drag_start_position = -1;
      }
    
      // the minimum distance in pixels between the labels
      private final static int MINIMUM_LABEL_SPACING = 50;
    
      /**
       *  Draw the scale line at the bottom of the graph.
       *  @param start The base on the left
       *  @param end The base on the right
       **/
    
    tjc's avatar
    tjc committed
      protected void drawScaleLine(final Graphics g,
                                   final int start, final int end) 
      {
    
    tjc's avatar
    tjc committed
        final int width  = getWidth() - window_changer.getWidth();
        final int height = getHeight();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        final int scale_number_y_pos =  height - 1;
    
    tjc's avatar
    tjc committed
    
        final float bases_per_pixel = 1.0F;
    
        // set the spacing so that the labels are at multiples of 10
        final int base_label_spacing = MINIMUM_LABEL_SPACING;
    
        final int label_spacing = (int)(base_label_spacing / bases_per_pixel);
    
        final int possible_index_of_first_label = start / base_label_spacing;
    
        final int index_of_first_label;
    
    
    tjc's avatar
    tjc committed
        if(possible_index_of_first_label == 0) 
    
    tjc's avatar
    tjc committed
          index_of_first_label = 1;
    
    tjc's avatar
    tjc committed
        else 
    
    tjc's avatar
    tjc committed
          index_of_first_label = possible_index_of_first_label;
    
        final int index_of_last_label = end / base_label_spacing;
    
    
    tjc's avatar
    tjc committed
        String label_string;
        for(int i = index_of_first_label; i <= index_of_last_label; i++)
    
    tjc's avatar
    tjc committed
        {
    
    tjc's avatar
    tjc committed
          label_string = String.valueOf((int)(i * base_label_spacing));
    
    tjc's avatar
    tjc committed
    
          final int scale_number_x_pos =
    
    tjc's avatar
    tjc committed
            (int)((i * base_label_spacing - start) / bases_per_pixel);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          g.drawString(label_string,
                       scale_number_x_pos + 2,
                       scale_number_y_pos);
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          g.drawLine(scale_number_x_pos, height - getScaleHeight() / 2,
                     scale_number_x_pos, height - getScaleHeight());
    
    tjc's avatar
    tjc committed
        }
      }
    
      /**
       *  Plot the given points onto a Graphics object.
       *  @param min_value The minimum of the plot_values.
       *  @param max_value The maximum of the plot_values.
       *  @param step_size The current step size for this algorithm.  This is
       *    never greater than window_size.
       *  @param window_size The window size used in calculating plot_values.
       *  @param total_unit_count The maximum number of residues/bases we can
       *    show.  This is used to draw the scale line and to calculate the
       *    distance (in pixels) between plot points.
       *  @param start_position The distance from the edge of the canvas (measured
       *    in residues/bases) to start drawing the plot.
       *  @param plot_values The values to plot.
       **/
    
    tjc's avatar
    tjc committed
      protected void 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) 
    
    tjc's avatar
    tjc committed
      {
    
    tjc's avatar
    tjc committed
        final float residues_per_pixel =
    
    tjc's avatar
    tjc committed
          (float) total_unit_count / getSize().width;
    
    tjc's avatar
    tjc committed
    
        // this is the height of the graph (slightly smaller than the canvas for
        // ease of viewing).
    
    tjc's avatar
    tjc committed
        final int graph_height = getSize().height -
          getLabelHeight() -       // leave room for the algorithm name
          getScaleHeight() -       // leave room for the scale
    
    tjc's avatar
    tjc committed
          2;
    
    
    tjc's avatar
    tjc committed
        // too small to draw
        if(graph_height < 5) 
    
    tjc's avatar
    tjc committed
          return;
    
    
        Color definedColours[] = null;
        String plotType = null;
        if(lines != null)
        {
          plotType = lines[value_index].getPlotType();
        
    
          int NUMBER_OF_SHADES = 254;
    
          if(plotType.equals(LineAttributes.PLOT_TYPES[2]))
          {
            definedColours = makeColours(lines[value_index].getLineColour(),
                                         NUMBER_OF_SHADES);
          }
        }
    
    
    tjc's avatar
    tjc committed
        final int number_of_values = plot_values.length;
    
    tjc's avatar
    tjc committed
        int start_residue;
        int end_residue;
        int start_x;
        int end_x;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        for(int i = 0; i<number_of_values - 1; ++i) 
        {
    
          if( (isBlast || isWiggle) && plot_values[i] == 0)
          {
            if( !(isBlast && plotType.equals(LineAttributes.PLOT_TYPES[0])) )
              continue;
          }
    
    tjc's avatar
    tjc committed
          start_residue = window_size / 2 + i * step_size + start_position;
          start_x = (int)(start_residue / residues_per_pixel);
    
          
          if(isWiggle)
          {
            int span = ((UserDataAlgorithm)getAlgorithm()).getWiggleSpan(value_index);
            end_residue   = start_residue + span;
          }
          else
            end_residue   = start_residue + step_size;
    
    tjc's avatar
    tjc committed
          end_x = (int)(end_residue / residues_per_pixel);
    
    tjc's avatar
    tjc committed
    
          // this is a number between 0.0 and 1.0
          final float scaled_start_value =
            (plot_values[i] - min_value) / (max_value - min_value);
          final int start_y =
    
    tjc's avatar
    tjc committed
            graph_height - (int)(scaled_start_value * graph_height) +
            getLabelHeight() + 1;
    
    tjc's avatar
    tjc committed
    
          final float scaled_end_value =
            (plot_values[i+1] - min_value) / (max_value - min_value);
          final int end_y =
    
    tjc's avatar
    tjc committed
            graph_height - (int)(scaled_end_value * graph_height) +
            getLabelHeight() + 1;
    
          
          if(plotType == null ||
             plotType.equals(LineAttributes.PLOT_TYPES[0]))
          {
            if(isWiggle)
            {
              g.drawLine(start_x, graph_height+getLabelHeight() + 1, start_x, start_y);
              g.drawLine(start_x, start_y, end_x, start_y);
              g.drawLine(end_x, graph_height+getLabelHeight() + 1, end_x, start_y);
            }
            else
              g.drawLine(start_x, start_y, end_x, end_y);
          }
          else if(plotType.equals(LineAttributes.PLOT_TYPES[1]))
          {
            if(isWiggle)
              g.fillRect(start_x, start_y, end_x-start_x, graph_height+getLabelHeight() + 1);
            {
              int xPoints[] = { start_x, end_x, end_x, start_x };
              int yPoints[] = { start_y, end_y,  
                  graph_height+getLabelHeight() + 1,
                  graph_height+getLabelHeight() + 1};
              g.fillPolygon(xPoints, yPoints, 4);
            }
          }
          else
          {
            int ytop = getLabelHeight() + 1 + 
                       (value_index*(graph_height/numberPlots));
            int ybtm = (graph_height/numberPlots);
    
            // set color based on value
            int colourIndex = 
              (int)(definedColours.length * 0.999 * scaled_start_value);
    
            if(colourIndex > definedColours.length - 1)
              colourIndex = definedColours.length - 1;
            else if (colourIndex < 0)
              colourIndex = 0;
            
            g.setColor(definedColours[ colourIndex ]);
            g.fillRect(start_x, ytop, end_x-start_x, ybtm);
          }
        }
      }
      
      /**
       * Generate the colours for heat map plots.
       * @param col
       * @param NUMBER_OF_SHADES
       * @return
       */
      protected Color[] makeColours(Color col, int NUMBER_OF_SHADES)
      {
        Color definedColour[] = new Color[NUMBER_OF_SHADES];
        for(int i = 0; i < NUMBER_OF_SHADES; ++i)
        {
          int R = col.getRed();
          int G = col.getGreen();
          int B = col.getBlue();
    
    tjc's avatar
    tjc committed
    
    
          float scale = ((float)(NUMBER_OF_SHADES-i) * (float)(255 / NUMBER_OF_SHADES )) ;
          
    
          if((R+scale) <= 255)
    
            R = 254;
    
          if((G+scale) <= 255)
    
            G = 254;
    
          if((B+scale) <= 255)
    
            B = 254;
    
    
          definedColour[i] = new Color(R,G,B);
    
    tjc's avatar
    tjc committed
        }
    
        return definedColour;
    
    tjc's avatar
    tjc committed
      }
    
      /**
       *  Redraw the graph on the canvas using the algorithm.
       *  @param g The object to draw into.
       **/
    
      protected abstract int drawMultiValueGraph(final Graphics g, LineAttributes[] lines);
    
    tjc's avatar
    tjc committed
    
      /**
       *  Draw a line representing the average of the algorithm over the feature.
       *  @param g The object to draw into.
       *  @param min_value The minimum value of the function for the range we are
       *    viewing
       *  @param max_value The maximum value of the function for the range we are
       *    viewing
       **/
    
    tjc's avatar
    tjc committed
      protected void drawGlobalAverage(final Graphics g,
    
    tjc's avatar
    tjc committed
                                        final float min_value,
    
    tjc's avatar
    tjc committed
                                        final float max_value) 
      {
    
    tjc's avatar
    tjc committed
        final Float average = getAlgorithm().getAverage();
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
        if(average != null) 
        {
          g.setColor(Color.gray);
    
    tjc's avatar
    tjc committed
    
          // this is the height of the graph (slightly smaller than the canvas for
          // ease of viewing).
          final int graph_height =
    
    tjc's avatar
    tjc committed
            getSize().height - getFontHeight();
    
    tjc's avatar
    tjc committed
    
          // this is a number between 0.0 and 1.0
          final float scaled_average =
    
    tjc's avatar
    tjc committed
            (average.floatValue() - min_value) / (max_value - min_value);
    
    tjc's avatar
    tjc committed
    
          final int position =
            graph_height -
    
    tjc's avatar
    tjc committed
            (int)(scaled_average * graph_height) +
            getFontHeight() + 1;
    
    tjc's avatar
    tjc committed
    
    
    tjc's avatar
    tjc committed
          g.drawLine(0, position,