001/* A histogram plotter. 002 003 @Copyright (c) 1997-2014 The Regents of the University of California. 004 All rights reserved. 005 006 Permission is hereby granted, without written agreement and without 007 license or royalty fees, to use, copy, modify, and distribute this 008 software and its documentation for any purpose, provided that the 009 above copyright notice and the following two paragraphs appear in all 010 copies of this software. 011 012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 016 SUCH DAMAGE. 017 018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 023 ENHANCEMENTS, OR MODIFICATIONS. 024 025 PT_COPYRIGHT_VERSION_2 026 COPYRIGHTENDKEY 027 */ 028package ptolemy.plot; 029 030import java.awt.Graphics; 031import java.awt.Rectangle; 032import java.io.BufferedWriter; 033import java.io.PrintWriter; 034import java.io.Writer; 035import java.util.ArrayList; 036import java.util.Enumeration; 037import java.util.HashSet; 038import java.util.Hashtable; 039import java.util.Locale; 040import java.util.Random; 041import java.util.Vector; 042 043import ptolemy.util.RunnableExceptionCatcher; 044 045/////////////////////////////////////////////////////////////////// 046//// Histogram 047 048/** 049 A histogram plotter. The plot can be configured and data can 050 be provided either through a file with commands or through direct 051 invocation of the public methods of the class. To read a file or a 052 URL, use the read() method. 053 <p> 054 When calling the public methods, in most cases the changes will not 055 be visible until paint() has been called. To request that this 056 be done, call repaint(). One exception is addPoint(), which 057 makes the affect of the new point visible immediately (or nearly 058 immediately) if the plot is visible on the screen. 059 <p> 060 The ASCII format for the file file contains any number commands, 061 one per line. Unrecognized commands and commands with syntax 062 errors are ignored. Comments are denoted by a line starting with a 063 pound sign "#". The recognized commands include those supported by 064 the base class, plus a few more. The commands are case 065 insensitive, but are usually capitalized. The number of data sets 066 to be plotted does not need to be specified. Data sets are added as needed. 067 Each dataset is identified with a color (see the base class). 068 <P> 069 The appearance of the histogram can be altered by the following commands: 070 <pre> 071 Bars: <i>width</i> 072 Bars: <i>width, offset</i> 073 </pre> 074 The <i>width</i> is a real number specifying the width of the bars 075 as a fraction of the bin width. It usually has a value less than 076 or equal to one, 077 and defaults to 0.5. The <i>offset</i> is a real number 078 specifying how much the bar of the <i>i </i><sup>th</sup> data set 079 is offset from the previous one. This allows bars to "peek out" 080 from behind the ones in front. It defaults to 0.15. 081 Note that the frontmost data set will be the first one. 082 <p> 083 The width of each bin of the histogram can be specified using: 084 <pre> 085 BinWidth: <i>width</i> 086 </pre> 087 This is given in whatever units the data has. 088 By default, each bin is centered at <i>x</i> = <i>nw</i>, 089 where <i>w</i> is the width of the bin and <i>n</i> is an integer. 090 That bin represents values in the range (<i>x - w/2, x + w/2</i>). 091 The alignment of the bins can be changed with the following command: 092 <pre> 093 BinOffset: <i>offset</i> 094 </pre> 095 If this method is used with argument <i>o</i>, then each bin is 096 centered at <i>x = nw + o</i>, and represents values in the range 097 (<i>x - w/2 + o, x + w/2 + o</i>). So for example, if <i>o = w/2</i>, 098 then each bin represents values from <i>nw</i> to 099 (<i>n</i> + 1)<i>w</i> for some integer <i>n</i>. 100 The default offset is 0.5, half the default bin width. 101 <p> 102 To specify data to be plotted, start a data set with the following command: 103 <pre> 104 DataSet: <i>string</i> 105 </pre> 106 Here, <i>string</i> is a label that will appear in the legend. 107 It is not necessary to enclose the string in quotation marks. 108 To start a new dataset without giving it a name, use: 109 <pre> 110 DataSet: 111 </pre> 112 In this case, no item will appear in the legend. 113 New datasets are plotted <i>behind</i> the previous ones. 114 The data itself is given by a sequence of numbers, one per line. 115 The numbers are specified as 116 strings that can be parsed by the Double parser in Java. 117 It is also possible to specify the numbers using all the formats 118 accepted by the Plot class, so that the same data may be plotted by 119 both classes. The <i>x</i> data is ignored, and only the <i>y</i> 120 data is used to calculate the histogram. 121 122 @author Edward A. Lee, Contributor: Bert Rodiers 123 @version $Id$ 124 @since Ptolemy II 0.3 125 @Pt.ProposedRating Yellow (cxh) 126 @Pt.AcceptedRating Yellow (cxh) 127 */ 128@SuppressWarnings("serial") 129public class Histogram extends PlotBox { 130 131 /////////////////////////////////////////////////////////////////// 132 //// public methods //// 133 134 /** Add a legend (displayed at the upper right) for the specified 135 * data set with the specified string. Short strings generally 136 * fit better than long strings. 137 * @param dataset The dataset index. 138 * @param legend The label for the dataset. 139 */ 140 @Override 141 public void addLegend(int dataset, String legend) { 142 _checkDatasetIndex(dataset); 143 super.addLegend(dataset, legend); 144 } 145 146 /** In the specified data set, add the specified value to the 147 * histogram. Data set indices begin with zero. If the data set 148 * does not exist, create it. 149 * The new point will visibly alter the histogram if the plot is visible 150 * on the screen. Otherwise, it will be drawn the next time the histogram 151 * is drawn on the screen. 152 * <p> 153 * In order to work well with swing and be thread safe, this method 154 * actually defers execution to the event dispatch thread, where 155 * all user interface actions are performed. Thus, the point will 156 * not be added immediately (unless you call this method from within 157 * the event dispatch thread). All the methods that do this deferring 158 * coordinate so that they are executed in the order that you 159 * called them. 160 * 161 * @param dataset The data set index. 162 * @param value The new value. 163 */ 164 public synchronized void addPoint(final int dataset, final double value) { 165 Runnable doAddPoint = new Runnable() { 166 @Override 167 public void run() { 168 _addPoint(dataset, value); 169 } 170 }; 171 172 deferIfNecessary(doAddPoint); 173 } 174 175 /** In the specified data set, add the specified y value to the 176 * histogram. The x value and the <i>connected</i> arguments are 177 * ignored. Data set indices begin with zero. If the data set 178 * does not exist, create it. 179 * @param dataset The data set index. 180 * @param x Ignored. 181 * @param y The Y position of the new point. 182 * @param connected Ignored 183 */ 184 public synchronized void addPoint(int dataset, double x, double y, 185 boolean connected) { 186 addPoint(dataset, y); 187 } 188 189 /** Clear the plot of all data points. If the argument is true, then 190 * reset all parameters to their initial conditions, including 191 * the persistence, plotting format, and axes formats. 192 * For the change to take effect, you must call repaint(). 193 * <p> 194 * In order to work well with swing and be thread safe, this method 195 * actually defers execution to the event dispatch thread, where 196 * all user interface actions are performed. Thus, the clear will 197 * not be executed immediately (unless you call this method from within 198 * the event dispatch thread). All the methods that do this deferring 199 * coordinate so that they are executed in the order that you 200 * called them. 201 * 202 * @param format If true, clear the format controls as well. 203 */ 204 @Override 205 public synchronized void clear(final boolean format) { 206 Runnable doClear = new Runnable() { 207 @Override 208 public void run() { 209 _clear(format); 210 } 211 }; 212 213 deferIfNecessary(doClear); 214 } 215 216 /** Write plot data information to the specified output stream in PlotML, 217 * but in such a way that the Plot class can read it and reproduce the 218 * histogram. The ordinary mechanism for saving the histogram 219 * records the raw data and the configuration to be used by this class. 220 * @param output A buffered print writer. 221 * @param dtd The DTD, or null to reference the default. 222 */ 223 public synchronized void exportToPlot(PrintWriter output, String dtd) { 224 if (dtd == null) { 225 output.println("<?xml version=\"1.0\" standalone=\"yes\"?>"); 226 output.println( 227 "<!DOCTYPE plot PUBLIC \"-//UC Berkeley//DTD PlotML 1//EN\""); 228 output.println( 229 " \"http://ptolemy.eecs.berkeley.edu/xml/dtd/PlotML_1.dtd\">"); 230 } else { 231 output.println("<?xml version=\"1.0\" standalone=\"no\"?>"); 232 output.println("<!DOCTYPE plot SYSTEM \"" + dtd + "\">"); 233 } 234 235 output.println("<plot>"); 236 output.println("<!-- Ptolemy plot, version " + PTPLOT_RELEASE 237 + " , PlotML format. Exported from Histogram. -->"); 238 239 super.writeFormat(output); 240 output.println("<barGraph width=\"" + _barwidth * _binWidth 241 + "\" offset=\"" + _baroffset * _binWidth + "\"/>"); 242 243 for (int dataset = 0; dataset < _points.size(); dataset++) { 244 // Write the dataset directive 245 String legend = getLegend(dataset); 246 247 if (legend != null) { 248 output.println( 249 "<dataset name=\"" + legend + "\" connected=\"no\">"); 250 } else { 251 output.println("<dataset connected=\"no\">"); 252 } 253 254 Hashtable data = (Hashtable) _histogram.elementAt(dataset); 255 Enumeration keys = data.keys(); 256 257 while (keys.hasMoreElements()) { 258 Integer bin = (Integer) keys.nextElement(); 259 Integer count = (Integer) data.get(bin); 260 261 // The X axis value is a bit complex to get. 262 int xValue = (int) (bin.intValue() * _binWidth + _binOffset); 263 output.println("<p x=\"" + xValue + "\" y=\"" + count.intValue() 264 + "\"/>"); 265 } 266 267 output.println("</dataset>"); 268 } 269 270 output.println("</plot>"); 271 output.flush(); 272 } 273 274 /** Rescale so that the data that is currently plotted just fits. 275 * This overrides the base class method to ensure that the 276 * fill is actually performed in the event dispatch thread. 277 * In order to work well with swing and be thread safe, this method 278 * actually defers execution to the event dispatch thread, where 279 * all user interface actions are performed. Thus, the fill will 280 * not occur immediately (unless you call this method from within 281 * the event dispatch thread). All the methods that do this deferring 282 * coordinate so that they are executed in the order that you 283 * called them. 284 */ 285 @Override 286 public synchronized void fillPlot() { 287 Runnable doFill = new Runnable() { 288 @Override 289 public void run() { 290 _fillPlot(); 291 } 292 }; 293 294 deferIfNecessary(doFill); 295 } 296 297 /** Create a sample plot. 298 */ 299 @Override 300 public synchronized void samplePlot() { 301 // Create a sample plot. 302 clear(true); 303 304 setTitle("Sample histogram"); 305 setXLabel("values"); 306 setYLabel("count"); 307 308 for (int i = 0; i <= 1000; i++) { 309 this.addPoint(0, 5.0 * Math.cos(Math.PI * i / 500.0)); 310 this.addPoint(1, 10.0 * _random.nextDouble() - 5.0); 311 this.addPoint(2, 2.0 * _random.nextGaussian()); 312 } 313 314 this.repaint(); 315 } 316 317 /** Set the width and offset of the bars. Both are specified 318 * as a fraction of a bin width. The offset is the amount by which the 319 * i <sup>th</sup> data set is shifted to the right, so that it 320 * peeks out from behind the earlier data sets. 321 * @param width The width of the bars. 322 * @param offset The offset per data set. 323 */ 324 public synchronized void setBars(double width, double offset) { 325 // Ensure replot of offscreen buffer. 326 _plotImage = null; 327 _barwidth = width; 328 _baroffset = offset; 329 } 330 331 /** Set the offset of the bins, in whatever units the data are given. 332 * Without calling this, each bin is centered at <i>x</i> = <i>nw</i>, 333 * where <i>w</i> is the width of the bin and <i>n</i> is an integer. 334 * That bin represents values in the range (<i>x - w/2, x + w/2</i>). 335 * If this method is called with argument <i>o</i>, then each bin is 336 * centered at <i>x = nw + o</i>, and represents values in the range 337 * (<i>x - w/2 + o, x + w/2 + o</i>). So for example, if <i>o = w/2</i>, 338 * then each bin represents values from <i>nw</i> to 339 * (<i>n</i> + 1)<i>w</i>) for some integer <i>n</i>. 340 * @param offset The bin offset. 341 */ 342 public synchronized void setBinOffset(double offset) { 343 // Ensure replot of offscreen buffer. 344 _plotImage = null; 345 _binOffset = offset; 346 } 347 348 /** Set the width of the bins, in whatever units the data are given. 349 * @param width The width of the bins. 350 */ 351 public void setBinWidth(double width) { 352 // Ensure replot of offscreen buffer. 353 _plotImage = null; 354 _binWidth = width; 355 } 356 357 /** Set the seed of the random number generator used to create 358 * the sample plot. This method is primarily used for testing. 359 * @param seed The seed. 360 * @see #samplePlot() 361 */ 362 public void setSeed(Long seed) { 363 _random.setSeed(seed); 364 } 365 366 /** Write the current data and plot configuration to the 367 * specified stream in PlotML syntax. This writes the histogram, 368 * not the raw data, in such a way that the Plot class (not the 369 * Histogram class) will correctly render it. 370 * The URL (relative or absolute) for the DTD is 371 * given as the second argument. If that argument is null, 372 * then the PlotML PUBLIC DTD is referenced, resulting in a file 373 * that can be read by a PlotML parser without any external file 374 * references, as long as that parser has local access to the DTD. 375 * The output is buffered, and is flushed before exiting. 376 * @param out An output writer. 377 * @param dtd The reference (URL) for the DTD, or null to use the 378 * PUBLIC DTD. 379 */ 380 @Override 381 public synchronized void write(Writer out, String dtd) { 382 // Auto-flush is disabled. 383 PrintWriter output = new PrintWriter(new BufferedWriter(out), false); 384 exportToPlot(output, dtd); 385 } 386 387 /** Write plot data information to the specified output stream in PlotML. 388 * @param output A buffered print writer. 389 */ 390 @Override 391 public synchronized void writeData(PrintWriter output) { 392 super.writeData(output); 393 394 for (int dataset = 0; dataset < _points.size(); dataset++) { 395 // Write the dataset directive 396 String legend = getLegend(dataset); 397 398 if (legend != null) { 399 output.println("<dataset name=\"" + legend + "\">"); 400 } else { 401 output.println("<dataset>"); 402 } 403 404 // Write the data 405 Vector pts = (Vector) _points.elementAt(dataset); 406 407 for (int pointnum = 0; pointnum < pts.size(); pointnum++) { 408 Double pt = (Double) pts.elementAt(pointnum); 409 output.println("<p y=\"" + pt.doubleValue() + "\"/>"); 410 } 411 412 output.println("</dataset>"); 413 } 414 } 415 416 /** Write plot format information to the specified output stream. 417 * @param output A buffered print writer. 418 */ 419 @Override 420 public synchronized void writeFormat(PrintWriter output) { 421 super.writeFormat(output); 422 423 // NOTE: Regrettably, the meaning of the barGraph data is 424 // different for a histogram than for a normal plot. 425 // In a histogram, it is proportional to the bin width. 426 // Thus, this is not the same as the corresponding line 427 // in exportToPlot(). 428 output.println("<barGraph width=\"" + _barwidth + "\" offset=\"" 429 + _baroffset + "\"/>"); 430 431 output.println("<bin width=\"" + _binWidth + "\" offset=\"" + _binOffset 432 + "\"/>"); 433 } 434 435 /////////////////////////////////////////////////////////////////// 436 //// protected methods //// 437 438 /** Check the argument to ensure that it is a valid data set index. 439 * If it is less than zero, throw an IllegalArgumentException (which 440 * is a runtime exception). If it does not refer to an existing 441 * data set, then fill out the _points and _histogram 442 * Vectors so that they refer 443 * to all existing data sets. 444 * @param dataset The data set index. 445 */ 446 protected void _checkDatasetIndex(int dataset) { 447 if (dataset < 0) { 448 throw new IllegalArgumentException("Plot._checkDatasetIndex: Cannot" 449 + " give a negative number for the data set index."); 450 } 451 452 while (dataset >= _points.size()) { 453 _points.addElement(new Vector()); 454 _histogram.addElement(new Hashtable()); 455 } 456 } 457 458 /** Draw bar from the specified point to the y axis. 459 * If the specified point is below the y axis or outside the 460 * x range, do nothing. If the <i>clip</i> argument is true, 461 * then do not draw above the y range. 462 * Note that paint() should be called before 463 * calling this method so that _xscale and _yscale are properly set. 464 * @param graphics The graphics context. 465 * @param dataset The index of the dataset. 466 * @param xpos The x position. 467 * @param ypos The y position. 468 * @param clip If true, then do not draw outside the range. 469 */ 470 protected void _drawBar(Graphics graphics, int dataset, long xpos, 471 long ypos, boolean clip) { 472 if (clip) { 473 if (ypos < _uly) { 474 ypos = _uly; 475 } 476 477 if (ypos > _lry) { 478 ypos = _lry; 479 } 480 } 481 482 if (ypos <= _lry && xpos <= _lrx && xpos >= _ulx) { 483 // left x position of bar. 484 int barlx = (int) (xpos - _barwidth * _binWidth * _xscale / 2 485 + dataset * _baroffset * _binWidth * _xscale); 486 487 // right x position of bar 488 int barrx = (int) (barlx + _barwidth * _binWidth * _xscale); 489 490 if (barlx < _ulx) { 491 barlx = _ulx; 492 } 493 494 if (barrx > _lrx) { 495 barrx = _lrx; 496 } 497 498 // Make sure that a bar is always at least one pixel wide. 499 if (barlx >= barrx) { 500 barrx = barlx + 1; 501 } 502 503 // The y position of the zero line. 504 long zeroypos = _lry - (long) ((0 - _yMin) * _yscale); 505 506 if (_lry < zeroypos) { 507 zeroypos = _lry; 508 } 509 510 if (_uly > zeroypos) { 511 zeroypos = _uly; 512 } 513 514 if (_yMin >= 0 || ypos <= zeroypos) { 515 graphics.fillRect(barlx, (int) ypos, barrx - barlx, 516 (int) (zeroypos - ypos)); 517 } else { 518 graphics.fillRect(barlx, (int) zeroypos, barrx - barlx, 519 (int) (ypos - zeroypos)); 520 } 521 } 522 } 523 524 /** Draw the axes and then plot the histogram. If the second 525 * argument is true, clear the display first. 526 * This method is called by paint(). To cause it to be called you 527 * would normally call repaint(), which eventually causes paint() to 528 * be called. 529 * <p> 530 * Note that this is synchronized so that points are not added 531 * by other threads while the drawing is occurring. This method 532 * should be called only from the event dispatch thread, consistent 533 * with swing policy. 534 * @param graphics The graphics context. 535 * @param clearfirst If true, clear the plot before proceeding. 536 * @param drawRect A specification of the size. 537 */ 538 @Override 539 protected synchronized void _drawPlot(Graphics graphics, boolean clearfirst, 540 Rectangle drawRect) { 541 // This method called when images are exported. 542 543 // We must call PlotBox._drawPlot() before calling _drawPlotPoint 544 // so that _xscale and _yscale are set. 545 super._drawPlot(graphics, clearfirst, drawRect); 546 547 _showing = true; 548 549 // Plot the histograms in reverse order so that the first colors 550 // appear on top. 551 for (int dataset = _points.size() - 1; dataset >= 0; dataset--) { 552 Hashtable data = (Hashtable) _histogram.elementAt(dataset); 553 Enumeration keys = data.keys(); 554 555 while (keys.hasMoreElements()) { 556 Integer bin = (Integer) keys.nextElement(); 557 Integer count = (Integer) data.get(bin); 558 _drawPlotPoint(graphics, dataset, bin.intValue(), 559 count.intValue()); 560 } 561 } 562 } 563 564 /** Parse a line that gives plotting information. Return true if 565 * the line is recognized. Lines with syntax errors are ignored. 566 * @param line A command line. 567 * It is not synchronized, so its caller should be. 568 * @return True if the line is recognized. 569 */ 570 @Override 571 protected boolean _parseLine(String line) { 572 // parse only if the super class does not recognize the line. 573 if (super._parseLine(line)) { 574 return true; 575 } else { 576 // We convert the line to lower case so that the command 577 // names are case insensitive 578 String lcLine = line.toLowerCase(Locale.getDefault()); 579 580 if (lcLine.startsWith("dataset:")) { 581 // new data set 582 _currentdataset++; 583 584 if (lcLine.length() > 0) { 585 String legend = line.substring(8).trim(); 586 587 if (legend != null && legend.length() > 0) { 588 addLegend(_currentdataset, legend); 589 } 590 } 591 592 return true; 593 } else if (lcLine.startsWith("bars:") 594 || lcLine.startsWith("bargraph:")) { 595 // The PlotML code uses barGraph, but the older style 596 // uses bars 597 int comma = line.indexOf(",", 5); 598 String barwidth; 599 String baroffset = null; 600 601 if (comma > 0) { 602 barwidth = line.substring(5, comma).trim(); 603 baroffset = line.substring(comma + 1).trim(); 604 } else { 605 barwidth = line.substring(5).trim(); 606 } 607 608 try { 609 Double bwidth = Double.valueOf(barwidth); 610 double boffset = _baroffset; 611 612 if (baroffset != null) { 613 boffset = Double.valueOf(baroffset).doubleValue(); 614 } 615 616 setBars(bwidth.doubleValue(), boffset); 617 } catch (NumberFormatException e) { 618 // ignore if format is bogus. 619 } 620 621 return true; 622 } else if (lcLine.startsWith("binwidth:")) { 623 String binwidth = line.substring(9).trim(); 624 625 try { 626 Double bwidth = Double.valueOf(binwidth); 627 setBinWidth(bwidth.doubleValue()); 628 } catch (NumberFormatException e) { 629 // ignore if format is bogus. 630 } 631 632 return true; 633 } else if (lcLine.startsWith("binoffset:")) { 634 String binoffset = line.substring(10).trim(); 635 636 try { 637 Double boffset = Double.valueOf(binoffset); 638 setBinOffset(boffset.doubleValue()); 639 } catch (NumberFormatException e) { 640 // ignore if format is bogus. 641 } 642 643 return true; 644 } else if (lcLine.startsWith("numsets:")) { 645 // Obsolete field... ignore. 646 return true; 647 } else if (line.startsWith("move:")) { 648 // deal with 'move: 1 2' and 'move:2 2' 649 line = line.substring(5, line.length()).trim(); 650 } else if (line.startsWith("move")) { 651 // deal with 'move 1 2' and 'move2 2' 652 line = line.substring(4, line.length()).trim(); 653 } else if (line.startsWith("draw:")) { 654 // a connected point, if connect is enabled. 655 line = line.substring(5, line.length()).trim(); 656 } else if (line.startsWith("draw")) { 657 // a connected point, if connect is enabled. 658 line = line.substring(4, line.length()).trim(); 659 } 660 661 line = line.trim(); 662 663 // Handle Plot formats 664 int fieldsplit = line.indexOf(","); 665 666 if (fieldsplit == -1) { 667 fieldsplit = line.indexOf(" "); 668 } 669 670 if (fieldsplit == -1) { 671 fieldsplit = line.indexOf("\t"); // a tab 672 } 673 674 if (fieldsplit == -1) { 675 // Have just one number per line 676 try { 677 Double xpt = Double.valueOf(line); 678 addPoint(_currentdataset, xpt.doubleValue()); 679 return true; 680 } catch (NumberFormatException e) { 681 // ignore if format is bogus. 682 } 683 } else { 684 String y = line.substring(fieldsplit + 1).trim(); 685 686 try { 687 Double ypt = Double.valueOf(y); 688 addPoint(_currentdataset, ypt.doubleValue()); 689 return true; 690 } catch (NumberFormatException e) { 691 // ignore if format is bogus. 692 } 693 } 694 } 695 696 return false; 697 } 698 699 /** Reset a scheduled redraw tasks. 700 */ 701 @Override 702 protected void _resetScheduledTasks() { 703 Runnable redraw = new RunnableExceptionCatcher(new Runnable() { 704 @Override 705 public void run() { 706 _scheduledBinsToAdd.clear(); 707 } 708 }); 709 synchronized (this) { 710 deferIfNecessary(redraw); 711 } 712 } 713 714 /** Perform a scheduled redraw. 715 */ 716 @Override 717 protected void _scheduledRedraw() { 718 if (_needPlotRefill || _needBinRedraw) { 719 Runnable redraw = new RunnableExceptionCatcher(new Runnable() { 720 @Override 721 public void run() { 722 ArrayList<HashSet<Integer>> scheduledBinsToAdd = new ArrayList<HashSet<Integer>>(); 723 for (int i = 0; i < _scheduledBinsToAdd.size(); ++i) { 724 HashSet<Integer> element = _scheduledBinsToAdd.get(i); 725 scheduledBinsToAdd 726 .add((HashSet<Integer>) element.clone()); 727 element.clear(); 728 } 729 _needBinRedraw = false; 730 if (_needPlotRefill) { 731 fillPlot(); 732 _needPlotRefill = false; 733 } else { 734 Graphics graphics = getGraphics(); 735 if (graphics != null) { 736 { 737 int nbrOfDataSets = scheduledBinsToAdd.size(); 738 for (int i = 0; i < nbrOfDataSets; ++i) { 739 Hashtable bins = (Hashtable) _histogram 740 .elementAt(i); 741 for (Integer bin : scheduledBinsToAdd 742 .get(i)) { 743 _drawPlotPoint(graphics, i, bin, 744 (Integer) bins.get(bin)); 745 } 746 } 747 } 748 } 749 } 750 } 751 }); 752 synchronized (this) { 753 deferIfNecessary(redraw); 754 } 755 } 756 } 757 758 /////////////////////////////////////////////////////////////////// 759 //// protected variables //// 760 761 /** The current dataset. 762 * @serial 763 */ 764 protected int _currentdataset = -1; 765 766 /** A vector of datasets. 767 * @serial 768 */ 769 protected Vector _points = new Vector(); 770 771 /** A vector of histogram data. 772 * @serial 773 */ 774 protected Vector _histogram = new Vector(); 775 776 /////////////////////////////////////////////////////////////////// 777 //// private methods //// 778 779 /* In the specified data set, add the specified value to the 780 * histogram. Data set indices begin with zero. If the data set 781 * does not exist, create it. 782 * The new point will visibly alter the histogram if the plot is visible 783 * on the screen. Otherwise, it will be drawn the next time the histogram 784 * is drawn on the screen. 785 * 786 * This is not synchronized, so the caller should be. Moreover, this 787 * should only be called in the event dispatch thread. It should only 788 * be called by _executeDeferredActions(). 789 * 790 * @param dataset The data set index. 791 * @param value The new value. 792 */ 793 private void _addPoint(int dataset, double value) { 794 // Ensure replot of offscreen buffer. 795 _plotImage = null; 796 797 _checkDatasetIndex(dataset); 798 799 // Calculate the bin number. 800 int bin = (int) Math.round((value - _binOffset) / _binWidth); 801 Integer binobj = Integer.valueOf(bin); 802 803 // Add to the appropriate bin 804 Hashtable bins = (Hashtable) _histogram.elementAt(dataset); 805 int count; 806 807 if (bins.containsKey(binobj)) { 808 // increase the count 809 count = 1 + ((Integer) bins.get(binobj)).intValue(); 810 bins.put(binobj, Integer.valueOf(count)); 811 } else { 812 // start a new entry. 813 count = 1; 814 bins.put(binobj, Integer.valueOf(count)); 815 } 816 817 // For auto-ranging, keep track of min and max. 818 double x = bin * _binWidth + _binOffset; 819 820 if (x < _xBottom) { 821 if (_automaticRescale() && _xTop != -Double.MAX_VALUE 822 && _xBottom != Double.MAX_VALUE) { 823 _needPlotRefill = true; 824 _xBottom = x - (_xTop - _xBottom); 825 } else { 826 _xBottom = x; 827 } 828 } 829 830 double xtop = x + _binWidth / 2.0; 831 832 if (xtop > _xTop) { 833 if (_automaticRescale() && _xTop != -Double.MAX_VALUE 834 && _xBottom != Double.MAX_VALUE) { 835 _needPlotRefill = true; 836 _xTop = xtop + _xTop - _xBottom; 837 } else { 838 _xTop = xtop; 839 } 840 } 841 842 _yBottom = 0.0; 843 844 if (count > _yTop) { 845 if (_automaticRescale() && _yTop != -Double.MAX_VALUE 846 && _yBottom != Double.MAX_VALUE) { 847 _needPlotRefill = true; 848 _yTop = count + _yTop - _yBottom; 849 } else { 850 _yTop = count; 851 } 852 } 853 854 Vector pts = (Vector) _points.elementAt(dataset); 855 pts.addElement(Double.valueOf(value)); 856 857 // Draw the point on the screen only if the plot is showing. 858 // Need to check that graphics is not null because plot may have 859 // been dismissed. 860 Graphics graphics = getGraphics(); 861 862 if (_showing && graphics != null) { 863 // In swing, updates to showing graphics must be done in the 864 // event thread, not here. Thus, we have to queue the request. 865 final int pendingDataset = dataset; 866 final int pendingBin = bin; 867 final int pendingCount = count; 868 869 if (!_timedRepaint()) { 870 // We are in the event thread, so this is safe... 871 _drawPlotPoint(graphics, pendingDataset, pendingBin, 872 pendingCount); 873 } else { 874 if (!_needPlotRefill) { 875 _scheduleBinRedraw(dataset, bin); 876 } 877 } 878 } 879 } 880 881 /* Clear the plot of all data points. If the argument is true, then 882 * reset all parameters to their initial conditions, including 883 * the persistence, plotting format, and axes formats. 884 * For the change to take effect, you must call repaint(). 885 * 886 * @param format If true, clear the format controls as well. 887 */ 888 private void _clear(boolean format) { 889 // Ensure replot of offscreen buffer. 890 _plotImage = null; 891 892 super.clear(format); 893 _currentdataset = -1; 894 _points = new Vector(); 895 _histogram = new Vector(); 896 _showing = false; 897 _resetScheduledTasks(); 898 899 if (format) { 900 // Reset format controls 901 _barwidth = 0.5; 902 _baroffset = 0.15; 903 _binWidth = 1.0; 904 _binOffset = 0.5; 905 } 906 } 907 908 /* Draw the specified histogram bar. 909 * Note that paint() should be called before 910 * calling this method so that it calls _drawPlot(), which sets 911 * _xscale and _yscale. Note that this does not check the dataset 912 * index. It is up to the caller to do that. 913 * 914 * Note that this method is not synchronized, so the caller should be. 915 * Moreover this method should always be called from the event thread 916 * when being used to write to the screen. 917 */ 918 private void _drawPlotPoint(Graphics graphics, int dataset, int bin, 919 int count) { 920 // Set the color 921 if (_usecolor) { 922 int color = dataset % _colors.length; 923 graphics.setColor(_colors[color]); 924 } else { 925 graphics.setColor(_foreground); 926 } 927 928 double y = count; 929 double x = _binWidth * bin + _binOffset; 930 931 if (_xlog) { 932 if (x <= 0.0) { 933 System.err.println("Can't plot non-positive X values " 934 + "when the logarithmic X axis value is specified: " 935 + x); 936 return; 937 } 938 939 x = Math.log(x) * _LOG10SCALE; 940 } 941 942 if (_ylog) { 943 if (y <= 0.0) { 944 System.err.println("Can't plot non-positive Y values " 945 + "when the logarithmic Y axis value is specified: " 946 + y); 947 return; 948 } 949 950 y = Math.log(y) * _LOG10SCALE; 951 } 952 953 // Use long here because these numbers can be quite large 954 // (when we are zoomed out a lot). 955 long ypos = _lry - (long) ((y - _yMin) * _yscale); 956 long xpos = _ulx + (long) ((x - _xMin) * _xscale); 957 958 _drawBar(graphics, dataset, xpos, ypos, true); 959 960 // Restore the color, in case the box gets redrawn. 961 graphics.setColor(_foreground); 962 } 963 964 /* Rescale so that the data that is currently plotted just fits. 965 * This simply calls the base class. 966 * 967 * This is not synchronized, so the caller should be. Moreover, this 968 * should only be called in the event dispatch thread. It should only 969 * be called by _executeDeferredActions(). 970 */ 971 private void _fillPlot() { 972 super.fillPlot(); 973 } 974 975 /** Schedule a bin to be (re)drawn due to the addition of point. 976 */ 977 private void _scheduleBinRedraw(int dataset, int bin) { 978 while (_scheduledBinsToAdd.size() <= dataset) { 979 _scheduledBinsToAdd.add(new HashSet<Integer>()); 980 } 981 _scheduledBinsToAdd.get(dataset).add(bin); 982 _needBinRedraw = true; 983 } 984 985 /////////////////////////////////////////////////////////////////// 986 //// private variables //// 987 988 /** @serial The width of a bar. */ 989 private volatile double _barwidth = 0.5; 990 991 /** @serial The offset between bars. */ 992 private volatile double _baroffset = 0.15; 993 994 /** @serial The width of a bin. */ 995 private double _binWidth = 1.0; 996 997 /** @serial The offset between bins. */ 998 private volatile double _binOffset = 0.5; 999 1000 // True when a bin has been changed and it need to be repainted 1001 // by the next scheduled repaint. 1002 private boolean _needBinRedraw = false; 1003 1004 // True when a the plot need to be refilled 1005 // by the next scheduled repaint. 1006 private boolean _needPlotRefill = false; 1007 1008 // _scheduledBinsToAdd a a list a bins that should be added by the scheduled 1009 // repaint. 1010 private ArrayList<HashSet<Integer>> _scheduledBinsToAdd = new ArrayList<HashSet<Integer>>(); 1011 1012 /** Random number generator used to create the sample plot. */ 1013 private Random _random = new Random(); 1014 1015 /** @serial Set by _drawPlot(), and reset by clear(). */ 1016 private boolean _showing = false; 1017}