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.26 2009-08-18 09:01:44 tjc Exp $
import uk.ac.sanger.artemis.circular.TextFieldFloat;
import uk.ac.sanger.artemis.plot.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.JTextField;
import javax.swing.JOptionPane;
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
* @version $Id: Plot.java,v 1.26 2009-08-18 09:01:44 tjc Exp $
/** off screen image used for double buffering when drawing */
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
* 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();
/**
* 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);
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();
/**
* 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.
**/
setFont(font);
FontMetrics fm = getFontMetrics(font);
MAX_WINDOW = getAlgorithm().getDefaultMaxWindowSize().intValue();
else
if(getAlgorithm().getDefaultMinWindowSize() != null)
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);
if(MAX_WINDOW >= 50)
window_changer.setBlockIncrement(MAX_WINDOW/50);
window_changer.addAdjustmentListener(new AdjustmentListener()
{
public void adjustmentValueChanged(AdjustmentEvent e)
{
addComponentListener(new ComponentAdapter()
{
public void componentShown(ComponentEvent e)
{
addMouseListener(mouse_listener);
addMouseMotionListener(mouse_motion_listener);
}
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.
**/
final MouseListener mouse_listener = new MouseAdapter()
/**
* Listen for mouse press events so that we can do a popup menu and a
* crosshair.
**/
public void mousePressed(MouseEvent event)
final JComponent parent = (JComponent)event.getSource();
final JPopupMenu popup = new JPopupMenu("Plot Options");
// 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...");
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
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);
final JCheckBoxMenuItem scaling_toggle =
new JCheckBoxMenuItem("Scaling");
scaling_toggle.setState(getAlgorithm().scalingFlag());
scaling_toggle.addItemListener(new ItemListener()
getAlgorithm().setScalingFlag(scaling_toggle.getState());
recalculate_flag = true;
repaint();
}
});
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
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();
}
});
}
final JMenu max_window_size =
new JMenu("Maximum Window Size");
final int[] window_sizes =
{
100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000,
200000, 500000, 1000000
};
for(int i = 0 ; i < window_sizes.length ; ++i)
{
final int size = i;
window_size_item = new JMenuItem(" " + window_sizes[i]);
window_size_item.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent _)
{
final int new_maximum = window_sizes[size];
if(new_maximum > window_changer.getMinimum())
window_changer.setMaximum(new_maximum + SCROLL_NOB_SIZE);
recalculate_flag = true;
repaint();
}
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()
{
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);
if(Plot.this instanceof BasePlot)
{
final JMenuItem showAverages =
new JMenuItem("Values and average(s) for selected range...");
showAverages.addActionListener(new ActionListener()
public void actionPerformed(ActionEvent e)
{
showAveragesForRange();
}
});
}
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)
{
}
parent.add(popup);
popup.show(parent, event.getX(), event.getY());
}
else
{
final int point_x = event.getPoint().x;
final int point_y = event.getPoint().y;
if(event.getClickCount() == 2)
fireDoubleClickEvent();
else
fireClickEvent();
repaint();
}
};
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;
if(point_y > getLabelHeight())
cross_hair_position = point_x;
else
cancelCrossHairs();
fireDragEvent();
repaint();
}
};
/**
* 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
*/
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();
}
/**
* 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
PlotMouseListener listener;
for(int i = 0; i < listener_list.size(); ++i)
listener = (PlotMouseListener)listener_list.elementAt(i);
* Call mouseDragged() on each of the PlotMouseListener objects in the
listener = (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 = (PlotMouseListener)listener_list.elementAt(i);
}
}
/**
* 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.
**/
final int width = getWidth() - window_changer.getWidth();
final int height = getHeight();
// there is no point painting a zero width canvas
if(height <= 0 || width <= 0)
if(offscreen == null || lastPaintHeight != height)
Graphics og = offscreen.getGraphics();
og.setClip(0, 0, width, height);
// 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);
protected void resetOffscreenImage()
{
offscreen = null;
}
/**
* Return the canvas x position of the last click or -1 if the user hasn't
* clicked anywhere yet.
**/
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 width = getWidth() - window_changer.getWidth();
final int height = getHeight();
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;
String label_string;
for(int i = index_of_first_label; i <= index_of_last_label; i++)
label_string = String.valueOf((int)(i * base_label_spacing));
(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, height - getScaleHeight() / 2,
scale_number_x_pos, 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 [] plot_values,
final int value_index,
final int numberPlots,
final boolean isWiggle,
final boolean isBlast)
// this is the height of the graph (slightly smaller than the canvas for
// ease of viewing).
final int graph_height = getSize().height -
getLabelHeight() - // leave room for the algorithm name
getScaleHeight() - // leave room for the scale
Color definedColours[] = null;
String plotType = null;
if(lines != null)
{
plotType = lines[value_index].getPlotType();
if(plotType.equals(LineAttributes.PLOT_TYPES[2]))
{
definedColours = makeColours(lines[value_index].getLineColour(),
NUMBER_OF_SHADES);
}
}
int start_residue;
int end_residue;
int start_x;
int end_x;
if( (isBlast || isWiggle) && plot_values[i] == 0)
{
if( !(isBlast && plotType.equals(LineAttributes.PLOT_TYPES[0])) )
continue;
}
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;
// 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;
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
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();
float scale = ((float)(NUMBER_OF_SHADES-i) * (float)(255 / NUMBER_OF_SHADES )) ;
R += scale;
else
G += scale;
else
B += scale;
else
definedColour[i] = new Color(R,G,B);
return definedColour;
}
/**
* 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);
/**
* 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
**/
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 =
// this is a number between 0.0 and 1.0
final float scaled_average =
(average.floatValue() - min_value) / (max_value - min_value);
(int)(scaled_average * graph_height) +
getFontHeight() + 1;