Newer
Older
/* 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.2 2004-07-01 12:43:21 tjc Exp $
import uk.ac.sanger.artemis.plot.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.JPanel;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenuItem;
import javax.swing.JScrollBar;
import javax.swing.JPopupMenu;
/**
* This class implements a simple plot component.
*
* @author Kim Rutherford
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
public abstract class Plot extends JPanel
{
/**
* The drawing area for this component.
**/
private JComponent canvas = null;
/**
* A scroll bar for changing the window size.
**/
private JScrollBar window_changer = null;
/**
* The height of the font used in this component.
**/
private int font_height;
/**
* Off screen image used for double buffering when drawing the canvas.
**/
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
* drawScaleLine () is called.
**/
private boolean draw_scale;
/**
* Set to true if drawMultiValueGraph () should call recalculateValues ().
* It is reset to false by recalculateValues ().
**/
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.
**/
final private java.util.Vector listener_list = new java.util.Vector ();
/**
* 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.
**/
this.algorithm = algorithm;
this.draw_scale = draw_scale;
final Font font = Options.getOptions ().getFont ();
setFont (font);
FontMetrics fm = getFontMetrics (font);
if(getAlgorithm().getDefaultMaxWindowSize () != null)
MAX_WINDOW = getAlgorithm().getDefaultMaxWindowSize().intValue();
else
MIN_WINDOW = getAlgorithm ().getDefaultMinWindowSize ().intValue ();
else
START_WINDOW = getAlgorithm().getDefaultWindowSize().intValue();
window_changer = new JScrollBar (Scrollbar.VERTICAL);
window_changer.setValues (START_WINDOW, SCROLL_NOB_SIZE,
MIN_WINDOW, MAX_WINDOW + SCROLL_NOB_SIZE);
window_changer.addAdjustmentListener(new AdjustmentListener()
{
public void adjustmentValueChanged(AdjustmentEvent e)
{
addComponentListener(new ComponentAdapter()
{
public void componentShown(ComponentEvent e)
{
}
final int SCROLL_NOB_SIZE = 10;
/**
* Return the algorithm that was passed to the constructor.
**/
return algorithm;
}
/**
* Return the current value of the window size, as set by the
* window_changer scrollbar.
**/
return window_changer.getValue ();
}
/**
* Create the canvas object for this BasePlot and add it to the component.
**/
private void createCanvas()
{
canvas = new JComponent()
{
/**
* Set the offscreen buffer to null as part of invalidation.
**/
super.invalidate();
offscreen = null;
}
/**
* Override update to *not* erase the background before painting
*/
/**
* Listen for mouse press events so that we can do a popup menu and a
* crosshair.
**/
public void mousePressed(MouseEvent event)
{
if(event.isPopupTrigger() || event.isMetaDown())
{
final JComponent parent = (JComponent)event.getSource();
final JPopupMenu popup = new JPopupMenu("Plot Options");
final JCheckBoxMenuItem scaling_toggle =
new JCheckBoxMenuItem ("Scaling");
scaling_toggle.setState(getAlgorithm ().scalingFlag ());
scaling_toggle.addItemListener(new ItemListener ()
{
public void itemStateChanged (ItemEvent _)
{
getAlgorithm ().setScalingFlag (scaling_toggle.getState ());
recalculate_flag = true;
final JMenuItem max_window_size =
new JMenuItem ("Maximum Window Size:");
popup.add (max_window_size);
100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000,
200000, 500000, 1000000
};
window_size_item.addActionListener(new ActionListener()
{
public void actionPerformed (ActionEvent _)
{
if(new_maximum > window_changer.getMinimum ())
{
window_changer.setMaximum(new_maximum + SCROLL_NOB_SIZE);
recalculate_flag = true;
repaintCanvas ();
}
}
});
popup.add (window_size_item);
}
parent.add (popup);
popup.show (parent, event.getX (), event.getY ());
final int point_x = event.getPoint ().x;
final int point_y = event.getPoint ().y;
if(event.getClickCount() == 2)
fireDoubleClickEvent();
else
fireClickEvent();
}
}
};
canvas.addMouseListener (mouse_listener);
final MouseMotionListener mouse_motion_listener =
new MouseMotionAdapter()
{
public void mouseDragged (MouseEvent event)
{
if(isMenuTrigger(event))
return;
final int point_x = event.getPoint().x;
final int point_y = event.getPoint().y;
}
};
canvas.addMouseMotionListener (mouse_motion_listener);
add (canvas, "Center");
}
/**
* 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;
}
/**
* Call mouseClick () on each of the PlotMouseListener objects in the
* listener list.
**/
private void fireClickEvent()
{
for (int i = 0 ; i < listener_list.size() ; ++i)
{
(PlotMouseListener)listener_list.elementAt(i);
listener.mouseClick(getPointPosition(cross_hair_position));
}
}
/**
* Call mouseDragged () on each of the PlotMouseListener objects in the
* listener list.
**/
private void fireDragEvent()
{
for(int i = 0; i < listener_list.size(); ++i)
{
(PlotMouseListener)listener_list.elementAt(i);
listener.mouseDrag(getPointPosition(drag_start_position),
getPointPosition(cross_hair_position));
}
}
/**
* Call mouseDoubleClick () on each of the PlotMouseListener objects in the
* listener list.
**/
private void fireDoubleClickEvent()
{
for(int i = 0; i < listener_list.size(); ++i)
{
(PlotMouseListener)listener_list.elementAt (i);
listener.mouseDoubleClick(getPointPosition(cross_hair_position));
}
}
/**
* Adds the given listener to receive mouse events from this object.
* @param l the listener.
**/
public void addPlotMouseListener(final PlotMouseListener listener)
{
listener_list.addElement(listener);
}
/**
* Removes the given listener from the list of those objects that receive
* mouse events from this object.
* @param l the listener.
**/
public void removePlotMouseListener(final PlotMouseListener listener)
{
listener_list.removeElement(listener);
}
/**
* 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.
**/
private void paintCanvas(final Graphics g)
{
if(!isVisible())
final int canvas_width = canvas.getSize().width;
final int canvas_height = canvas.getSize().height;
// there is no point painting a zero width canvas
return;
}
if(offscreen == null)
offscreen = createImage(canvas_width,
canvas_height);
og.fillRect(0, 0, canvas.getSize().width, canvas.getSize().height);
// Redraw the graph on the canvas using the algorithm from the
// constructor.
}
/**
* Return the canvas x position of the last click or -1 if the user hasn't
* clicked anywhere yet.
**/
protected int getCrossHairPosition()
{
if(cross_hair_position >= getCanvas().getSize().width)
return cross_hair_position;
}
/**
* Force this component to stop drawing crosshairs.
**/
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
**/
protected void drawScaleLine(final Graphics g,
final int start, final int end)
{
final int canvas_width = canvas.getSize ().width;
final int canvas_height = canvas.getSize ().height;
final int scale_number_y_pos = canvas_height - 1;
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;
index_of_first_label = possible_index_of_first_label;
final int index_of_last_label = end / base_label_spacing;
for (int i = index_of_first_label ;
i <= index_of_last_label ;
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
final String label_string =
String.valueOf ((int)(i * base_label_spacing));
final int scale_number_x_pos =
(int) ((i * base_label_spacing - start) / bases_per_pixel);
g.drawString (label_string,
scale_number_x_pos + 2,
scale_number_y_pos);
g.drawLine (scale_number_x_pos, canvas_height - getScaleHeight () / 2,
scale_number_x_pos, canvas_height - getScaleHeight ());
}
}
/**
* 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.
**/
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 residues_per_pixel =
(float) total_unit_count / canvas.getSize ().width;
// this is the height of the graph (slightly smaller than the canvas for
// ease of viewing).
final int graph_height = canvas.getSize ().height -
getLabelHeight () - // leave room for the algorithm name
getScaleHeight () - // leave room for the scale
2;
if (graph_height < 5) {
// too small to draw
return;
}
final int number_of_values = plot_values.length;
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
final int start_residue =
window_size / 2 + i * step_size + start_position;
final int end_residue =
start_residue + step_size;
final int start_x = (int) (start_residue / residues_per_pixel);
final int end_x = (int) (end_residue / residues_per_pixel);
// 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 =
graph_height - (int) (scaled_start_value * graph_height) +
getLabelHeight () + 1;
final float scaled_end_value =
(plot_values[i+1] - min_value) / (max_value - min_value);
final int end_y =
graph_height - (int) (scaled_end_value * graph_height) +
getLabelHeight () + 1;
g.drawLine (start_x, start_y, end_x, end_y);
}
}
/**
* Redraw the graph on the canvas using the algorithm.
* @param g The object to draw into.
**/
protected abstract void drawMultiValueGraph (final Graphics g);
/**
* 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
**/
protected void drawGlobalAverage (final Graphics g,
final float min_value,
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
final Float average = getAlgorithm ().getAverage ();
if (average != null) {
g.setColor (Color.gray);
// this is the height of the graph (slightly smaller than the canvas for
// ease of viewing).
final int graph_height =
canvas.getSize ().height - getFontHeight ();
// this is a number between 0.0 and 1.0
final float scaled_average =
(average.floatValue () - min_value) / (max_value - min_value);
final int position =
graph_height -
(int) (scaled_average * graph_height) +
getFontHeight () + 1;
g.drawLine (0, position,
canvas.getSize ().width, position);
final FontMetrics fm = g.getFontMetrics ();
final int canvas_width = canvas.getSize ().width;
final String average_string =
String.valueOf (Math.round (average.floatValue () * 100.0) / 100.0);
g.drawString (average_string,
canvas_width - fm.stringWidth (average_string) - 1,
position);
}
}
/**
* Put the algorithm name in the top left corner of the canvas and the
* window size in the bottom left.
* @param g The object to draw into.
**/
g.setColor (Color.black);
g.drawString (getAlgorithm ().getAlgorithmName () +
" Window size: " +
String.valueOf (window_changer.getValue ()),
2,
font_height);
}
/**
* The method converts the min_value and max_value to String objects and
* then draws them onto the canvas. The min_value is drawn at the bottom
* right, max_value at the top right.
**/
protected void drawMinMax(final Graphics g,
final float min_value, final float max_value)
{
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
g.setColor (Color.black);
final int canvas_width = canvas.getSize ().width;
final int canvas_height = canvas.getSize ().height;
g.drawLine (0, canvas_height - getScaleHeight (),
canvas_width, canvas_height - getScaleHeight ());
g.drawLine (0, getLabelHeight (),
canvas_width, getLabelHeight ());
final FontMetrics fm = g.getFontMetrics ();
final String min_string =
String.valueOf (((int) (min_value * 100)) / 100.0);
g.drawString (min_string,
canvas_width - fm.stringWidth (min_string) - 1,
canvas_height - 1 - getScaleHeight ());
final String max_string =
String.valueOf (((int) (max_value * 100)) / 100.0);
g.drawString (max_string,
canvas_width - fm.stringWidth (max_string) - 1,
1 + getFontHeight () * 2);
}
/**
* Draw a vertical line at the given position.
* @param label The label to use on the crosshair
* @param label_pos The position on the line at which the label should be
* drawn (0 is nearest the top).
**/
protected void drawCrossHair(final Graphics g, final int x_position,
final String label, final int label_pos)
{
if (x_position >= 0)
{
g.drawLine (x_position, getLabelHeight (),
x_position, canvas.getSize ().height);
g.drawString (label, x_position + 2,
getFontHeight () * (2 + label_pos) + 2);
}
}
/**
* Return the JComponent of this Plot.
**/
return canvas;
}
/**
* Call repaint () on the canvas object.
**/
canvas.repaint ();
}
/**
* 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);
/**
* Return the amount of vertical space (in pixels) to use for the scale.
**/
private int getScaleHeight()
{
if (draw_scale)
return getFontHeight() + 2;
else
return 0;
}
/**
* Return the height in algorithm name and label line (returns the font
* height plus a small amount).
**/
}
/**
* Return the height in pixels of the current font.
**/