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