001/* A signal 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 030// TO DO: 031// - steps between points rather than connected lines. 032// - cubic spline interpolation 033// 034// NOTE: The XOR drawing mode is needed in order to be able to erase 035// plotted points and restore the grid line, tick marks, and boundary 036// rectangle. This introduces a number of artifacts, particularly 037// where lines cross. A better alternative in the long run would be 038// use Java 2-D, which treats each notation on the screen as an object, 039// and supports redrawing only damaged regions of the screen. 040// NOTE: There are quite a few subjective spacing parameters, all 041// given, unfortunately, in pixels. This means that as resolutions 042// get better, this program may need to be adjusted. 043import java.awt.BasicStroke; 044import java.awt.Component; 045import java.awt.Graphics; 046import java.awt.Graphics2D; 047import java.awt.Rectangle; 048import java.awt.RenderingHints; 049import java.awt.Stroke; 050import java.io.IOException; 051import java.io.InputStream; 052import java.io.PrintWriter; 053import java.io.Serializable; 054import java.net.URL; 055import java.util.ArrayList; 056import java.util.Formatter; 057import java.util.HashMap; 058import java.util.Locale; 059 060import javax.swing.JComponent; 061 062import ptolemy.util.RunnableExceptionCatcher; 063 064/////////////////////////////////////////////////////////////////// 065//// Plot 066 067/** 068 A flexible signal plotter. The plot can be configured and data can 069 be provided either through a file with commands or through direct 070 invocation of the public methods of the class. 071 <p> 072 When calling the public methods, in most cases the changes will not 073 be visible until paintComponent() is called. To request that this 074 be done, call repaint(). One exception is addPoint(), which 075 makes the new point visible immediately if the plot is visible on 076 the screen and addPoint() is called from the event dispatching thread. 077 <p> 078 This base class supports a simple file syntax that has largely been 079 replaced by the XML-based PlotML syntax. To read a file or a 080 URL in this older syntax, use the read() method. 081 This older syntax contains any number commands, 082 one per line. Unrecognized commands and commands with syntax 083 errors are ignored. Comments are denoted by a line starting with a 084 pound sign "#". The recognized commands include those supported by 085 the base class, plus a few more. The commands are case 086 insensitive, but are usually capitalized. The number of data sets 087 to be plotted does not need to be specified. Data sets are added as needed. 088 Each dataset can be optionally identified with 089 color (see the base class) or with unique marks. The style of 090 marks used to denote a data point is defined by one of the following 091 commands: 092 <pre> 093 Marks: none 094 Marks: points 095 Marks: bigdots 096 Marks: dots 097 Marks: various 098 Marks: pixels 099 </pre> 100 Here, "points" are small dots, while "dots" are larger. If "various" 101 is specified, then unique marks are used for the first ten data sets, 102 and then recycled. If "pixels" are specified, then each point is 103 drawn as one pixel. 104 Using no marks is useful when lines connect the points in a plot, 105 which is done by default. However, if persistence is set, then you 106 may want to choose "pixels" because the lines may overlap, resulting 107 in annoying gaps in the drawn line. 108 If the above directive appears before any DataSet directive, then it 109 specifies the default for all data sets. If it appears after a DataSet 110 directive, then it applies only to that data set. 111 <p> 112 To disable connecting lines, use: 113 <pre> 114 Lines: off 115 </pre> 116 To reenable them, use 117 <pre> 118 Lines: on 119 </pre> 120 You can control the line style on a per dataset basis by adding 121 <pre> 122 LineStyle: solid 123 </pre> 124 Other supported line styles are "dotted", "dashed", "dotdashed" and 125 "dotdotdashed". 126 You can also specify "impulses", which are lines drawn from a plotted point 127 down to the x axis. Plots with impulses are often called "stem plots." 128 These are off by default, but can be turned on with the 129 command: 130 <pre> 131 Impulses: on 132 </pre> 133 or back off with the command 134 <pre> 135 Impulses: off 136 </pre> 137 If that command appears before any DataSet directive, then the command 138 applies to all data sets. Otherwise, it applies only to the current data 139 set. 140 To create a bar graph, turn off lines and use any of the following commands: 141 <pre> 142 Bars: on 143 Bars: <i>width</i> 144 Bars: <i>width, offset</i> 145 </pre> 146 The <i>width</i> is a real number specifying the width of the bars 147 in the units of the x axis. The <i>offset</i> is a real number 148 specifying how much the bar of the <i>i</i><sup>th</sup> data set 149 is offset from the previous one. This allows bars to "peek out" 150 from behind the ones in front. Note that the frontmost data set 151 will be the first one. To turn off bars, use 152 <pre> 153 Bars: off 154 </pre> 155 To specify data to be plotted, start a data set with the following command: 156 <pre> 157 DataSet: <i>string</i> 158 </pre> 159 Here, <i>string</i> is a label that will appear in the legend. 160 It is not necessary to enclose the string in quotation marks. 161 To start a new dataset without giving it a name, use: 162 <pre> 163 DataSet: 164 </pre> 165 In this case, no item will appear in the legend. 166 New datasets are plotted <i>behind</i> the previous ones. 167 If the following directive occurs: 168 <pre> 169 ReuseDataSets: on 170 </pre> 171 Then datasets with the same name will be merged. This makes it 172 easier to combine multiple datafiles that contain the same datasets 173 into one file. By default, this capability is turned off, so 174 datasets with the same name are not merged. 175 The data itself is given by a sequence of commands with one of the 176 following forms: 177 <pre> 178 <i>x</i>, <i>y</i> 179 draw: <i>x</i>, <i>y</i> 180 move: <i>x</i>, <i>y</i> 181 <i>x</i>, <i>y</i>, <i>yLowErrorBar</i>, <i>yHighErrorBar</i> 182 draw: <i>x</i>, <i>y</i>, <i>yLowErrorBar</i>, <i>yHighErrorBar</i> 183 move: <i>x</i>, <i>y</i>, <i>yLowErrorBar</i>, <i>yHighErrorBar</i> 184 </pre> 185 The "draw" command is optional, so the first two forms are equivalent. 186 The "move" command causes a break in connected points, if lines are 187 being drawn between points. The numbers <i>x</i> and <i>y</i> are 188 arbitrary numbers as supported by the Double parser in Java. 189 If there are four numbers, then the last two numbers are assumed to 190 be the lower and upper values for error bars. 191 The numbers can be separated by commas, spaces or tabs. 192 <p>Some of the methods, such as those that add points a plot, are 193 executed in the event thread, possibly some time after they are called. 194 If they are called from a thread different from the event thread, 195 then the order in which changes to the plot take effect may be 196 surprising. We recommend that any code you write that changes 197 the plot in visible ways be executed in the event thread. You 198 can accomplish this using the following template: 199 <pre> 200 Runnable doAction = new Runnable() { 201 public void run() { 202 ... make changes here (e.g. setMarksStyle()) ... 203 } 204 }; 205 synchronized (plot) { 206 plot.deferIfNecessary(doAction); 207 } 208 </pre> 209 Note that deferIfNecessary() is not synchronized, but the caller of 210 deferIfNecessary() should be synchronized on the Plot object. 211 <p> 212 This plotter has some <a name="ptplotLimitations">limitations</a>: 213 <ul> 214 <li> If you zoom in far enough, the plot becomes unreliable. 215 In particular, if the total extent of the plot is more than 216 2<sup>32</sup> times extent of the visible area, quantization 217 errors can result in displaying points or lines. 218 Note that 2<sup>32</sup> is over 4 billion. 219 <li> The limitations of the log axis facility are listed in 220 the <code>_gridInit()</code> method in the PlotBox class. 221 </ul> 222 223 @author Edward A. Lee, Christopher Brooks, Contributor: Tom Peachey, Bert Rodiers 224 @version $Id$ 225 @since Ptolemy II 0.2 226 @Pt.ProposedRating Yellow (cxh) 227 @Pt.AcceptedRating Yellow (cxh) 228 */ 229@SuppressWarnings("serial") 230public class Plot extends PlotBox implements PlotInterface { 231 /////////////////////////////////////////////////////////////////// 232 //// public methods //// 233 234 /** Add a legend (displayed at the upper right) for the specified 235 * data set with the specified string. Short strings generally 236 * fit better than long strings. 237 * @param dataset The dataset index. 238 * @param legend The label for the dataset. 239 */ 240 @Override 241 public synchronized void addLegend(int dataset, String legend) { 242 _checkDatasetIndex(dataset); 243 244 if (!_reuseDatasets) { 245 super.addLegend(dataset, legend); 246 } else { 247 // If _reuseDataSets is true, then look to see if we 248 // already have a dataset with the same legend. 249 String possibleLegend = getLegend(dataset); 250 251 if (possibleLegend == null 252 || (/*possibleLegend != null &&*/!possibleLegend 253 .equals(legend))) { 254 super.addLegend(dataset, legend); 255 } 256 } 257 } 258 259 /** In the specified data set, add the specified x, y point to the 260 * plot. Data set indices begin with zero. If the data set 261 * does not exist, create it. The fourth argument indicates 262 * whether the point should be connected by a line to the previous 263 * point. Regardless of the value of this argument, a line will not 264 * drawn if either there has been no previous point for this dataset 265 * or setConnected() has been called with a false argument. 266 * <p> 267 * In order to work well with swing and be thread safe, this method 268 * actually defers execution to the event dispatch thread, where 269 * all user interface actions are performed. Thus, the point will 270 * not be added immediately (unless you call this method from within 271 * the event dispatch thread). All the methods that do this deferring 272 * coordinate so that they are executed in the order that you 273 * called them. 274 * 275 * @param dataset The data set index. 276 * @param x The X position of the new point. 277 * @param y The Y position of the new point. 278 * @param derivatives The derivatives, if any. 279 * @param connected If true, a line is drawn to connect to the previous 280 * point. 281 */ 282 @Override 283 public synchronized void addPoint(final int dataset, final double x, 284 final double y, final double[] derivatives, 285 final boolean connected) { 286 Runnable doAddPoint = new RunnableExceptionCatcher(new Runnable() { 287 @Override 288 public void run() { 289 _addPoint(dataset, x, y, derivatives, 0, 0, connected, false); 290 } 291 }); 292 293 deferIfNecessary(doAddPoint); 294 } 295 296 /** In the specified data set, add the specified x, y point to the 297 * plot with error bars. Data set indices begin with zero. If 298 * the dataset does not exist, create it. yLowEB and 299 * yHighEB are the lower and upper error bars. The sixth argument 300 * indicates whether the point should be connected by a line to 301 * the previous point. 302 * The new point will be made visible if the plot is visible 303 * on the screen. Otherwise, it will be drawn the next time the plot 304 * is drawn on the screen. 305 * This method is based on a suggestion by 306 * Michael Altmann (michael@email.labmed.umn.edu). 307 * <p> 308 * In order to work well with swing and be thread safe, this method 309 * actually defers execution to the event dispatch thread, where 310 * all user interface actions are performed. Thus, the point will 311 * not be added immediately (unless you call this method from within 312 * the event dispatch thread). All the methods that do this deferring 313 * coordinate so that they are executed in the order that you 314 * called them. 315 * 316 * @param dataset The data set index. 317 * @param x The X position of the new point. 318 * @param y The Y position of the new point. 319 * @param derivatives The derivatives, if any. 320 * @param yLowEB The low point of the error bar. 321 * @param yHighEB The high point of the error bar. 322 * @param connected If true, a line is drawn to connect to the previous 323 * point. 324 */ 325 @Override 326 public synchronized void addPointWithErrorBars(final int dataset, 327 final double x, final double y, final double[] derivatives, 328 final double yLowEB, final double yHighEB, 329 final boolean connected) { 330 Runnable doAddPoint = new RunnableExceptionCatcher(new Runnable() { 331 @Override 332 public void run() { 333 _addPoint(dataset, x, y, derivatives, yLowEB, yHighEB, 334 connected, true); 335 } 336 }); 337 338 deferIfNecessary(doAddPoint); 339 } 340 341 /** Clear the plot of all data points. If the argument is true, then 342 * reset all parameters to their initial conditions, including 343 * the persistence, plotting format, and axes formats. 344 * For the change to take effect, you must call repaint(). 345 * @param format If true, clear the format controls as well. 346 * <p> 347 * In order to work well with swing and be thread safe, this method 348 * actually defers execution to the event dispatch thread, where 349 * all user interface actions are performed. Thus, the clear will 350 * not be executed immediately (unless you call this method from within 351 * the event dispatch thread). All the methods that do this deferring 352 * coordinate so that they are executed in the order that you 353 * called them. 354 */ 355 @Override 356 public synchronized void clear(final boolean format) { 357 Runnable doClear = new RunnableExceptionCatcher(new Runnable() { 358 @Override 359 public void run() { 360 _clear(format); 361 } 362 }); 363 364 deferIfNecessary(doClear); 365 } 366 367 /** Clear the plot of data points in the specified dataset. 368 * This calls repaint() to request an update of the display. 369 * <p> 370 * In order to work well with swing and be thread safe, this method 371 * actually defers execution to the event dispatch thread, where 372 * all user interface actions are performed. Thus, the point will 373 * not be added immediately (unless you call this method from within 374 * the event dispatch thread). If you call this method, the addPoint() 375 * method, and the erasePoint() method in any order, they are assured 376 * of being processed in the order that you called them. 377 * 378 * @param dataset The dataset to clear. 379 */ 380 @Override 381 public synchronized void clear(final int dataset) { 382 Runnable doClear = new RunnableExceptionCatcher(new Runnable() { 383 @Override 384 public void run() { 385 _clear(dataset); 386 } 387 }); 388 389 deferIfNecessary(doClear); 390 } 391 392 /** Erase the point at the given index in the given dataset. If 393 * lines are being drawn, these lines are erased and if necessary new 394 * ones will be drawn. The point is not checked to 395 * see whether it is in range, so care must be taken by the caller 396 * to ensure that it is. 397 * <p> 398 * In order to work well with swing and be thread safe, this method 399 * actually defers execution to the event dispatch thread, where 400 * all user interface actions are performed. Thus, the point will 401 * not be erased immediately (unless you call this method from within 402 * the event dispatch thread). All the methods that do this deferring 403 * coordinate so that they are executed in the order that you 404 * called them. 405 * 406 * @param dataset The data set index. 407 * @param index The index of the point to erase. 408 */ 409 @Override 410 public synchronized void erasePoint(final int dataset, final int index) { 411 Runnable doErasePoint = new RunnableExceptionCatcher(new Runnable() { 412 @Override 413 public void run() { 414 _erasePoint(dataset, index); 415 } 416 }); 417 418 deferIfNecessary(doErasePoint); 419 } 420 421 /** Rescale so that the data that is currently plotted just fits. 422 * This overrides the base class method to ensure that the protected 423 * variables _xBottom, _xTop, _yBottom, and _yTop are valid. 424 * This method calls repaint(), which eventually causes the display 425 * to be updated. 426 * <p> 427 * In order to work well with swing and be thread safe, this method 428 * actually defers execution to the event dispatch thread, where 429 * all user interface actions are performed. Thus, the fill will 430 * not occur immediately (unless you call this method from within 431 * the event dispatch thread). All the methods that do this deferring 432 * coordinate so that they are executed in the order that you 433 * called them. 434 */ 435 @Override 436 public synchronized void fillPlot() { 437 Runnable doFill = new RunnableExceptionCatcher(new Runnable() { 438 @Override 439 public void run() { 440 _fillPlot(); 441 } 442 }); 443 444 deferIfNecessary(doFill); 445 } 446 447 /** Return whether the default is to connect 448 * subsequent points with a line. If the result is false, then 449 * points are not connected. When points are by default 450 * connected, individual points can be not connected by giving the 451 * appropriate argument to addPoint(). Also, a different default 452 * can be set for each dataset, overriding this global default. 453 * @return True if points will be connected by default 454 * @see #setConnected 455 */ 456 @Override 457 public boolean getConnected() { 458 // FindBugs reports "Unsynchronize get method, synchronized 459 // set method". FindBugs reports that this method is 460 // unsyncronized, whereas setConnected(boolean, int) is 461 // syncronized. However, the set* method corresponding to 462 // this method is setConnected(boolean). 463 return _connected; 464 } 465 466 /** Return whether a line will be drawn from any 467 * plotted point down to the x axis. 468 * A plot with such lines is also known as a stem plot. 469 * @return True if this is an impulse plot 470 * @see #setImpulses(boolean) 471 * @see #setImpulses(boolean, int) 472 */ 473 @Override 474 public boolean getImpulses() { 475 // FindBugs reports "Unsynchronize get method, synchronized 476 // set method". FindBugs reports that this method is 477 // unsyncronized, whereas setImpulses(boolean, int) is 478 // syncronized. However, the set* method corresponding to 479 // this method is setImpulses(boolean). 480 return _impulses; 481 } 482 483 /** Return false if setLineStyles() has not yet been called or if 484 * setLineStyles(false) has been called, which signifies that 485 * different line styles are not to be used. Otherwise, return true. 486 * @return True if line styles are to be used. 487 * @see #setLineStyles(boolean) 488 */ 489 @Override 490 public boolean getLineStyles() { 491 // FIXME: should this be syncronized? 492 // FindBugs reports "Unsynchronize get method, synchronized 493 // set method". 494 return _lineStyles; 495 } 496 497 /** Get the marks style, which is one of 498 * "none", "points", "dots", or "various". 499 * @return A string specifying the style for points. 500 * @see #setMarksStyle 501 */ 502 @Override 503 public synchronized String getMarksStyle() { 504 // NOTE: If the number of marks increases, we will need to do 505 // something better here... 506 if (_marks == 0) { 507 return "none"; 508 } else if (_marks == 1) { 509 return "points"; 510 } else if (_marks == 2) { 511 return "dots"; 512 } else if (_marks == 3) { 513 return "various"; 514 } else if (_marks == 4) { 515 return "bigdots"; 516 } else { 517 return "pixels"; 518 } 519 } 520 521 /** Return the maximum number of data sets. 522 * This method is deprecated, since there is no longer an upper bound. 523 * @return The maximum number of data sets 524 * @deprecated 525 */ 526 @Override 527 @Deprecated 528 public int getMaxDataSets() { 529 return Integer.MAX_VALUE; 530 } 531 532 /** Return the actual number of data sets. 533 * @return The number of data sets that have been created. 534 */ 535 @Override 536 public synchronized int getNumDataSets() { 537 return _points.size(); 538 } 539 540 /** Return false if setReuseDatasets() has not yet been called 541 * or if setReuseDatasets(false) has been called. 542 * @return false if setReuseDatasets() has not yet been called 543 * or if setReuseDatasets(false) has been called. 544 * @since Ptplot 5.3 545 * @see #setReuseDatasets(boolean) 546 */ 547 @Override 548 public boolean getReuseDatasets() { 549 return _reuseDatasets; 550 } 551 552 /** Override the base class to indicate that a new data set is being read. 553 * This method is deprecated. Use read() instead (to read the old 554 * file format) or one of the classes in the plotml package to read 555 * the new (XML) file format. 556 * @deprecated 557 */ 558 @Override 559 @Deprecated 560 public void parseFile(String filespec, URL documentBase) { 561 _firstInSet = true; 562 _sawFirstDataSet = false; 563 super.parseFile(filespec, documentBase); 564 } 565 566 /** Mark the disconnections with a Dot in case value equals true, otherwise these 567 * points are not marked. 568 * @param value True when disconnections should be marked. 569 */ 570 @Override 571 public void markDisconnections(boolean value) { 572 _markDisconnections = value; 573 } 574 575 /** Read a file with the old syntax (non-XML). 576 * Override the base class to register that we are reading a new 577 * data set. 578 * @param inputStream The input stream. 579 * @exception IOException If the stream cannot be read. 580 */ 581 @Override 582 public synchronized void read(InputStream inputStream) throws IOException { 583 super.read(inputStream); 584 _firstInSet = true; 585 _sawFirstDataSet = false; 586 } 587 588 /** Create a sample plot. This is not actually done immediately 589 * unless the calling thread is the event dispatch thread. 590 * Instead, it is deferred to the event dispatch thread. 591 * It is important that the calling thread not hold a synchronize 592 * lock on the Plot object, or deadlock will result (unless the 593 * calling thread is the event dispatch thread). 594 */ 595 @Override 596 public synchronized void samplePlot() { 597 // This needs to be done in the event thread. 598 Runnable sample = new RunnableExceptionCatcher(new Runnable() { 599 @Override 600 public void run() { 601 synchronized (Plot.this) { 602 // Create a sample plot. 603 clear(true); 604 605 setTitle("Sample plot"); 606 setYRange(-4, 4); 607 setXRange(0, 100); 608 setXLabel("time"); 609 setYLabel("value"); 610 addYTick("-PI", -Math.PI); 611 addYTick("-PI/2", -Math.PI / 2); 612 addYTick("0", 0); 613 addYTick("PI/2", Math.PI / 2); 614 addYTick("PI", Math.PI); 615 setMarksStyle("none"); 616 setImpulses(true); 617 618 boolean first = true; 619 620 for (int i = 0; i <= 100; i++) { 621 double xvalue = i; 622 623 // NOTE: jdk 1.3beta has a bug exhibited here. 624 // The value of the second argument in the calls 625 // to addPoint() below is corrupted the second 626 // time that this method is called. The print 627 // statement below shows that the value is 628 // correct before the call. 629 // System.out.println("x value: " + xvalue); 630 // For some bizarre reason, this problem goes 631 // away when this code is executed in the event 632 // dispatch thread. 633 addPoint(0, xvalue, 5 * Math.cos(Math.PI * i / 20), 634 !first); 635 addPoint(1, xvalue, 4.5 * Math.cos(Math.PI * i / 25), 636 !first); 637 addPoint(2, xvalue, 4 * Math.cos(Math.PI * i / 30), 638 !first); 639 addPoint(3, xvalue, 3.5 * Math.cos(Math.PI * i / 35), 640 !first); 641 addPoint(4, xvalue, 3 * Math.cos(Math.PI * i / 40), 642 !first); 643 addPoint(5, xvalue, 2.5 * Math.cos(Math.PI * i / 45), 644 !first); 645 addPoint(6, xvalue, 2 * Math.cos(Math.PI * i / 50), 646 !first); 647 addPoint(7, xvalue, 1.5 * Math.cos(Math.PI * i / 55), 648 !first); 649 addPoint(8, xvalue, 1 * Math.cos(Math.PI * i / 60), 650 !first); 651 addPoint(9, xvalue, 0.5 * Math.cos(Math.PI * i / 65), 652 !first); 653 first = false; 654 } // for 655 } // synchronized 656 657 repaint(); 658 } // run method 659 }); // Runnable class 660 661 deferIfNecessary(sample); 662 } 663 664 /** Turn bars on or off (for bar charts). Note that this is a global 665 * property, not per dataset. 666 * @param on If true, turn bars on. 667 */ 668 @Override 669 public void setBars(boolean on) { 670 // Ensure replot of offscreen buffer. 671 _plotImage = null; 672 _bars = on; 673 } 674 675 /** Turn bars on and set the width and offset. Both are specified 676 * in units of the x axis. The offset is the amount by which the 677 * i <sup>th</sup> data set is shifted to the right, so that it 678 * peeks out from behind the earlier data sets. 679 * @param width The width of the bars. 680 * @param offset The offset per data set. 681 */ 682 @Override 683 public synchronized void setBars(double width, double offset) { 684 // Ensure replot of offscreen buffer. 685 _plotImage = null; 686 _barWidth = width; 687 _barOffset = offset; 688 _bars = true; 689 } 690 691 /** If the argument is true, then the default is to connect 692 * subsequent points with a line. If the argument is false, then 693 * points are not connected. When points are by default 694 * connected, individual points can be not connected by giving the 695 * appropriate argument to addPoint(). Also, a different default 696 * can be set for each dataset, overriding this global default. 697 * setConnected will also change the behavior of points that were 698 * already drawn if the graph is redrawn. If it isn't the points 699 * are not touched. If you change back the setConnected state, 700 * the again see what was visible before. 701 * @param on If true, draw lines between points. 702 * @see #setConnected(boolean, int) 703 * @see #getConnected 704 */ 705 @Override 706 public void setConnected(boolean on) { 707 // Ensure replot of offscreen buffer. 708 _plotImage = null; 709 _connected = on; 710 } 711 712 /** If the first argument is true, then by default for the specified 713 * dataset, points will be connected by a line. Otherwise, the 714 * points will not be connected. When points are by default 715 * connected, individual points can be not connected by giving the 716 * appropriate argument to addPoint(). 717 * Note that this method should be called before adding any points. 718 * Note further that this method should probably be called from 719 * the event thread. 720 * @param on If true, draw lines between points. 721 * @param dataset The dataset to which this should apply. 722 * @see #setConnected(boolean) 723 * @see #getConnected 724 */ 725 @Override 726 public synchronized void setConnected(boolean on, int dataset) { 727 // Ensure replot of offscreen buffer. 728 _plotImage = null; 729 _checkDatasetIndex(dataset); 730 731 Format fmt = _formats.get(dataset); 732 fmt.connected = on; 733 fmt.connectedUseDefault = false; 734 } 735 736 /** If the argument is true, then a line will be drawn from any 737 * plotted point down to the x axis. Otherwise, this feature is 738 * disabled. A plot with such lines is also known as a stem plot. 739 * @param on If true, draw a stem plot. 740 * @see #getImpulses 741 */ 742 @Override 743 public synchronized void setImpulses(boolean on) { 744 // Ensure replot of offscreen buffer. 745 _plotImage = null; 746 _impulses = on; 747 } 748 749 /** If the first argument is true, then a line will be drawn from any 750 * plotted point in the specified dataset down to the x axis. 751 * Otherwise, this feature is 752 * disabled. A plot with such lines is also known as a stem plot. 753 * @param on If true, draw a stem plot. 754 * @param dataset The dataset to which this should apply. 755 * @see #getImpulses 756 */ 757 @Override 758 public synchronized void setImpulses(boolean on, int dataset) { 759 // Ensure replot of offscreen buffer. 760 _plotImage = null; 761 _checkDatasetIndex(dataset); 762 763 Format fmt = _formats.get(dataset); 764 fmt.impulses = on; 765 fmt.impulsesUseDefault = false; 766 } 767 768 /** Set the style of the lines joining marks. 769 * @param styleString A string specifying the color for points. 770 * The following styles are permitted: "solid", "dotted", 771 * "dashed", "dotdashed", "dotdotdashed". 772 * @param dataset The data set index. 773 */ 774 @Override 775 public synchronized void setLineStyle(String styleString, int dataset) { 776 float[] dashvalues; 777 // Ensure replot of offscreen buffer. 778 _plotImage = null; 779 _checkDatasetIndex(dataset); 780 781 Format format = _formats.get(dataset); 782 if (styleString.equalsIgnoreCase("solid")) { 783 format.lineStroke = new BasicStroke(_width, BasicStroke.CAP_BUTT, 784 BasicStroke.JOIN_BEVEL, 0); 785 ///_graphics.setStroke(stroke); 786 } else if (styleString.equalsIgnoreCase("dotted")) { 787 dashvalues = new float[2]; 788 dashvalues[0] = (float) 2.0; 789 dashvalues[1] = (float) 2.0; 790 format.lineStroke = new BasicStroke(_width, BasicStroke.CAP_BUTT, 791 BasicStroke.JOIN_BEVEL, 0, dashvalues, 0); 792 } else if (styleString.equalsIgnoreCase("dashed")) { 793 dashvalues = new float[2]; 794 dashvalues[0] = (float) 8.0; 795 dashvalues[1] = (float) 4.0; 796 format.lineStroke = new BasicStroke(_width, BasicStroke.CAP_ROUND, 797 BasicStroke.JOIN_BEVEL, 0, dashvalues, 0); 798 } else if (styleString.equalsIgnoreCase("dotdashed")) { 799 dashvalues = new float[4]; 800 dashvalues[0] = (float) 2.0; 801 dashvalues[1] = (float) 2.0; 802 dashvalues[2] = (float) 8.0; 803 dashvalues[3] = (float) 2.0; 804 format.lineStroke = new BasicStroke(_width, BasicStroke.CAP_BUTT, 805 BasicStroke.JOIN_BEVEL, 0, dashvalues, 0); 806 } else if (styleString.equalsIgnoreCase("dotdotdashed")) { 807 dashvalues = new float[6]; 808 dashvalues[0] = (float) 2.0; 809 dashvalues[1] = (float) 2.0; 810 dashvalues[2] = (float) 2.0; 811 dashvalues[3] = (float) 2.0; 812 dashvalues[4] = (float) 8.0; 813 dashvalues[5] = (float) 2.0; 814 format.lineStroke = new BasicStroke(_width, BasicStroke.CAP_BUTT, 815 BasicStroke.JOIN_BEVEL, 0, dashvalues, 0); 816 } else { 817 StringBuffer results = new StringBuffer(); 818 for (String style : java.util.Arrays.asList(_LINE_STYLES_ARRAY)) { 819 if (results.length() > 0) { 820 results.append(", "); 821 } 822 results.append("\"" + style + "\""); 823 } 824 throw new IllegalArgumentException("Line style \"" + styleString 825 + "\" is not found, style must be one of " + results); 826 } 827 format.lineStyle = styleString; 828 format.lineStyleUseDefault = false; 829 } 830 831 /** If the argument is true, draw the data sets with different line 832 * styles. Otherwise, use one line style. 833 * @param lineStyles True if the data sets are to be drawn in different 834 * line styles. 835 * @see #getLineStyles() 836 */ 837 @Override 838 public synchronized void setLineStyles(boolean lineStyles) { 839 // Ensure replot of offscreen buffer. 840 _plotImage = null; 841 _lineStyles = lineStyles; 842 if (!_lineStyles) { 843 for (Format fmt : _formats) { 844 fmt.lineStyle = null; 845 fmt.lineStroke = null; 846 fmt.lineStyleUseDefault = true; 847 } 848 } 849 } 850 851 /** Set the marks style to "none", "points", "dots", or "various". 852 * In the last case, unique marks are used for the first ten data 853 * sets, then recycled. 854 * This method should be called only from the event dispatch thread. 855 * @param style A string specifying the style for points. 856 * @see #getMarksStyle 857 */ 858 @Override 859 public synchronized void setMarksStyle(String style) { 860 // Ensure replot of offscreen buffer. 861 _plotImage = null; 862 863 if (style.equalsIgnoreCase("none")) { 864 _marks = 0; 865 } else if (style.equalsIgnoreCase("points")) { 866 _marks = 1; 867 } else if (style.equalsIgnoreCase("dots")) { 868 _marks = 2; 869 } else if (style.equalsIgnoreCase("various")) { 870 _marks = 3; 871 } else if (style.equalsIgnoreCase("bigdots")) { 872 _marks = 4; 873 } else if (style.equalsIgnoreCase("pixels")) { 874 _marks = 5; 875 } 876 } 877 878 /** Set the marks style to "none", "points", "dots", "various", 879 * or "pixels" for the specified dataset. 880 * In the last case, unique marks are used for the first ten data 881 * sets, then recycled. 882 * @param style A string specifying the style for points. 883 * @param dataset The dataset to which this should apply. 884 * @see #getMarksStyle 885 */ 886 @Override 887 public synchronized void setMarksStyle(String style, int dataset) { 888 // Ensure replot of offscreen buffer. 889 _plotImage = null; 890 _checkDatasetIndex(dataset); 891 892 Format fmt = _formats.get(dataset); 893 894 if (style.equalsIgnoreCase("none")) { 895 fmt.marks = 0; 896 } else if (style.equalsIgnoreCase("points")) { 897 fmt.marks = 1; 898 } else if (style.equalsIgnoreCase("dots")) { 899 fmt.marks = 2; 900 } else if (style.equalsIgnoreCase("various")) { 901 fmt.marks = 3; 902 } else if (style.equalsIgnoreCase("bigdots")) { 903 fmt.marks = 4; 904 } else if (style.equalsIgnoreCase("pixels")) { 905 fmt.marks = 5; 906 } 907 908 fmt.marksUseDefault = false; 909 } 910 911 /** Specify the number of data sets to be plotted together. 912 * This method is deprecated, since it is no longer necessary to 913 * specify the number of data sets ahead of time. 914 * @param numSets The number of data sets. 915 * @deprecated 916 */ 917 @Override 918 @Deprecated 919 public void setNumSets(int numSets) { 920 // Ensure replot of offscreen buffer. 921 _plotImage = null; 922 923 if (numSets < 1) { 924 throw new IllegalArgumentException("Number of data sets (" + numSets 925 + ") must be greater than 0."); 926 } 927 928 _currentdataset = -1; 929 _points.clear(); 930 _bins.clear(); 931 _formats.clear(); 932 _prevxpos.clear(); 933 _prevypos.clear(); 934 _prevErasedxpos.clear(); 935 _prevErasedypos.clear(); 936 _lastPointWithExtraDot.clear(); 937 938 for (int i = 0; i < numSets; i++) { 939 _points.add(new ArrayList<PlotPoint>()); 940 _formats.add(new Format()); 941 _prevxpos.add(_INITIAL_PREVIOUS_VALUE); 942 _prevypos.add(_INITIAL_PREVIOUS_VALUE); 943 _prevErasedxpos.add(_INITIAL_PREVIOUS_VALUE); 944 _prevErasedypos.add(_INITIAL_PREVIOUS_VALUE); 945 _lastPointWithExtraDot.put(i, null); 946 } 947 } 948 949 /** Calling this method with a positive argument sets the 950 * persistence of the plot to the given number of points. Calling 951 * with a zero argument turns off this feature, reverting to 952 * infinite memory (unless sweeps persistence is set). If both 953 * sweeps and points persistence are set then sweeps take 954 * precedence. 955 * <p> 956 * Setting the persistence greater than zero forces the plot to 957 * be drawn in XOR mode, which allows points to be quickly and 958 * efficiently erased. However, there is a bug in Java (as of 959 * version 1.3), where XOR mode does not work correctly with 960 * double buffering. Thus, if you call this with an argument 961 * greater than zero, then we turn off double buffering for this 962 * panel <i>and all of its parents</i>. This actually happens 963 * on the next call to addPoint(). 964 * @param persistence Number of points to persist for. 965 */ 966 @Override 967 public void setPointsPersistence(int persistence) { 968 // Ensure replot of offscreen buffer. 969 _plotImage = null; 970 971 // NOTE: No file format. It's not clear it makes sense to have one. 972 _pointsPersistence = persistence; 973 } 974 975 /** If the argument is true, then datasets with the same name 976 * are merged into a single dataset. 977 * @param on If true, then merge datasets. 978 * @see #getReuseDatasets() 979 */ 980 @Override 981 public void setReuseDatasets(boolean on) { 982 // Ensure replot of offscreen buffer. 983 _plotImage = null; 984 _reuseDatasets = on; 985 } 986 987 /** Calling this method with a positive argument sets the 988 * persistence of the plot to the given width in units of the 989 * horizontal axis. Calling 990 * with a zero argument turns off this feature, reverting to 991 * infinite memory (unless points persistence is set). If both 992 * X and points persistence are set then both are applied, 993 * meaning that points that are old by either criterion will 994 * be erased. 995 * <p> 996 * Setting the X persistence greater than zero forces the plot to 997 * be drawn in XOR mode, which allows points to be quickly and 998 * efficiently erased. However, there is a bug in Java (as of 999 * version 1.3), where XOR mode does not work correctly with 1000 * double buffering. Thus, if you call this with an argument 1001 * greater than zero, then we turn off double buffering for this 1002 * panel <i>and all of its parents</i>. This actually happens 1003 * on the next call to addPoint(). 1004 * @param persistence Persistence in units of the horizontal axis. 1005 */ 1006 @Override 1007 public void setXPersistence(double persistence) { 1008 // Ensure replot of offscreen buffer. 1009 _plotImage = null; 1010 1011 // NOTE: No file format. It's not clear it makes sense to have one. 1012 _xPersistence = persistence; 1013 } 1014 1015 /** Write plot data information to the specified output stream in PlotML. 1016 * @param output A buffered print writer. 1017 */ 1018 @Override 1019 public synchronized void writeData(PrintWriter output) { 1020 super.writeData(output); 1021 1022 for (int dataset = 0; dataset < _points.size(); dataset++) { 1023 StringBuffer options = new StringBuffer(); 1024 1025 Format fmt = _formats.get(dataset); 1026 1027 if (!fmt.connectedUseDefault) { 1028 if (_isConnected(dataset)) { 1029 options.append(" connected=\"yes\""); 1030 } else { 1031 options.append(" connected=\"no\""); 1032 } 1033 } 1034 1035 if (!fmt.impulsesUseDefault) { 1036 if (fmt.impulses) { 1037 options.append(" stems=\"yes\""); 1038 } else { 1039 output.println(" stems=\"no\""); 1040 } 1041 } 1042 1043 if (!fmt.lineStyleUseDefault && fmt.lineStyle.length() > 0) { 1044 options.append(" lineStyle=\"" + fmt.lineStyle + "\""); 1045 } 1046 1047 String legend = getLegend(dataset); 1048 1049 if (legend != null) { 1050 options.append(" name=\"" + getLegend(dataset) + "\""); 1051 } 1052 1053 output.println("<dataset" + options.toString() + ">"); 1054 1055 // Write the data 1056 ArrayList<PlotPoint> pts = _points.get(dataset); 1057 1058 for (int pointnum = 0; pointnum < pts.size(); pointnum++) { 1059 PlotPoint pt = pts.get(pointnum); 1060 1061 if (!pt.connected) { 1062 output.print("<m "); 1063 } else { 1064 output.print("<p "); 1065 } 1066 1067 output.print("x=\"" + pt.x + "\" y=\"" + pt.y + "\""); 1068 1069 if (pt.errorBar) { 1070 output.print(" lowErrorBar=\"" + pt.yLowEB 1071 + "\" highErrorBar=\"" + pt.yHighEB + "\""); 1072 } 1073 1074 output.println("/>"); 1075 } 1076 1077 output.println("</dataset>"); 1078 } 1079 } 1080 1081 /** Write plot format information to the specified output stream in 1082 * PlotML, an XML scheme. 1083 * @param output A buffered print writer. 1084 */ 1085 @Override 1086 public synchronized void writeFormat(PrintWriter output) { 1087 super.writeFormat(output); 1088 1089 if (_reuseDatasets) { 1090 output.println("<reuseDatasets/>"); 1091 } 1092 1093 StringBuffer defaults = new StringBuffer(); 1094 1095 if (!_connected) { 1096 defaults.append(" connected=\"no\""); 1097 } 1098 1099 if (_lineStyles) { 1100 defaults.append(" lineStyles=\"yes\""); 1101 } 1102 1103 switch (_marks) { 1104 case 0: 1105 // Ignore, marks = none. 1106 break; 1107 case 1: 1108 defaults.append(" marks=\"points\""); 1109 break; 1110 1111 case 2: 1112 defaults.append(" marks=\"dots\""); 1113 break; 1114 1115 case 3: 1116 defaults.append(" marks=\"various\""); 1117 break; 1118 1119 case 4: 1120 defaults.append(" marks=\"bigdots\""); 1121 break; 1122 1123 case 5: 1124 defaults.append(" marks=\"pixels\""); 1125 break; 1126 default: 1127 throw new RuntimeException("Internal Error. Mark " + "style " 1128 + _marks + " not supported."); 1129 } 1130 1131 // Write the defaults for formats that can be controlled by dataset 1132 if (_impulses) { 1133 defaults.append(" stems=\"yes\""); 1134 } 1135 1136 if (defaults.length() > 0) { 1137 output.println("<default" + defaults.toString() + "/>"); 1138 } 1139 1140 if (_bars) { 1141 output.println("<barGraph width=\"" + _barWidth + "\" offset=\"" 1142 + _barOffset + "\"/>"); 1143 } 1144 } 1145 1146 /////////////////////////////////////////////////////////////////// 1147 //// protected methods //// 1148 1149 /** Check the argument to ensure that it is a valid data set index. 1150 * If it is less than zero, throw an IllegalArgumentException (which 1151 * is a runtime exception). If it does not refer to an existing 1152 * data set, then fill out the _points Vector so that it does refer 1153 * to an existing data set. All other dataset-related vectors are 1154 * similarly filled out. 1155 * @param dataset The data set index. 1156 */ 1157 protected synchronized void _checkDatasetIndex(int dataset) { 1158 if (dataset < 0) { 1159 throw new IllegalArgumentException("Plot._checkDatasetIndex: Cannot" 1160 + " give a negative number for the data set index."); 1161 } 1162 1163 while (dataset >= _points.size()) { 1164 _points.add(new ArrayList<PlotPoint>()); 1165 _bins.add(new ArrayList<Bin>()); 1166 _pointInBinOffset.add(0); 1167 _formats.add(new Format()); 1168 _prevxpos.add(_INITIAL_PREVIOUS_VALUE); 1169 _prevypos.add(_INITIAL_PREVIOUS_VALUE); 1170 _prevErasedxpos.add(_INITIAL_PREVIOUS_VALUE); 1171 _prevErasedypos.add(_INITIAL_PREVIOUS_VALUE); 1172 } 1173 } 1174 1175 /** Draw bar from the specified point to the y axis. 1176 * If the specified point is below the y axis or outside the 1177 * x range, do nothing. If the <i>clip</i> argument is true, 1178 * then do not draw above the y range. 1179 * Note that paintComponent() should be called before 1180 * calling this method so that _xscale and _yscale are properly set. 1181 * This method should be called only from the event dispatch thread. 1182 * It is not synchronized, so its caller should be. 1183 * @param graphics The graphics context. 1184 * @param dataset The index of the dataset. 1185 * @param xpos The x position. 1186 * @param ypos The y position. 1187 * @param clip If true, then do not draw outside the range. 1188 */ 1189 protected void _drawBar(Graphics graphics, int dataset, long xpos, 1190 long ypos, boolean clip) { 1191 if (clip) { 1192 if (ypos < _uly) { 1193 ypos = _uly; 1194 } 1195 1196 if (ypos > _lry) { 1197 ypos = _lry; 1198 } 1199 } 1200 1201 if (ypos <= _lry && xpos <= _lrx && xpos >= _ulx) { 1202 // left x position of bar. 1203 int barlx = (int) (xpos - _barWidth * _xscale / 2 1204 + dataset * _barOffset * _xscale); 1205 1206 // right x position of bar 1207 int barrx = (int) (barlx + _barWidth * _xscale); 1208 1209 if (barlx < _ulx) { 1210 barlx = _ulx; 1211 } 1212 1213 if (barrx > _lrx) { 1214 barrx = _lrx; 1215 } 1216 1217 // Make sure that a bar is always at least one pixel wide. 1218 if (barlx >= barrx) { 1219 barrx = barlx + 1; 1220 } 1221 1222 // The y position of the zero line. 1223 long zeroypos = _lry - (long) ((0 - _yMin) * _yscale); 1224 1225 if (_lry < zeroypos) { 1226 zeroypos = _lry; 1227 } 1228 1229 if (_uly > zeroypos) { 1230 zeroypos = _uly; 1231 } 1232 1233 if (_yMin >= 0 || ypos <= zeroypos) { 1234 graphics.fillRect(barlx, (int) ypos, barrx - barlx, 1235 (int) (zeroypos - ypos)); 1236 } else { 1237 graphics.fillRect(barlx, (int) zeroypos, barrx - barlx, 1238 (int) (ypos - zeroypos)); 1239 } 1240 } 1241 } 1242 1243 /** Draw an error bar for the specified yLowEB and yHighEB values. 1244 * If the specified point is below the y axis or outside the 1245 * x range, do nothing. If the <i>clip</i> argument is true, 1246 * then do not draw above the y range. 1247 * This method should be called only from the event dispatch thread. 1248 * It is not synchronized, so its caller should be. 1249 * @param graphics The graphics context. 1250 * @param dataset The index of the dataset. 1251 * @param xpos The x position. 1252 * @param yLowEBPos The lower y position of the error bar. 1253 * @param yHighEBPos The upper y position of the error bar. 1254 * @param clip If true, then do not draw above the range. 1255 */ 1256 protected void _drawErrorBar(Graphics graphics, int dataset, long xpos, 1257 long yLowEBPos, long yHighEBPos, boolean clip) { 1258 _drawLine(graphics, dataset, xpos - _ERRORBAR_LEG_LENGTH, yHighEBPos, 1259 xpos + _ERRORBAR_LEG_LENGTH, yHighEBPos, clip); 1260 _drawLine(graphics, dataset, xpos, yLowEBPos, xpos, yHighEBPos, clip); 1261 _drawLine(graphics, dataset, xpos - _ERRORBAR_LEG_LENGTH, yLowEBPos, 1262 xpos + _ERRORBAR_LEG_LENGTH, yLowEBPos, clip); 1263 } 1264 1265 /** Draw a line from the specified point to the y axis. 1266 * If the specified point is below the y axis or outside the 1267 * x range, do nothing. If the <i>clip</i> argument is true, 1268 * then do not draw above the y range. 1269 * This method should be called only from the event dispatch thread. 1270 * It is not synchronized, so its caller should be. 1271 * @param graphics The graphics context. 1272 * @param xpos The x position. 1273 * @param ypos The y position. 1274 * @param clip If true, then do not draw outside the range. 1275 */ 1276 protected void _drawImpulse(Graphics graphics, long xpos, long ypos, 1277 boolean clip) { 1278 if (clip) { 1279 if (ypos < _uly) { 1280 ypos = _uly; 1281 } 1282 1283 if (ypos > _lry) { 1284 ypos = _lry; 1285 } 1286 } 1287 1288 if (ypos <= _lry && xpos <= _lrx && xpos >= _ulx) { 1289 // The y position of the zero line. 1290 double zeroypos = _lry - (long) ((0 - _yMin) * _yscale); 1291 1292 if (_lry < zeroypos) { 1293 zeroypos = _lry; 1294 } 1295 1296 if (_uly > zeroypos) { 1297 zeroypos = _uly; 1298 } 1299 1300 _setWidth(graphics, 1f); 1301 graphics.drawLine((int) xpos, (int) ypos, (int) xpos, 1302 (int) zeroypos); 1303 } 1304 } 1305 1306 /** Draw a line from the specified starting point to the specified 1307 * ending point. The current color is used. If the <i>clip</i> argument 1308 * is true, then draw only that portion of the line that lies within the 1309 * plotting rectangle. This method draws a line one pixel wide. 1310 * This method should be called only from the event dispatch thread. 1311 * It is not synchronized, so its caller should be. 1312 * @param graphics The graphics context. 1313 * @param dataset The index of the dataset. 1314 * @param startx The starting x position. 1315 * @param starty The starting y position. 1316 * @param endx The ending x position. 1317 * @param endy The ending y position. 1318 * @param clip If true, then do not draw outside the range. 1319 */ 1320 protected void _drawLine(Graphics graphics, int dataset, long startx, 1321 long starty, long endx, long endy, boolean clip) { 1322 _drawLine(graphics, dataset, startx, starty, endx, endy, clip, 1f); 1323 } 1324 1325 /** Draw a line from the specified starting point to the specified 1326 * ending point. The current color is used. If the <i>clip</i> argument 1327 * is true, then draw only that portion of the line that lies within the 1328 * plotting rectangle. The width argument is ignored if the graphics 1329 * argument is not an instance of Graphics2D. 1330 * This method should be called only from the event dispatch thread. 1331 * It is not synchronized, so its caller should be. 1332 * @param graphics The graphics context. 1333 * @param dataset The index of the dataset. 1334 * @param startx The starting x position. 1335 * @param starty The starting y position. 1336 * @param endx The ending x position. 1337 * @param endy The ending y position. 1338 * @param clip If true, then do not draw outside the range. 1339 * @param width The thickness of the line. 1340 */ 1341 protected void _drawLine(Graphics graphics, int dataset, long startx, 1342 long starty, long endx, long endy, boolean clip, float width) { 1343 _setWidth(graphics, width); 1344 1345 Format format = _formats.get(dataset); 1346 Stroke previousStroke = null; 1347 if (!format.lineStyleUseDefault && graphics instanceof Graphics2D) { 1348 previousStroke = ((Graphics2D) graphics).getStroke(); 1349 // Draw a dashed or dotted line 1350 ((Graphics2D) graphics).setStroke(format.lineStroke); 1351 } 1352 1353 if (clip) { 1354 // Rule out impossible cases. 1355 if (!(endx <= _ulx && startx <= _ulx 1356 || endx >= _lrx && startx >= _lrx 1357 || endy <= _uly && starty <= _uly 1358 || endy >= _lry && starty >= _lry)) { 1359 // If the end point is out of x range, adjust 1360 // end point to boundary. 1361 // The integer arithmetic has to be done with longs so as 1362 // to not loose precision on extremely close zooms. 1363 if (startx != endx) { 1364 if (endx < _ulx) { 1365 endy = (int) (endy + (starty - endy) * (_ulx - endx) 1366 / (startx - endx)); 1367 endx = _ulx; 1368 } else if (endx > _lrx) { 1369 endy = (int) (endy + (starty - endy) * (_lrx - endx) 1370 / (startx - endx)); 1371 endx = _lrx; 1372 } 1373 } 1374 1375 // If end point is out of y range, adjust to boundary. 1376 // Note that y increases downward 1377 if (starty != endy) { 1378 if (endy < _uly) { 1379 endx = (int) (endx + (startx - endx) * (_uly - endy) 1380 / (starty - endy)); 1381 endy = _uly; 1382 } else if (endy > _lry) { 1383 endx = (int) (endx + (startx - endx) * (_lry - endy) 1384 / (starty - endy)); 1385 endy = _lry; 1386 } 1387 } 1388 1389 // Adjust current point to lie on the boundary. 1390 if (startx != endx) { 1391 if (startx < _ulx) { 1392 starty = (int) (starty + (endy - starty) 1393 * (_ulx - startx) / (endx - startx)); 1394 startx = _ulx; 1395 } else if (startx > _lrx) { 1396 starty = (int) (starty + (endy - starty) 1397 * (_lrx - startx) / (endx - startx)); 1398 startx = _lrx; 1399 } 1400 } 1401 1402 if (starty != endy) { 1403 if (starty < _uly) { 1404 startx = (int) (startx + (endx - startx) 1405 * (_uly - starty) / (endy - starty)); 1406 starty = _uly; 1407 } else if (starty > _lry) { 1408 startx = (int) (startx + (endx - startx) 1409 * (_lry - starty) / (endy - starty)); 1410 starty = _lry; 1411 } 1412 } 1413 } 1414 1415 // Are the new points in range? 1416 if (endx >= _ulx && endx <= _lrx && endy >= _uly && endy <= _lry 1417 && startx >= _ulx && startx <= _lrx && starty >= _uly 1418 && starty <= _lry) { 1419 graphics.drawLine((int) startx, (int) starty, (int) endx, 1420 (int) endy); 1421 } 1422 } else { 1423 // draw unconditionally. 1424 graphics.drawLine((int) startx, (int) starty, (int) endx, 1425 (int) endy); 1426 } 1427 if (previousStroke != null) { 1428 ((Graphics2D) graphics).setStroke(previousStroke); 1429 } 1430 } 1431 1432 /** Draw the axes and then plot all points. If the second 1433 * argument is true, clear the display first. 1434 * This method is called by paintComponent(). 1435 * To cause it to be called you would normally call repaint(), 1436 * which eventually causes paintComponent() to be called. 1437 * <p> 1438 * Note that this is synchronized so that points are not added 1439 * by other threads while the drawing is occurring. This method 1440 * should be called only from the event dispatch thread, consistent 1441 * with swing policy. 1442 * @param graphics The graphics context. 1443 * @param clearfirst If true, clear the plot before proceeding. 1444 * @param drawRectangle The Rectangle to draw in. 1445 */ 1446 @Override 1447 protected synchronized void _drawPlot(Graphics graphics, boolean clearfirst, 1448 Rectangle drawRectangle) { 1449 // BRDebug System.err.println("_drawPlot(begin)"); 1450 if (_graphics == null) { 1451 _graphics = graphics; 1452 } else if (graphics != _graphics) { 1453 // If the graphics has changed, then we don't care about 1454 // the previous values. Exporting to EPS uses a different 1455 // graphics, see test/onePointStem.plt for an example that 1456 // requires this change. 1457 _graphics = graphics; 1458 _prevxpos.clear(); 1459 _prevypos.clear(); 1460 _prevErasedxpos.clear(); 1461 _prevErasedypos.clear(); 1462 _lastPointWithExtraDot.clear(); 1463 for (int dataset = 0; dataset < _points.size(); dataset++) { 1464 _prevxpos.add(_INITIAL_PREVIOUS_VALUE); 1465 _prevypos.add(_INITIAL_PREVIOUS_VALUE); 1466 _prevErasedxpos.add(_INITIAL_PREVIOUS_VALUE); 1467 _prevErasedypos.add(_INITIAL_PREVIOUS_VALUE); 1468 _lastPointWithExtraDot.put(dataset, null); 1469 } 1470 } 1471 1472 // We must call PlotBox._drawPlot() before calling _drawPlotPoint 1473 // so that _xscale and _yscale are set. 1474 super._drawPlot(graphics, clearfirst, drawRectangle); 1475 1476 // Divide the points into different Bins. This should be done each time 1477 // _xscale and _yscale are set 1478 _dividePointsIntoBins(); 1479 1480 // Plot the points in reverse order so that the first colors 1481 // appear on top. 1482 for (int dataset = _bins.size() - 1; dataset >= 0; dataset--) { 1483 ArrayList<Bin> data = _bins.get(dataset); 1484 1485 int numberOfBins = data.size(); 1486 1487 for (int binnum = 0; binnum < numberOfBins; binnum++) { 1488 _drawBin(graphics, dataset, binnum); 1489 } 1490 1491 if (_markDisconnections && _marks == 0 && numberOfBins > 0) { 1492 Bin bin = data.get(numberOfBins - 1); 1493 1494 // We are going to add an extra dot for the last point. 1495 // Every segment will be marked by two dots in case there 1496 // are no marks. 1497 // Typically at the end the plot is repaint and hence also the 1498 // dot need to be added to close the last segment. However 1499 // it might happen that points are added afterwards again and in this 1500 // case the mark has to be removed again. 1501 1502 boolean connectedFlag = getConnected(); 1503 ArrayList<PlotPoint> points = _points.get(dataset); 1504 1505 int currentPointPosition = points.size() - 1; 1506 PlotPoint lastPoint = points.get(currentPointPosition); 1507 if (connectedFlag && lastPoint.connected) { 1508 // In case the point is not connected there is already a dot. 1509 _setColorForDrawing(graphics, dataset, false); 1510 long xpos = bin.xpos; 1511 long ypos = _lry - (long) ((lastPoint.y - _yMin) * _yscale); 1512 // BRDebug System.out.println("_drawPlot"); 1513 _drawPoint(graphics, dataset, xpos, ypos, true, 2 /*dots*/); 1514 _resetColorForDrawing(graphics, false); 1515 1516 // We keep track of the last dot that has been add to be able to 1517 // remove the dot again in case an extra point was added afterwards. 1518 _lastPointWithExtraDot.put(dataset, lastPoint); 1519 } 1520 } 1521 } 1522 1523 _showing = true; 1524 } 1525 1526 /** Put a mark corresponding to the specified dataset at the 1527 * specified x and y position. The mark is drawn in the current 1528 * color. What kind of mark is drawn depends on the _marks 1529 * variable and the dataset argument. If the fourth argument is 1530 * true, then check the range and plot only points that 1531 * are in range. 1532 * This method should be called only from the event dispatch thread. 1533 * It is not synchronized, so its caller should be. 1534 * @param graphics The graphics context. 1535 * @param dataset The index of the dataset. 1536 * @param xpos The x position. 1537 * @param ypos The y position. 1538 * @param clip If true, then do not draw outside the range. 1539 */ 1540 @Override 1541 protected void _drawPoint(Graphics graphics, int dataset, long xpos, 1542 long ypos, boolean clip) { 1543 // Check to see whether the dataset has a marks directive 1544 Format fmt = _formats.get(dataset); 1545 int marks = _marks; 1546 1547 if (!fmt.marksUseDefault) { 1548 marks = fmt.marks; 1549 } 1550 _drawPoint(graphics, dataset, xpos, ypos, clip, marks); 1551 } 1552 1553 /** Return Latex plot data. 1554 * @return A string suitable for inclusion in Latex. 1555 */ 1556 @Override 1557 protected String _exportLatexPlotData() { 1558 StringBuilder result = new StringBuilder(); 1559 Formatter formatter = null; 1560 try { 1561 formatter = new Formatter(result, Locale.US); 1562 1563 for (int i = 0; i < _points.size(); i++) { 1564 result.append("\\pscurve[showpoints=true]{-}"); 1565 ArrayList<PlotPoint> pts = _points.get(i); 1566 for (int pointnum = 0; pointnum < pts.size(); pointnum++) { 1567 PlotPoint pt = pts.get(pointnum); 1568 if (!pt.connected) { 1569 // FIXME: Break connection. 1570 } 1571 if (pt.errorBar) { 1572 // FIXME: Support error bars. 1573 } else { 1574 // NOTE: Latex doesn't understand scientific notation, 1575 // so we can't just use pt.x and pt.y. 1576 result.append("("); 1577 formatter.format("%f", pt.x); 1578 result.append(","); 1579 formatter.format("%f", pt.y); 1580 result.append(")"); 1581 } 1582 } 1583 result.append("\n"); 1584 } 1585 } finally { 1586 if (formatter != null) { 1587 formatter.close(); 1588 } 1589 } 1590 return result.toString(); 1591 } 1592 1593 /** Parse a line that gives plotting information. Return true if 1594 * the line is recognized. Lines with syntax errors are ignored. 1595 * It is not synchronized, so its caller should be. 1596 * @param line A command line. 1597 * @return True if the line is recognized. 1598 */ 1599 @Override 1600 protected boolean _parseLine(String line) { 1601 boolean connected = false; 1602 1603 if (_isConnected(_currentdataset)) { 1604 connected = true; 1605 } 1606 1607 // parse only if the super class does not recognize the line. 1608 if (super._parseLine(line)) { 1609 return true; 1610 } else { 1611 // We convert the line to lower case so that the command 1612 // names are case insensitive 1613 String lcLine = line.toLowerCase(Locale.getDefault()); 1614 1615 if (lcLine.startsWith("linestyle:")) { 1616 String style = line.substring(10).trim(); 1617 setLineStyle(style, _currentdataset); 1618 return true; 1619 } else if (lcLine.startsWith("marks:")) { 1620 // If we have seen a dataset directive, then apply the 1621 // request to the current dataset only. 1622 String style = line.substring(6).trim(); 1623 1624 if (_sawFirstDataSet) { 1625 setMarksStyle(style, _currentdataset); 1626 } else { 1627 setMarksStyle(style); 1628 } 1629 1630 return true; 1631 } else if (lcLine.startsWith("numsets:")) { 1632 // Ignore. No longer relevant. 1633 return true; 1634 } else if (lcLine.startsWith("reusedatasets:")) { 1635 if (lcLine.indexOf("off", 16) >= 0) { 1636 setReuseDatasets(false); 1637 } else { 1638 setReuseDatasets(true); 1639 } 1640 1641 return true; 1642 } else if (lcLine.startsWith("dataset:")) { 1643 if (_reuseDatasets && lcLine.length() > 0) { 1644 String tlegend = line.substring(8).trim(); 1645 _currentdataset = -1; 1646 1647 int i; 1648 1649 for (i = 0; i <= _maxDataset; i++) { 1650 if (getLegend(i).compareTo(tlegend) == 0) { 1651 _currentdataset = i; 1652 } 1653 } 1654 1655 if (_currentdataset != -1) { 1656 return true; 1657 } else { 1658 _currentdataset = _maxDataset; 1659 } 1660 } 1661 1662 // new data set 1663 _firstInSet = true; 1664 _sawFirstDataSet = true; 1665 _currentdataset++; 1666 1667 if (lcLine.length() > 0) { 1668 String legend = line.substring(8).trim(); 1669 1670 if (legend != null && legend.length() > 0) { 1671 addLegend(_currentdataset, legend); 1672 } 1673 } 1674 1675 _maxDataset = _currentdataset; 1676 return true; 1677 } else if (lcLine.startsWith("lines:")) { 1678 if (_sawFirstDataSet) { 1679 // Backward compatbility with xgraph here. 1680 // If we see some data sets, then they are drawn 1681 // with lines, if we then see a Lines: off 1682 // the current dataset and succeeding datasets 1683 // will be drawn without lines. 1684 // For each of the existing datasets, if 1685 // it fmt.connectedUseDefault is true, then 1686 // set fmt.connectedUseDefault to false and set 1687 // the value of fmt.connected 1688 1689 for (Format format : _formats) { 1690 1691 if (format.connectedUseDefault) { 1692 format.connectedUseDefault = false; 1693 format.connected = _connected; 1694 } 1695 } 1696 } 1697 1698 if (lcLine.indexOf("off", 6) >= 0) { 1699 setConnected(false); 1700 } else { 1701 setConnected(true); 1702 } 1703 1704 return true; 1705 } else if (lcLine.startsWith("impulses:")) { 1706 // If we have not yet seen a dataset, then this is interpreted 1707 // as the global default. Otherwise, it is assumed to apply 1708 // only to the current dataset. 1709 if (_sawFirstDataSet) { 1710 if (lcLine.indexOf("off", 9) >= 0) { 1711 setImpulses(false, _currentdataset); 1712 } else { 1713 setImpulses(true, _currentdataset); 1714 } 1715 } else { 1716 if (lcLine.indexOf("off", 9) >= 0) { 1717 setImpulses(false); 1718 } else { 1719 setImpulses(true); 1720 } 1721 } 1722 1723 return true; 1724 } else if (lcLine.startsWith("bars:")) { 1725 if (lcLine.indexOf("off", 5) >= 0) { 1726 setBars(false); 1727 } else { 1728 setBars(true); 1729 1730 int comma = line.indexOf(",", 5); 1731 String _barWidth; 1732 String baroffset = null; 1733 1734 if (comma > 0) { 1735 _barWidth = line.substring(5, comma).trim(); 1736 baroffset = line.substring(comma + 1).trim(); 1737 } else { 1738 _barWidth = line.substring(5).trim(); 1739 } 1740 1741 try { 1742 // Use Double.parseDouble() and avoid creating a Double 1743 double bwidth = Double.parseDouble(_barWidth); 1744 double boffset = _barOffset; 1745 1746 if (baroffset != null) { 1747 boffset = Double.parseDouble(baroffset); 1748 } 1749 1750 setBars(bwidth, boffset); 1751 } catch (NumberFormatException e) { 1752 // ignore if format is bogus. 1753 } 1754 } 1755 1756 return true; 1757 } else if (line.startsWith("move:")) { 1758 // a disconnected point 1759 connected = false; 1760 1761 // deal with 'move: 1 2' and 'move:2 2' 1762 line = line.substring(5, line.length()).trim(); 1763 } else if (line.startsWith("move")) { 1764 // a disconnected point 1765 connected = false; 1766 1767 // deal with 'move 1 2' and 'move2 2' 1768 line = line.substring(4, line.length()).trim(); 1769 } else if (line.startsWith("draw:")) { 1770 // a connected point, if connect is enabled. 1771 line = line.substring(5, line.length()).trim(); 1772 } else if (line.startsWith("draw")) { 1773 // a connected point, if connect is enabled. 1774 line = line.substring(4, line.length()).trim(); 1775 } 1776 1777 line = line.trim(); 1778 1779 // We can't use StreamTokenizer here because it can't 1780 // process numbers like 1E-01. 1781 // This code is somewhat optimized for speed, since 1782 // most data consists of two data points, we want 1783 // to handle that case as efficiently as possible. 1784 int fieldsplit = line.indexOf(","); 1785 1786 if (fieldsplit == -1) { 1787 fieldsplit = line.indexOf(" "); 1788 } 1789 1790 if (fieldsplit == -1) { 1791 fieldsplit = line.indexOf("\t"); // a tab 1792 } 1793 1794 if (fieldsplit > 0) { 1795 String x = line.substring(0, fieldsplit).trim(); 1796 String y = line.substring(fieldsplit + 1).trim(); 1797 1798 // Any more separators? 1799 int fieldsplit2 = y.indexOf(","); 1800 1801 if (fieldsplit2 == -1) { 1802 fieldsplit2 = y.indexOf(" "); 1803 } 1804 1805 if (fieldsplit2 == -1) { 1806 fieldsplit2 = y.indexOf("\t"); // a tab 1807 } 1808 1809 if (fieldsplit2 > 0) { 1810 line = y.substring(fieldsplit2 + 1).trim(); 1811 y = y.substring(0, fieldsplit2).trim(); 1812 } 1813 1814 try { 1815 // Use Double.parseDouble() and avoid creating a Double. 1816 double xpt = Double.parseDouble(x); 1817 double ypt = Double.parseDouble(y); 1818 1819 if (fieldsplit2 > 0) { 1820 // There was one separator after the y value, now 1821 // look for another separator. 1822 int fieldsplit3 = line.indexOf(","); 1823 1824 if (fieldsplit3 == -1) { 1825 fieldsplit3 = line.indexOf(" "); 1826 } 1827 1828 //if (fieldsplit3 == -1) { 1829 // fieldsplit2 = line.indexOf("\t"); // a tab 1830 //} 1831 if (fieldsplit3 > 0) { 1832 // We have more numbers, assume that this is 1833 // an error bar 1834 String yl = line.substring(0, fieldsplit3).trim(); 1835 String yh = line.substring(fieldsplit3 + 1).trim(); 1836 double yLowEB = Double.parseDouble(yl); 1837 double yHighEB = Double.parseDouble(yh); 1838 connected = _addLegendIfNecessary(connected); 1839 addPointWithErrorBars(_currentdataset, xpt, ypt, 1840 null, yLowEB, yHighEB, connected); 1841 return true; 1842 } else { 1843 // It is unlikely that we have a fieldsplit2 >0 1844 // but not fieldsplit3 >0, but just in case: 1845 connected = _addLegendIfNecessary(connected); 1846 addPoint(_currentdataset, xpt, ypt, connected); 1847 return true; 1848 } 1849 } else { 1850 // There were no more fields, so this is 1851 // a regular pt. 1852 connected = _addLegendIfNecessary(connected); 1853 addPoint(_currentdataset, xpt, ypt, connected); 1854 return true; 1855 } 1856 } catch (NumberFormatException e) { 1857 // ignore if format is bogus. 1858 } 1859 } 1860 } 1861 1862 return false; 1863 } 1864 1865 /** Reset a scheduled redraw tasks. 1866 */ 1867 @Override 1868 protected void _resetScheduledTasks() { 1869 Runnable redraw = new RunnableExceptionCatcher(new Runnable() { 1870 @Override 1871 public void run() { 1872 _scheduledBinsToAdd.clear(); 1873 _scheduledBinsToErase.clear(); 1874 } 1875 }); 1876 synchronized (this) { 1877 deferIfNecessary(redraw); 1878 } 1879 } 1880 1881 /** Perform a scheduled redraw. 1882 */ 1883 @Override 1884 protected void _scheduledRedraw() { 1885 if (_needPlotRefill || _needBinRedraw) { 1886 Runnable redraw = new RunnableExceptionCatcher(new Runnable() { 1887 @Override 1888 public void run() { 1889 ArrayList<Integer> scheduledBinsToAdd = new ArrayList<Integer>(); 1890 for (int i = 0; i < _scheduledBinsToAdd.size(); ++i) { 1891 scheduledBinsToAdd.add(_scheduledBinsToAdd.get(i)); 1892 _scheduledBinsToAdd.set(i, 0); 1893 } 1894 ArrayList<Integer> scheduledBinsToErase = new ArrayList<Integer>(); 1895 for (int i = 0; i < _scheduledBinsToErase.size(); ++i) { 1896 scheduledBinsToErase.add(_scheduledBinsToErase.get(i)); 1897 _scheduledBinsToErase.set(i, 0); 1898 } 1899 _needBinRedraw = false; 1900 if (_needPlotRefill) { 1901 fillPlot(); 1902 _needPlotRefill = false; 1903 } else { 1904 Graphics graphics = getGraphics(); 1905 if (graphics != null) { 1906 { 1907 int nbrOfDataSets = _scheduledBinsToAdd.size(); 1908 for (int i = 0; i < nbrOfDataSets; ++i) { 1909 int nbrOfBins = _bins.get(i).size(); 1910 int nbrOfBinsToAdd = scheduledBinsToAdd 1911 .get(i); 1912 for (int binIndex = nbrOfBins 1913 - nbrOfBinsToAdd; binIndex < nbrOfBins; ++binIndex) { 1914 assert binIndex >= 0; 1915 _drawBin(graphics, i, binIndex); 1916 } 1917 } 1918 } 1919 { 1920 int nbrOfDataSets = _scheduledBinsToErase 1921 .size(); 1922 for (int i = 0; i < nbrOfDataSets; ++i) { 1923 int nbrOfBinsToErase = scheduledBinsToErase 1924 .get(i); 1925 for (int binIndex = 0; binIndex < nbrOfBinsToErase; ++binIndex) { 1926 _eraseFirstBin(i); 1927 } 1928 } 1929 } 1930 } 1931 } 1932 } 1933 }); 1934 synchronized (this) { 1935 deferIfNecessary(redraw); 1936 } 1937 } 1938 } 1939 1940 /** If the graphics argument is an instance of Graphics2D, then set 1941 * the current stroke to the specified width. Otherwise, do nothing. 1942 * @param graphics The graphics object. 1943 * @param width The width. 1944 */ 1945 protected void _setWidth(Graphics graphics, float width) { 1946 _width = width; 1947 1948 // For historical reasons, the API here only assumes Graphics 1949 // objects, not Graphics2D. 1950 if (graphics instanceof Graphics2D) { 1951 // We cache the two most common cases. 1952 if (width == 1f) { 1953 ((Graphics2D) graphics).setStroke(_LINE_STROKE1); 1954 } else if (width == 2f) { 1955 ((Graphics2D) graphics).setStroke(_LINE_STROKE2); 1956 } else { 1957 ((Graphics2D) graphics).setStroke(new BasicStroke(width, 1958 BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND)); 1959 } 1960 } 1961 } 1962 1963 /** Write plot information to the specified output stream in 1964 * the "old syntax," which predates PlotML. 1965 * Derived classes should override this method to first call 1966 * the parent class method, then add whatever additional information 1967 * they wish to add to the stream. 1968 * It is not synchronized, so its caller should be. 1969 * @param output A buffered print writer. 1970 * @deprecated 1971 */ 1972 @Override 1973 @Deprecated 1974 protected void _writeOldSyntax(PrintWriter output) { 1975 super._writeOldSyntax(output); 1976 1977 // NOTE: NumSets is obsolete, so we don't write it. 1978 if (_reuseDatasets) { 1979 output.println("ReuseDatasets: on"); 1980 } 1981 1982 if (!_connected) { 1983 output.println("Lines: off"); 1984 } 1985 1986 if (_bars) { 1987 output.println("Bars: " + _barWidth + ", " + _barOffset); 1988 } 1989 1990 // Write the defaults for formats that can be controlled by dataset 1991 if (_impulses) { 1992 output.println("Impulses: on"); 1993 } 1994 1995 switch (_marks) { 1996 case 0: 1997 //Ignore: Marks: none 1998 break; 1999 case 1: 2000 output.println("Marks: points"); 2001 break; 2002 2003 case 2: 2004 output.println("Marks: dots"); 2005 break; 2006 2007 case 3: 2008 output.println("Marks: various"); 2009 break; 2010 2011 case 4: 2012 output.println("Marks: bigdots"); 2013 break; 2014 2015 case 5: 2016 output.println("Marks: pixelss"); 2017 break; 2018 default: 2019 throw new RuntimeException("Internal Error. Mark " + "style " 2020 + _marks + " not supported."); 2021 } 2022 2023 for (int dataset = 0; dataset < _points.size(); dataset++) { 2024 // Write the dataset directive 2025 String legend = getLegend(dataset); 2026 2027 if (legend != null) { 2028 output.println("DataSet: " + getLegend(dataset)); 2029 } else { 2030 output.println("DataSet:"); 2031 } 2032 2033 // Write dataset-specific format information 2034 Format fmt = _formats.get(dataset); 2035 2036 if (!fmt.impulsesUseDefault) { 2037 if (fmt.impulses) { 2038 output.println("Impulses: on"); 2039 } else { 2040 output.println("Impulses: off"); 2041 } 2042 } 2043 2044 if (!fmt.lineStyleUseDefault) { 2045 output.println("lineStyle: " + fmt.lineStyle); 2046 } 2047 2048 if (!fmt.marksUseDefault) { 2049 switch (fmt.marks) { 2050 case 0: 2051 output.println("Marks: none"); 2052 break; 2053 2054 case 1: 2055 output.println("Marks: points"); 2056 break; 2057 2058 case 2: 2059 output.println("Marks: dots"); 2060 break; 2061 2062 case 3: 2063 output.println("Marks: various"); 2064 break; 2065 2066 case 4: 2067 output.println("Marks: pixels"); 2068 break; 2069 } 2070 } 2071 2072 // Write the data 2073 ArrayList<PlotPoint> pts = _points.get(dataset); 2074 2075 for (int pointnum = 0; pointnum < pts.size(); pointnum++) { 2076 PlotPoint pt = pts.get(pointnum); 2077 2078 if (!pt.connected) { 2079 output.print("move: "); 2080 } 2081 2082 if (pt.errorBar) { 2083 output.println(pt.x + ", " + pt.y + ", " + pt.yLowEB + ", " 2084 + pt.yHighEB); 2085 } else { 2086 output.println(pt.x + ", " + pt.y); 2087 } 2088 } 2089 } 2090 } 2091 2092 /////////////////////////////////////////////////////////////////// 2093 //// protected variables //// 2094 2095 /** The current dataset. */ 2096 protected int _currentdataset = -1; 2097 2098 /** An indicator of the marks style. See _parseLine method for 2099 * interpretation. 2100 */ 2101 protected volatile int _marks; 2102 2103 /** A vector of datasets. */ 2104 protected ArrayList<ArrayList<PlotPoint>> _points = new ArrayList<ArrayList<PlotPoint>>(); 2105 2106 /////////////////////////////////////////////////////////////////// 2107 //// private methods //// 2108 2109 /* Add a legend if necessary, return the value of the connected flag. 2110 */ 2111 private boolean _addLegendIfNecessary(boolean connected) { 2112 if ((!_sawFirstDataSet || _currentdataset < 0) && !_reuseDatasets) { 2113 // We did not set a DataSet line, but 2114 // we did get called with -<digit> args and 2115 // we did not see reusedatasets: yes 2116 _sawFirstDataSet = true; 2117 _currentdataset++; 2118 } 2119 2120 if (!_sawFirstDataSet && getLegend(_currentdataset) == null) { 2121 // We did not see a "DataSet" string yet, 2122 // nor did we call addLegend(). 2123 _firstInSet = true; 2124 _sawFirstDataSet = true; 2125 addLegend(_currentdataset, "Set " + _currentdataset); 2126 } 2127 2128 if (_firstInSet && !_reuseDatasets) { 2129 connected = false; 2130 _firstInSet = false; 2131 } 2132 2133 return connected; 2134 } 2135 2136 /* In the specified data set, add the specified x, y point to the 2137 * plot. Data set indices begin with zero. If the dataset 2138 * argument is less than zero, throw an IllegalArgumentException 2139 * (a runtime exception). If it refers to a data set that does 2140 * not exist, create the data set. The fourth argument indicates 2141 * whether the point should be connected by a line to the previous 2142 * point. However, this argument is ignored if setConnected() has 2143 * been called with a false argument. In that case, a point is never 2144 * connected to the previous point. That argument is also ignored 2145 * if the point is the first in the specified dataset. 2146 * The point is drawn on the screen only if is visible. 2147 * Otherwise, it is drawn the next time paintComponent() is called. 2148 * 2149 * This is not synchronized, so the caller should be. Moreover, this 2150 * should only be called in the event dispatch thread. It should only 2151 * be called via deferIfNecessary(). 2152 */ 2153 private void _addPoint(int dataset, double x, double y, 2154 double[] derivatives, double yLowEB, double yHighEB, 2155 boolean connected, boolean errorBar) { 2156 // Ensure replot of offscreen buffer. 2157 _plotImage = null; 2158 2159 _checkDatasetIndex(dataset); 2160 2161 if (_xlog) { 2162 if (x <= 0.0) { 2163 System.err.println("Can't plot non-positive X values " 2164 + "when the logarithmic X axis value is specified: " 2165 + x); 2166 return; 2167 } 2168 2169 x = Math.log(x) * _LOG10SCALE; 2170 } 2171 2172 if (_ylog) { 2173 if (y <= 0.0) { 2174 System.err.println("Can't plot non-positive Y values " 2175 + "when the logarithmic Y axis value is specified: " 2176 + y); 2177 return; 2178 } 2179 2180 y = Math.log(y) * _LOG10SCALE; 2181 2182 if (errorBar) { 2183 if (yLowEB <= 0.0 || yHighEB <= 0.0) { 2184 System.err.println("Can't plot non-positive Y values " 2185 + "when the logarithmic Y axis value is specified: " 2186 + y); 2187 return; 2188 } 2189 2190 yLowEB = Math.log(yLowEB) * _LOG10SCALE; 2191 yHighEB = Math.log(yHighEB) * _LOG10SCALE; 2192 } 2193 } 2194 2195 ArrayList<Bin> bins = _bins.get(dataset); 2196 ArrayList<PlotPoint> points = _points.get(dataset); 2197 2198 // If X persistence has been set, then delete any old points. 2199 if (_xPersistence > 0.0) { 2200 int numToDelete = 0; 2201 int nbrOfBins = bins.size(); 2202 2203 while (numToDelete < nbrOfBins) { 2204 Bin old = bins.get(numToDelete); 2205 2206 if (x - points.get( 2207 old.firstPointIndex()).originalx <= _xPersistence) { 2208 break; 2209 } 2210 2211 numToDelete++; 2212 } 2213 2214 numToDelete = Math.min(numToDelete, nbrOfBins - 1); 2215 //We want to keep at least one bin. 2216 2217 if (!_timedRepaint()) { 2218 for (int i = 0; i < numToDelete; i++) { 2219 // Again, we are in the event thread, so this is safe... 2220 _eraseFirstBin(dataset); 2221 } 2222 } else { 2223 _scheduleBinRedrawRemove(dataset, numToDelete); 2224 } 2225 } 2226 2227 // Get the new size after deletions. 2228 int size = points.size(); 2229 2230 PlotPoint pt = new PlotPoint(); 2231 2232 // Original value of x before wrapping. 2233 pt.originalx = x; 2234 2235 // Modify x if wrapping. 2236 if (_wrap) { 2237 double width = _wrapHigh - _wrapLow; 2238 2239 if (x < _wrapLow) { 2240 x += width * Math.floor(1.0 + (_wrapLow - x) / width); 2241 } else if (x > _wrapHigh) { 2242 x -= width * Math.floor(1.0 + (x - _wrapHigh) / width); 2243 2244 // NOTE: Could quantization errors be a problem here? 2245 if (Math.abs(x - _wrapLow) < 0.00001) { 2246 x = _wrapHigh; 2247 } 2248 } 2249 } 2250 2251 boolean needPlotRefill = false; 2252 2253 // For auto-ranging, keep track of min and max. 2254 2255 if (x < _xBottom) { 2256 if (_automaticRescale() && _xTop != -Double.MAX_VALUE 2257 && _xBottom != Double.MAX_VALUE) { 2258 needPlotRefill = true; 2259 _xBottom = x - (_xTop - _xBottom); 2260 } else { 2261 _xBottom = x; 2262 } 2263 } 2264 2265 if (x > _xTop) { 2266 if (_automaticRescale() && _xTop != -Double.MAX_VALUE 2267 && _xBottom != Double.MAX_VALUE) { 2268 needPlotRefill = true; 2269 _xTop = x + _xTop - _xBottom; 2270 } else { 2271 _xTop = x; 2272 } 2273 } 2274 2275 if (y < _yBottom) { 2276 if (_automaticRescale() && _yTop != -Double.MAX_VALUE 2277 && _yBottom != Double.MAX_VALUE) { 2278 needPlotRefill = true; 2279 _yBottom = y - (_yTop - _yBottom); 2280 } else { 2281 _yBottom = y; 2282 } 2283 } 2284 2285 if (y > _yTop) { 2286 if (_automaticRescale() && _yTop != -Double.MAX_VALUE 2287 && _yBottom != Double.MAX_VALUE) { 2288 needPlotRefill = true; 2289 _yTop = y + _yTop - _yBottom; 2290 } else { 2291 _yTop = y; 2292 } 2293 } 2294 2295 pt.x = x; 2296 pt.y = y; 2297 pt.derivatives = derivatives != null ? (double[]) derivatives.clone() 2298 : null; 2299 pt.connected = connected && _isConnected(dataset); 2300 2301 if (errorBar) { 2302 if (yLowEB < _yBottom) { 2303 _yBottom = yLowEB; 2304 } 2305 2306 if (yLowEB > _yTop) { 2307 _yTop = yLowEB; 2308 } 2309 2310 if (yHighEB < _yBottom) { 2311 _yBottom = yHighEB; 2312 } 2313 2314 if (yHighEB > _yTop) { 2315 _yTop = yHighEB; 2316 } 2317 2318 pt.yLowEB = yLowEB; 2319 pt.yHighEB = yHighEB; 2320 pt.errorBar = true; 2321 } 2322 2323 // If this is the first point in the dataset, clear the connected bit. 2324 if (size == 0) { 2325 pt.connected = false; 2326 } else if (_wrap) { 2327 // Do not connect points if wrapping... 2328 PlotPoint old = points.get(size - 1); 2329 2330 if (old.x > x) { 2331 pt.connected = false; 2332 } 2333 } 2334 2335 points.add(pt); 2336 2337 int nbrOfBins = dataset < _bins.size() ? _bins.get(dataset).size() : 0; 2338 _addPointToBin(dataset, pt, size); 2339 2340 boolean binAdded = _bins.get(dataset).size() != nbrOfBins; 2341 2342 // If points persistence has been set, then delete first bin if there are to many points 2343 // However we don't want to delete all bins... 2344 if (_pointsPersistence > 0) { 2345 if (size > _pointsPersistence && bins.size() > 2) { 2346 // Again, we are in the event thread, so this is safe... 2347 if (!_timedRepaint()) { 2348 _eraseFirstBin(dataset); 2349 } else { 2350 _scheduleBinRedrawRemove(dataset, 1); 2351 } 2352 } 2353 } 2354 2355 // Draw the point on the screen only if the plot is showing. 2356 Graphics graphics = getGraphics(); 2357 2358 // Need to check that graphics is not null because plot may have 2359 // been dismissed. 2360 if (_showing && graphics != null) { 2361 if ((_pointsPersistence > 0 || _xPersistence > 0.0) 2362 && isDoubleBuffered()) { 2363 // NOTE: Double buffering has a bug in Java (in at least 2364 // version 1.3) where there is a one pixel alignment problem 2365 // that prevents XOR drawing from working correctly. 2366 // XOR drawing is used for live plots, and if double buffering 2367 // is turned on, then cruft is left on the screen whenever the 2368 // fill or zoom functions are used. 2369 // Here, if it hasn't been done already, we turn off double 2370 // buffering on this panel and all its parents for which this 2371 // is possible. Note that we could do this globally using 2372 // 2373 // RepaintManager repaintManager 2374 // = RepaintManager.currentManager(this); 2375 // repaintManager.setDoubleBufferingEnabled(false); 2376 // 2377 // However, that turns off double buffering in all windows 2378 // of the application, which means that other windows that only 2379 // work properly with double buffering (such as vergil windows) 2380 // will not work. 2381 // 2382 // NOTE: This fix creates another problem... 2383 // If there are other widgets besides the plotter in the 2384 // same top-level window, and they implement double 2385 // buffering (which they will by default), then they 2386 // need to be opaque or drawing artifacts will appear 2387 // upon exposure events. The workaround is simple: 2388 // Make these other objects opaque, and set their 2389 // background color appropriately. 2390 // 2391 // See: 2392 // <pre> 2393 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4219548 2394 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4204551 2395 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4295712 2396 // </pre> 2397 // 2398 // Since we are assured of being in the event dispatch thread, 2399 // we can simply execute this. 2400 setDoubleBuffered(false); 2401 2402 Component parent = getParent(); 2403 2404 while (parent != null) { 2405 if (parent instanceof JComponent) { 2406 ((JComponent) parent).setDoubleBuffered(false); 2407 } 2408 2409 parent = parent.getParent(); 2410 } 2411 } 2412 2413 assert _bins.get(dataset).size() > 0; 2414 2415 if (!_timedRepaint()) { 2416 // Again, we are in the event thread, so this is safe... 2417 _drawBin(graphics, dataset, _bins.get(dataset).size() - 1); 2418 } else { 2419 if (needPlotRefill) { 2420 _needPlotRefill = true; 2421 } else { 2422 _scheduleBinRedrawAdd(dataset, binAdded); 2423 } 2424 } 2425 } 2426 2427 if (_wrap && Math.abs(x - _wrapHigh) < 0.00001) { 2428 // Plot a second point at the low end of the range. 2429 _addPoint(dataset, _wrapLow, y, derivatives, yLowEB, yHighEB, false, 2430 errorBar); 2431 } 2432 } 2433 2434 /** Add point to the corresponding Bin. If the point fits into the last bin 2435 * (the same xpos) it will be added to this one, otherwise a new bin will 2436 * be created 2437 */ 2438 private void _addPointToBin(int dataset, PlotPoint point, int pointIndex) { 2439 ArrayList<Bin> bins = _bins.get(dataset); //we could move this out of this function (for performance) 2440 2441 // Use long for positions because these numbers can be quite large 2442 // (when we are zoomed out a lot). 2443 2444 // _drawPlot should have been called to fill in _xscale and _yscale 2445 2446 long xpos = _ulx + (long) ((point.x - _xMin) * _xscale); 2447 long ypos = _lry - (long) ((point.y - _yMin) * _yscale); 2448 int nbrOfBins = bins.size(); 2449 //Cached since it came out in JProfiler (everything becomes costly if you 2450 // do it a lot of times) 2451 2452 Bin lastBin = nbrOfBins > 0 ? bins.get(nbrOfBins - 1) : null; 2453 //Cached since it came out in JProfiler (everything becomes costly if you do 2454 // it a lot of times) 2455 2456 if (nbrOfBins == 0 || lastBin.xpos != xpos) { 2457 // Does not fall within last bin => add one bin 2458 // nbrOfBins += 1; 2459 lastBin = new Bin(xpos, dataset, point.derivatives); 2460 bins.add(lastBin); 2461 } 2462 lastBin.addPoint(point, pointIndex, ypos); 2463 } 2464 2465 /* Clear the plot of all data points. If the argument is true, then 2466 * reset all parameters to their initial conditions, including 2467 * the persistence, plotting format, and axes formats. 2468 * For the change to take effect, you must call repaint(). 2469 * 2470 * This is not synchronized, so the caller should be. Moreover, this 2471 * should only be called in the event dispatch thread. It should only 2472 * be called via deferIfNecessary(). 2473 */ 2474 private void _clear(boolean format) { 2475 // Ensure replot of offscreen buffer. 2476 _plotImage = null; 2477 super.clear(format); 2478 _currentdataset = -1; 2479 _points.clear(); 2480 for (ArrayList<Bin> data : _bins) { 2481 data.clear(); 2482 } 2483 _bins.clear(); 2484 _prevxpos.clear(); 2485 _prevypos.clear(); 2486 _prevErasedxpos.clear(); 2487 _prevErasedypos.clear(); 2488 _maxDataset = -1; 2489 _firstInSet = true; 2490 _sawFirstDataSet = false; 2491 _xyInvalid = true; 2492 _resetScheduledTasks(); 2493 2494 if (format) { 2495 _showing = false; 2496 2497 // Reset format controls 2498 _formats.clear(); 2499 _marks = 0; 2500 _pointsPersistence = 0; 2501 _xPersistence = 0; 2502 _bars = false; 2503 _barWidth = 0.5; 2504 _barOffset = 0.05; 2505 _connected = true; 2506 _impulses = false; 2507 _reuseDatasets = false; 2508 } 2509 } 2510 2511 /** Clear the plot of data points in the specified dataset. 2512 * This calls repaint() to request an update of the display. 2513 * 2514 * This is not synchronized, so the caller should be. Moreover, this 2515 * should only be called in the event dispatch thread. It should only 2516 * be called via deferIfNecessary(). 2517 */ 2518 private void _clear(int dataset) { 2519 // Ensure replot of offscreen buffer. 2520 _plotImage = null; 2521 _checkDatasetIndex(dataset); 2522 _xyInvalid = true; 2523 2524 ArrayList<PlotPoint> points = _points.get(dataset); 2525 2526 points.clear(); 2527 2528 _points.set(dataset, points); 2529 _bins.get(dataset).clear(); 2530 2531 _lastPointWithExtraDot.clear(); 2532 repaint(); 2533 } 2534 2535 /** Subdivide all points into different bins. A bin is represents a number of 2536 * points that are all displayed on the same x position. When calling this function 2537 * all existing bins will first be cleared. 2538 */ 2539 private void _dividePointsIntoBins() { 2540 for (int i = 0; i < _scheduledBinsToAdd.size(); ++i) { 2541 _scheduledBinsToAdd.set(i, 0); 2542 } 2543 for (int i = 0; i < _scheduledBinsToErase.size(); ++i) { 2544 _scheduledBinsToErase.set(i, 0); 2545 } 2546 _needBinRedraw = false; 2547 2548 _bins.clear(); 2549 _pointInBinOffset.clear(); 2550 int nbrOfDataSets = _points.size(); 2551 for (int i = 0; i < nbrOfDataSets; ++i) { 2552 _bins.add(new ArrayList<Bin>()); 2553 _pointInBinOffset.add(0); 2554 } 2555 2556 for (int dataset = 0; dataset < nbrOfDataSets; ++dataset) { 2557 ArrayList<PlotPoint> points = _points.get(dataset); 2558 int numberOfPoints = points.size(); 2559 for (int pointIndex = 0; pointIndex < numberOfPoints; ++pointIndex) { 2560 _addPointToBin(dataset, points.get(pointIndex), pointIndex); 2561 } 2562 } 2563 } 2564 2565 /* Draw the points within a specific bin and associated lines, if any. 2566 * Note that paintComponent() should be called before 2567 * calling this method so that it calls _drawPlot(), which sets 2568 * _xscale and _yscale, and subdivides the points into different Bins. Note that this does not check the dataset 2569 * index. It is up to the caller to do that. 2570 * 2571 * Note that this method is not synchronized, so the caller should be. 2572 * Moreover this method should always be called from the event thread 2573 * when being used to write to the screen. 2574 */ 2575 private void _drawBin(Graphics graphics, int dataset, int binIndex) { 2576 // BRDebug System.out.println("_drawBin"); 2577 2578 _setColorForDrawing(graphics, dataset, false); 2579 2580 if (_lineStyles) { 2581 int lineStyle = dataset % _LINE_STYLES_ARRAY.length; 2582 setLineStyle(_LINE_STYLES_ARRAY[lineStyle], dataset); 2583 } 2584 2585 assert dataset < _bins.size(); 2586 ArrayList<Bin> bins = _bins.get(dataset); 2587 assert binIndex < bins.size(); 2588 Bin bin = bins.get(binIndex); 2589 long xpos = bin.xpos; 2590 2591 if (!bin.needReplot()) { 2592 return; 2593 } 2594 2595 boolean connectedFlag = getConnected(); 2596 2597 int startPosition = bin.nextPointToPlot(); 2598 int endPosition = bin.afterLastPointIndex(); 2599 2600 ArrayList<PlotPoint> points = _points.get(dataset); 2601 2602 // Check to see whether the dataset has a marks directive 2603 int marks = _marks; 2604 2605 // Draw decorations that may be specified on a per-dataset basis 2606 Format fmt = _formats.get(dataset); 2607 2608 if (!fmt.marksUseDefault) { 2609 marks = fmt.marks; 2610 } 2611 2612 if (_markDisconnections && marks == 0 && endPosition > startPosition 2613 && startPosition > 0) { 2614 PlotPoint previousPoint = points.get(startPosition - 1); 2615 if (!(connectedFlag && points.get(startPosition).connected)) { 2616 2617 // This point is not connected with the previous one. 2618 // We want to put a dot each end of the at each segment. 2619 // If the previous one was connected no dot was drawn. 2620 // We will now add this extra dot for the previous point. 2621 2622 if (connectedFlag && previousPoint.connected) { 2623 if (_lastPointWithExtraDot.get(dataset) != previousPoint) { 2624 long prevypos = _prevypos.get(dataset); 2625 long prevxpos = _prevxpos.get(dataset); 2626 // BRDebug System.out.println("Plotting point:" + prevxpos + ", " + prevypos + ", position :" + (startPosition-1) + ", previous"); 2627 _drawPoint(graphics, dataset, prevxpos, prevypos, true, 2628 2 /*dots*/); 2629 } else { 2630 // BRDebug System.out.println("Skipping point"); 2631 2632 // We already painted this dot in the _drawplot code. No need 2633 // to draw the same point again here. 2634 // Now reset the flag: 2635 _lastPointWithExtraDot.put(dataset, null); 2636 } 2637 } 2638 } else { 2639 if (_lastPointWithExtraDot.get(dataset) == previousPoint) { 2640 long prevypos = _prevypos.get(dataset); 2641 long prevxpos = _prevxpos.get(dataset); 2642 // BRDebug System.err.println("Erasing point:" + prevxpos + ", " + prevypos + ", position :" + (startPosition-1) + ", previous"); 2643 2644 // We keep track of the last dot that has been add to be able to 2645 // remove the dot again in case an extra point was added afterwards. 2646 // The erasing happens by drawing the point again with the same color (EXOR) 2647 2648 _setColorForDrawing(graphics, dataset, true); 2649 _drawPoint(graphics, dataset, prevxpos, prevypos, true, 2650 2 /*dots*/); 2651 _resetColorForDrawing(graphics, true); 2652 _setColorForDrawing(graphics, dataset, false); 2653 } 2654 } 2655 } 2656 2657 if (binIndex > 0) { 2658 Bin previousBin = bins.get(binIndex - 1); 2659 final double[] derivs = previousBin.getDerivs(); 2660 long prev_xpos = previousBin.xpos; 2661 long prev_ypos = previousBin.lastYPos(); 2662 if (derivs != null) { 2663 final double x0 = (prev_xpos - _ulx) / +_xscale + _xMin; 2664 final double y0 = (prev_ypos - _lry) / -_yscale + _yMin; 2665 for (long xpos_k = prev_xpos + 1; xpos_k <= xpos; ++xpos_k) { 2666 final double x = (xpos_k - _ulx) / +_xscale + _xMin; 2667 double y = 0; 2668 for (int i = derivs.length; i >= 0; --i) { 2669 y = y / (i + 1) * (x - x0) 2670 + (i > 0 ? derivs[i - 1] : y0); 2671 } 2672 final long ypos_k = _lry - (long) ((y - _yMin) * _yscale); 2673 _drawLine(graphics, dataset, xpos_k, ypos_k, prev_xpos, 2674 prev_ypos, true, _DEFAULT_WIDTH); 2675 prev_xpos = xpos_k; 2676 prev_ypos = ypos_k; 2677 } 2678 } 2679 if (connectedFlag && bin.needConnectionWithPreviousBin()) { 2680 _drawLine(graphics, dataset, xpos, bin.firstYPos(), prev_xpos, 2681 prev_ypos, true, _DEFAULT_WIDTH); 2682 } 2683 } 2684 2685 if (connectedFlag && bin.isConnected() && bin.rangeChanged() 2686 && bin.minYPos() != bin.maxYPos()) { 2687 _drawLine(graphics, dataset, xpos, bin.minYPos(), xpos, 2688 bin.maxYPos(), true, _DEFAULT_WIDTH); 2689 } 2690 2691 if (fmt.impulsesUseDefault && _impulses 2692 || !fmt.impulsesUseDefault && fmt.impulses) { 2693 long prevypos = _prevypos.get(dataset); 2694 long prevxpos = _prevxpos.get(dataset); 2695 2696 for (int i = startPosition; i < endPosition; ++i) { 2697 PlotPoint point = points.get(i); 2698 long ypos = _lry - (long) ((point.y - _yMin) * _yscale); 2699 if (prevypos != ypos || prevxpos != xpos) { 2700 _drawImpulse(graphics, xpos, ypos, true); 2701 prevypos = ypos; 2702 prevxpos = xpos; 2703 } 2704 } 2705 } 2706 2707 { 2708 long prevypos = _prevypos.get(dataset); 2709 long prevxpos = _prevxpos.get(dataset); 2710 2711 for (int i = startPosition; i < endPosition; ++i) { 2712 PlotPoint point = points.get(i); 2713 2714 // If a point is not connected, we mark it with a dot. 2715 if (marks != 0 || _markDisconnections 2716 && !(connectedFlag && point.connected)) { 2717 long ypos = _lry - (long) ((point.y - _yMin) * _yscale); 2718 if (prevypos != ypos || prevxpos != xpos) { 2719 int updatedMarks = marks; 2720 if (!(connectedFlag && point.connected) && marks == 0) { 2721 updatedMarks = 2; // marking style: dots 2722 } 2723 // BRDebug System.out.println("Plotting point:" + xpos + ", " + ypos + ", position :" + (i) + ", current"); 2724 _drawPoint(graphics, dataset, xpos, ypos, true, 2725 updatedMarks); 2726 prevypos = ypos; 2727 prevxpos = xpos; 2728 } 2729 } 2730 2731 } 2732 } 2733 if (_bars) { 2734 long prevypos = _prevypos.get(dataset); 2735 long prevxpos = _prevxpos.get(dataset); 2736 for (int i = startPosition; i < endPosition; ++i) { 2737 PlotPoint point = points.get(i); 2738 long ypos = _lry - (long) ((point.y - _yMin) * _yscale); 2739 if (prevypos != ypos || prevxpos != xpos) { 2740 _drawBar(graphics, dataset, xpos, ypos, true); 2741 prevypos = ypos; 2742 prevxpos = xpos; 2743 } 2744 } 2745 } 2746 2747 if (bin.errorBar()) { 2748 long prevypos = _prevypos.get(dataset); 2749 long prevxpos = _prevxpos.get(dataset); 2750 for (int i = startPosition; i < endPosition; ++i) { 2751 PlotPoint point = points.get(i); 2752 if (point.errorBar) { 2753 long ypos = _lry - (long) ((point.y - _yMin) * _yscale); 2754 if (prevypos != ypos || prevxpos != xpos) { 2755 _drawErrorBar(graphics, dataset, xpos, _lry 2756 - (long) ((point.yLowEB - _yMin) * _yscale), 2757 _lry - (long) ((point.yHighEB - _yMin) 2758 * _yscale), 2759 true); 2760 prevypos = ypos; 2761 prevxpos = xpos; 2762 2763 } 2764 } 2765 } 2766 } 2767 2768 _prevxpos.set(dataset, xpos); 2769 _prevypos.set(dataset, bin.lastYPos()); 2770 2771 bin.resetDisplayStateAfterPlot(); 2772 2773 _resetColorForDrawing(graphics, false); 2774 } 2775 2776 /** Put a mark corresponding to the specified dataset at the 2777 * specified x and y position. The mark is drawn in the current 2778 * color. What kind of mark is drawn depends on the marks 2779 * argument and the dataset argument. If the fourth argument is 2780 * true, then check the range and plot only points that 2781 * are in range. 2782 * This method should be called only from the event dispatch thread. 2783 * It is not synchronized, so its caller should be. 2784 * @param graphics The graphics context. 2785 * @param dataset The index of the dataset. 2786 * @param xpos The x position. 2787 * @param ypos The y position. 2788 * @param clip If true, then do not draw outside the range. 2789 * @param marks The marks that have to be used for plotting the point. 2790 */ 2791 private void _drawPoint(Graphics graphics, int dataset, long xpos, 2792 long ypos, boolean clip, final int marks) { 2793 // BRDebug System.out.println("_drawPoint, " + xpos + ", " + ypos); 2794 2795 // If the point is not out of range, draw it. 2796 boolean pointinside = ypos <= _lry && ypos >= _uly && xpos <= _lrx 2797 && xpos >= _ulx; 2798 2799 if (!clip || pointinside) { 2800 int xposi = (int) xpos; 2801 int yposi = (int) ypos; 2802 2803 // If the point is out of range, and being drawn, then it is 2804 // probably a legend point. When printing in black and white, 2805 // we want to use a line rather than a point for the legend. 2806 // (So that line patterns are visible). The only exception is 2807 // when the marks style uses distinct marks, or if there is 2808 // no line being drawn. 2809 // NOTE: It is unfortunate to have to test the class of graphics, 2810 // but there is no easy way around this that I can think of. 2811 if (!pointinside && marks != 3 && _isConnected(dataset) 2812 && (graphics instanceof EPSGraphics || !_usecolor)) { 2813 // Use our line styles. 2814 _drawLine(graphics, dataset, xposi - 6, yposi, xposi + 6, yposi, 2815 false, _width); 2816 } else { 2817 // Color display. Use normal legend. 2818 switch (marks) { 2819 case 0: 2820 2821 // If no mark style is given, draw a filled rectangle. 2822 // This is used, for example, to draw the legend. 2823 graphics.fillRect(xposi - 6, yposi - 6, 6, 6); 2824 break; 2825 2826 case 1: 2827 2828 // points -- use 3-pixel ovals. 2829 graphics.fillOval(xposi - 1, yposi - 1, 3, 3); 2830 break; 2831 2832 case 2: 2833 2834 // dots 2835 graphics.fillOval(xposi - _radius, yposi - _radius, 2836 _diameter, _diameter); 2837 break; 2838 2839 case 3: 2840 2841 // various 2842 int[] xpoints; 2843 2844 // various 2845 int[] ypoints; 2846 2847 // Points are only distinguished up to _MAX_MARKS data sets. 2848 int mark = dataset % _MAX_MARKS; 2849 2850 switch (mark) { 2851 case 0: 2852 2853 // filled circle 2854 graphics.fillOval(xposi - _radius, yposi - _radius, 2855 _diameter, _diameter); 2856 break; 2857 2858 case 1: 2859 2860 // cross 2861 graphics.drawLine(xposi - _radius, yposi - _radius, 2862 xposi + _radius, yposi + _radius); 2863 graphics.drawLine(xposi + _radius, yposi - _radius, 2864 xposi - _radius, yposi + _radius); 2865 break; 2866 2867 case 2: 2868 2869 // square 2870 graphics.drawRect(xposi - _radius, yposi - _radius, 2871 _diameter, _diameter); 2872 break; 2873 2874 case 3: 2875 2876 // filled triangle 2877 xpoints = new int[4]; 2878 ypoints = new int[4]; 2879 xpoints[0] = xposi; 2880 ypoints[0] = yposi - _radius; 2881 xpoints[1] = xposi + _radius; 2882 ypoints[1] = yposi + _radius; 2883 xpoints[2] = xposi - _radius; 2884 ypoints[2] = yposi + _radius; 2885 xpoints[3] = xposi; 2886 ypoints[3] = yposi - _radius; 2887 graphics.fillPolygon(xpoints, ypoints, 4); 2888 break; 2889 2890 case 4: 2891 2892 // diamond 2893 xpoints = new int[5]; 2894 ypoints = new int[5]; 2895 xpoints[0] = xposi; 2896 ypoints[0] = yposi - _radius; 2897 xpoints[1] = xposi + _radius; 2898 ypoints[1] = yposi; 2899 xpoints[2] = xposi; 2900 ypoints[2] = yposi + _radius; 2901 xpoints[3] = xposi - _radius; 2902 ypoints[3] = yposi; 2903 xpoints[4] = xposi; 2904 ypoints[4] = yposi - _radius; 2905 graphics.drawPolygon(xpoints, ypoints, 5); 2906 break; 2907 2908 case 5: 2909 2910 // circle 2911 graphics.drawOval(xposi - _radius, yposi - _radius, 2912 _diameter, _diameter); 2913 break; 2914 2915 case 6: 2916 2917 // plus sign 2918 graphics.drawLine(xposi, yposi - _radius, xposi, 2919 yposi + _radius); 2920 graphics.drawLine(xposi - _radius, yposi, 2921 xposi + _radius, yposi); 2922 break; 2923 2924 case 7: 2925 2926 // filled square 2927 graphics.fillRect(xposi - _radius, yposi - _radius, 2928 _diameter, _diameter); 2929 break; 2930 2931 case 8: 2932 2933 // triangle 2934 xpoints = new int[4]; 2935 ypoints = new int[4]; 2936 xpoints[0] = xposi; 2937 ypoints[0] = yposi - _radius; 2938 xpoints[1] = xposi + _radius; 2939 ypoints[1] = yposi + _radius; 2940 xpoints[2] = xposi - _radius; 2941 ypoints[2] = yposi + _radius; 2942 xpoints[3] = xposi; 2943 ypoints[3] = yposi - _radius; 2944 graphics.drawPolygon(xpoints, ypoints, 4); 2945 break; 2946 2947 case 9: 2948 2949 // filled diamond 2950 xpoints = new int[5]; 2951 ypoints = new int[5]; 2952 xpoints[0] = xposi; 2953 ypoints[0] = yposi - _radius; 2954 xpoints[1] = xposi + _radius; 2955 ypoints[1] = yposi; 2956 xpoints[2] = xposi; 2957 ypoints[2] = yposi + _radius; 2958 xpoints[3] = xposi - _radius; 2959 ypoints[3] = yposi; 2960 xpoints[4] = xposi; 2961 ypoints[4] = yposi - _radius; 2962 graphics.fillPolygon(xpoints, ypoints, 5); 2963 break; 2964 default: 2965 throw new RuntimeException("Internal Error. Mark " 2966 + "style " + mark + " not supported."); 2967 } 2968 2969 break; 2970 2971 case 4: 2972 2973 // bigdots 2974 //graphics.setColor(_marksColor); 2975 if (graphics instanceof Graphics2D) { 2976 Object obj = ((Graphics2D) graphics).getRenderingHint( 2977 RenderingHints.KEY_ANTIALIASING); 2978 ((Graphics2D) graphics).setRenderingHint( 2979 RenderingHints.KEY_ANTIALIASING, 2980 RenderingHints.VALUE_ANTIALIAS_ON); 2981 graphics.fillOval(xposi - 4, yposi - 4, 8, 8); 2982 ((Graphics2D) graphics).setRenderingHint( 2983 RenderingHints.KEY_ANTIALIASING, obj); 2984 } else { 2985 graphics.fillOval(xposi - 4, yposi - 4, 8, 8); 2986 } 2987 break; 2988 2989 case 5: 2990 2991 // If the mark style is pixels, draw a filled rectangle. 2992 graphics.fillRect(xposi, yposi, 1, 1); 2993 break; 2994 2995 default: 2996 throw new RuntimeException("Internal Error. Mark " 2997 + "style " + marks + " not supported."); 2998 } 2999 } 3000 } 3001 } 3002 3003 /* Erase the points within the first bin in the given dataset. If 3004 * lines are being drawn, also erase the line to the next points. 3005 * 3006 * This is not synchronized, so the caller should be. Moreover, this 3007 * should only be called in the event dispatch thread. It should only 3008 * be called via deferIfNecessary(). 3009 */ 3010 private void _eraseFirstBin(int dataset) { 3011 // Ensure replot of offscreen buffer. 3012 _plotImage = null; 3013 3014 _checkDatasetIndex(dataset); 3015 3016 // Plot has probably been dismissed. Return. 3017 Graphics graphics = getGraphics(); 3018 3019 ArrayList<PlotPoint> points = _points.get(dataset); 3020 ArrayList<Bin> bins = _bins.get(dataset); 3021 Bin bin = bins.get(0); 3022 int nbrOfBins = bins.size(); 3023 assert nbrOfBins > 0; 3024 3025 long xpos = bin.xpos; 3026 int startPosition = bin.firstPointIndex(); 3027 assert startPosition >= 0; 3028 int endPosition = bin.afterLastPointIndex(); 3029 assert endPosition > startPosition; //Otherwise there is nothing to remove 3030 3031 // Need to check that graphics is not null because plot may have 3032 // been dismissed. 3033 if (_showing && graphics != null) { 3034 _setColorForDrawing(graphics, dataset, false); 3035 //First clear bin itself 3036 long minYPos = bin.minYPos(); 3037 long maxYPos = bin.maxYPos(); 3038 boolean connectedFlag = getConnected(); 3039 3040 if (connectedFlag && bin.isConnected() && minYPos != maxYPos) { 3041 _drawLine(graphics, dataset, xpos, minYPos, xpos, maxYPos, true, 3042 _DEFAULT_WIDTH); 3043 } 3044 3045 // Erase line to the next bin, if appropriate. 3046 if (nbrOfBins > 1) { //We have more than one bin 3047 Bin nextBin = bins.get(1); 3048 long nextx = nextBin.xpos; 3049 3050 // NOTE: I have no idea why I have to give this point backwards. 3051 if (connectedFlag && nextBin.isConnectedWithPreviousBin()) { 3052 _drawLine(graphics, dataset, nextx, nextBin.firstYPos(), 3053 xpos, bin.lastYPos(), true, 2f); 3054 } 3055 } 3056 3057 // Draw decorations that may be specified on a per-dataset basis 3058 Format fmt = _formats.get(dataset); 3059 3060 if (fmt.impulsesUseDefault && _impulses 3061 || !fmt.impulsesUseDefault && fmt.impulses) { 3062 long prevypos = _prevErasedypos.get(dataset); 3063 long prevxpos = _prevErasedxpos.get(dataset); 3064 for (int i = startPosition; i < endPosition; ++i) { 3065 PlotPoint point = points.get(i); 3066 long ypos = _lry - (long) ((point.y - _yMin) * _yscale); 3067 if (prevypos != ypos || prevxpos != xpos) { 3068 _drawImpulse(graphics, xpos, ypos, true); 3069 prevypos = ypos; 3070 prevxpos = xpos; 3071 } 3072 } 3073 } 3074 3075 // Check to see whether the dataset has a marks directive 3076 int marks = _marks; 3077 3078 if (!fmt.marksUseDefault) { 3079 marks = fmt.marks; 3080 } 3081 { 3082 long prevypos = _prevErasedypos.get(dataset); 3083 long prevxpos = _prevErasedxpos.get(dataset); 3084 3085 for (int i = startPosition; i < endPosition; ++i) { 3086 PlotPoint point = points.get(i); 3087 if (marks != 0 || !(connectedFlag && point.connected)) { 3088 long ypos = _lry - (long) ((point.y - _yMin) * _yscale); 3089 if (prevypos != ypos || prevxpos != xpos) { 3090 int updatedMarks = marks; 3091 if (!(connectedFlag && point.connected) 3092 && marks == 0) { 3093 updatedMarks = 2; // marking style: dots 3094 } 3095 // BRDebug System.out.println("Erasing point:" + xpos + ", " + ypos + ", position :" + (i) +", current"); 3096 3097 _drawPoint(graphics, dataset, xpos, ypos, true, 3098 updatedMarks); 3099 prevypos = ypos; 3100 prevxpos = xpos; 3101 } 3102 } 3103 } 3104 } 3105 3106 if (_markDisconnections && marks == 0 && endPosition > startPosition 3107 && endPosition < points.size()) { 3108 PlotPoint point = points.get(endPosition - 1); 3109 if (connectedFlag && point.connected) { 3110 3111 // This point is not connected with the previous one. 3112 // We want to put a dot each end of the at each segment. 3113 // If the previous one was connected no dot was drawn. 3114 // We will now add this extra dot for the previous point. 3115 PlotPoint nextPoint = points.get(endPosition); 3116 if (!(connectedFlag && nextPoint.connected)) { 3117 long ypos = _lry - (long) ((point.y - _yMin) * _yscale); 3118 // BRDebug System.out.println("Erasing point:" + xpos + ", " + ypos + ", position :" + (endPosition-1) + ", previous"); 3119 _drawPoint(graphics, dataset, xpos, ypos, true, 3120 2 /*dots*/); 3121 } 3122 } 3123 } 3124 3125 if (_bars) { 3126 long prevypos = _prevErasedypos.get(dataset); 3127 long prevxpos = _prevErasedxpos.get(dataset); 3128 3129 for (int i = startPosition; i < endPosition; ++i) { 3130 PlotPoint point = points.get(i); 3131 long ypos = _lry - (long) ((point.y - _yMin) * _yscale); 3132 if (prevypos != ypos || prevxpos != xpos) { 3133 _drawBar(graphics, dataset, xpos, ypos, true); 3134 prevypos = ypos; 3135 prevxpos = xpos; 3136 } 3137 } 3138 } 3139 3140 if (bin.errorBar()) { 3141 long prevypos = _prevErasedypos.get(dataset); 3142 long prevxpos = _prevErasedxpos.get(dataset); 3143 3144 for (int i = startPosition; i < endPosition; ++i) { 3145 PlotPoint point = points.get(i); 3146 if (point.errorBar) { 3147 long ypos = _lry - (long) ((point.y - _yMin) * _yscale); 3148 if (prevypos != ypos || prevxpos != xpos) { 3149 _drawErrorBar(graphics, dataset, xpos, _lry 3150 - (long) ((point.yLowEB - _yMin) * _yscale), 3151 _lry - (long) ((point.yHighEB - _yMin) 3152 * _yscale), 3153 true); 3154 prevypos = ypos; 3155 prevxpos = xpos; 3156 } 3157 } 3158 } 3159 } 3160 3161 _resetColorForDrawing(graphics, false); 3162 } 3163 3164 // The following is executed whether the plot is showing or not. 3165 // Remove the bin from the model. 3166 3167 if (nbrOfBins > 1) { 3168 Bin nextBin = bins.get(1); 3169 nextBin.setNotConnectedWithPreviousBin(); 3170 } 3171 3172 if (nbrOfBins == 1) { 3173 //No bins left in the end 3174 _prevxpos.set(dataset, _INITIAL_PREVIOUS_VALUE); 3175 _prevypos.set(dataset, _INITIAL_PREVIOUS_VALUE); 3176 } 3177 3178 // If a point is at the maximum or minimum x or y boundary, 3179 // then flag that boundary needs to be recalculated next time 3180 // fillPlot() is called. 3181 if (xpos == _xBottom || xpos == _xTop || bin.minYPos() == _yBottom 3182 || bin.maxYPos() == _yTop) { 3183 _xyInvalid = true; 3184 } 3185 3186 //Delete points and bin 3187 assert startPosition == 0; //No actually necessary in this code, but it should be valid 3188 for (int i = startPosition; i < endPosition; ++i) { 3189 points.remove(startPosition); 3190 } 3191 assert bin.firstPointIndex() >= 0; 3192 3193 _pointInBinOffset.set(dataset, _pointInBinOffset.get(dataset) 3194 + bin.afterLastPointIndex() - bin.firstPointIndex()); 3195 //FIXME? Warning: Could overflow for scopes with a really large history... 3196 // (the points aren't kept in memory, since it is only here where it goes wrong. 3197 // We could check for overflow and in this case reset the _pointInBinOffset to 3198 // zero, recalculate all bins, and repaint everything. 3199 3200 //This code is actually only checking some invariants. Not revelant in 3201 // production code 3202 if (nbrOfBins > 1) { 3203 Bin nextBin = bins.get(1); 3204 assert nextBin.firstPointIndex() >= 0; //otherwise out of box 3205 assert nextBin.firstPointIndex() == 0; 3206 //This is a combination of two things: first of all we are deleting the first bin and secondly all points should be in a bin 3207 // => the first point in the first bin (once this one has deleted) has to be the first point of all points 3208 3209 } 3210 3211 _prevErasedxpos.set(dataset, xpos); 3212 _prevErasedypos.set(dataset, bin.lastYPos()); 3213 3214 bins.remove(0); 3215 } 3216 3217 /* Erase the point at the given index in the given dataset. If 3218 * lines are being drawn, these lines are erased and if necessary new 3219 * ones will be drawn. 3220 * 3221 * This is not synchronized, so the caller should be. Moreover, this 3222 * should only be called in the event dispatch thread. It should only 3223 * be called via deferIfNecessary(). 3224 */ 3225 private void _erasePoint(int dataset, int index) { 3226 _points.get(dataset).remove(index); 3227 repaint(); 3228 } 3229 3230 /* Rescale so that the data that is currently plotted just fits. 3231 * This overrides the base class method to ensure that the protected 3232 * variables _xBottom, _xTop, _yBottom, and _yTop are valid. 3233 * This method calls repaint(), which causes the display 3234 * to be updated. 3235 * 3236 * This is not synchronized, so the caller should be. Moreover, this 3237 * should only be called in the event dispatch thread. It should only 3238 * be called via deferIfNecessary(). 3239 */ 3240 private void _fillPlot() { 3241 if (_xyInvalid) { 3242 // Recalculate the boundaries based on currently visible data 3243 _xBottom = Double.MAX_VALUE; 3244 _xTop = -Double.MAX_VALUE; 3245 _yBottom = Double.MAX_VALUE; 3246 _yTop = -Double.MAX_VALUE; 3247 3248 for (int dataset = 0; dataset < _points.size(); dataset++) { 3249 ArrayList<PlotPoint> points = _points.get(dataset); 3250 3251 for (int index = 0; index < points.size(); index++) { 3252 PlotPoint pt = points.get(index); 3253 3254 if (pt.x < _xBottom) { 3255 _xBottom = pt.x; 3256 } 3257 3258 if (pt.x > _xTop) { 3259 _xTop = pt.x; 3260 } 3261 3262 if (pt.y < _yBottom) { 3263 _yBottom = pt.y; 3264 } 3265 3266 if (pt.y > _yTop) { 3267 _yTop = pt.y; 3268 } 3269 } 3270 } 3271 } 3272 3273 _xyInvalid = false; 3274 3275 // If this is a bar graph, then make sure the Y range includes 0 3276 if (_bars) { 3277 if (_yBottom > 0.0) { 3278 _yBottom = 0.0; 3279 } 3280 3281 if (_yTop < 0.0) { 3282 _yTop = 0.0; 3283 } 3284 } 3285 3286 super.fillPlot(); 3287 } 3288 3289 // Return true if the specified dataset is connected by default. 3290 private boolean _isConnected(int dataset) { 3291 if (dataset < 0) { 3292 return _connected; 3293 } 3294 3295 _checkDatasetIndex(dataset); 3296 3297 Format fmt = _formats.get(dataset); 3298 3299 if (fmt.connectedUseDefault) { 3300 return _connected; 3301 } else { 3302 return fmt.connected; 3303 } 3304 } 3305 3306 /** Reset the color for drawing. This typically needs to happen after having drawn 3307 * a bin or erasing one. 3308 * @param graphics The graphics context. 3309 * @param forceExorWithBackground Restore the paint made back from exor mode 3310 */ 3311 private void _resetColorForDrawing(Graphics graphics, 3312 boolean forceExorWithBackground) { 3313 // Restore the color, in case the box gets redrawn. 3314 graphics.setColor(_foreground); 3315 3316 if (_pointsPersistence > 0 || _xPersistence > 0.0 3317 || forceExorWithBackground) { 3318 // Restore paint mode in case axes get redrawn. 3319 graphics.setPaintMode(); 3320 } 3321 } 3322 3323 /** Schedule a bin to be (re)drawn due to the addition of bin. 3324 */ 3325 private void _scheduleBinRedrawAdd(int dataset, boolean binAdded) { 3326 while (_scheduledBinsToAdd.size() <= dataset) { 3327 _scheduledBinsToAdd.add(0); 3328 } 3329 int previousCount = _scheduledBinsToAdd.get(dataset); 3330 if (binAdded || previousCount == 0) { 3331 _scheduledBinsToAdd.set(dataset, previousCount + 1); 3332 _needBinRedraw = true; 3333 } 3334 } 3335 3336 /** Schedule a bin to be (re)drawn due to the removal of a bin. 3337 */ 3338 private void _scheduleBinRedrawRemove(int dataset, 3339 int nbrOfElementsToErase) { 3340 while (_scheduledBinsToErase.size() <= dataset) { 3341 _scheduledBinsToErase.add(0); 3342 } 3343 _scheduledBinsToErase.set(dataset, Math.max(nbrOfElementsToErase, 3344 _scheduledBinsToErase.get(dataset))); 3345 _needBinRedraw = true; 3346 } 3347 3348 /** Set the color for drawing. This typically needs to happen before drawing 3349 * a bin or erasing one. 3350 * @param graphics The graphics context. 3351 * @param dataset The index of the dataset. 3352 * @param forceExorWithBackground Force to go into exor mode. 3353 */ 3354 private void _setColorForDrawing(Graphics graphics, int dataset, 3355 boolean forceExorWithBackground) { 3356 if (_pointsPersistence > 0 || _xPersistence > 0.0 3357 || forceExorWithBackground) { 3358 // To allow erasing to work by just redrawing the points. 3359 if (_background == null) { 3360 // java.awt.Component.setBackground(color) says that 3361 // if the color "parameter is null then this component 3362 // will inherit the background color of its parent." 3363 graphics.setXORMode(getBackground()); 3364 } else { 3365 graphics.setXORMode(_background); 3366 } 3367 } 3368 3369 // Set the color 3370 if (_usecolor) { 3371 // NOTE: Temporary to use the same color three times. 3372 // int color = (dataset/3) % _colors.length; 3373 int color = dataset % _colors.length; 3374 graphics.setColor(_colors[color]); 3375 } else { 3376 graphics.setColor(_foreground); 3377 } 3378 } 3379 3380 /////////////////////////////////////////////////////////////////// 3381 //// private variables //// 3382 3383 /** True when disconnections should be marked.*/ 3384 private boolean _markDisconnections = false; 3385 3386 /** @serial Offset per dataset in x axis units. */ 3387 private volatile double _barOffset = 0.05; 3388 3389 /** @serial True if this is a bar plot. */ 3390 private boolean _bars = false; 3391 3392 /** @serial Width of a bar in x axis units. */ 3393 private volatile double _barWidth = 0.5; 3394 3395 /** 3396 * An arraylist of the bins in the plot. A bin is represents a number of points that are all displayed on the same x position. 3397 * A bin is meant to avoid superfluous drawings of lines. So instead of having to draw 3398 * a line between each point, you can draw a line between the minimum and maximum and maximum 3399 * position. 3400 */ 3401 private ArrayList<ArrayList<Bin>> _bins = new ArrayList<ArrayList<Bin>>(); 3402 3403 /** @serial True if the points are connected. */ 3404 private boolean _connected = true; 3405 3406 /** @serial Give the diameter of a point for efficiency. */ 3407 private int _diameter = 6; 3408 3409 /** The initial default width. 3410 */ 3411 private static final float _DEFAULT_WIDTH = 2f; 3412 3413 // Half of the length of the error bar horizontal leg length; 3414 private static final int _ERRORBAR_LEG_LENGTH = 5; 3415 3416 /** @serial Is this the first datapoint in a set? */ 3417 private boolean _firstInSet = true; 3418 3419 /** @serial Format information on a per data set basis. */ 3420 private ArrayList<Format> _formats = new ArrayList<Format>(); 3421 3422 /** Cached copy of graphics, needed to reset when we are exporting 3423 * to EPS. 3424 */ 3425 private Graphics _graphics = null; 3426 3427 /** @serial True if this is an impulse plot. */ 3428 private boolean _impulses = false; 3429 3430 /** Initial value for elements in _prevx and _prevy that indicate 3431 * we have not yet seen data. 3432 */ 3433 private static final Long _INITIAL_PREVIOUS_VALUE = Long.MIN_VALUE; 3434 3435 // We keep track of the last dot that has been add to be able to 3436 // remove the dot again in case an extra point was added afterwards. 3437 private HashMap<Integer, PlotPoint> _lastPointWithExtraDot = new HashMap<Integer, PlotPoint>(); 3438 3439 // A stroke of width 1. 3440 private static final BasicStroke _LINE_STROKE1 = new BasicStroke(1f, 3441 BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); 3442 3443 // A stroke of width 2. 3444 private static final BasicStroke _LINE_STROKE2 = new BasicStroke(2f, 3445 BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); 3446 3447 /** True if different line styles should be used. */ 3448 private boolean _lineStyles = false; 3449 3450 /** True if different line styles should be used. */ 3451 private static String[] _LINE_STYLES_ARRAY = { "solid", "dotted", "dashed", 3452 "dotdashed", "dotdotdashed" }; 3453 3454 /** @serial The highest data set used. */ 3455 private int _maxDataset = -1; 3456 3457 // Maximum number of different marks 3458 // NOTE: There are 11 colors in the base class. Combined with 10 3459 // marks, that makes 110 unique signal identities. 3460 private static final int _MAX_MARKS = 10; 3461 3462 // True when a bin has been changed and it need to be repainted 3463 // by the next scheduled repaint. 3464 private boolean _needBinRedraw = false; 3465 3466 // True when a the plot need to be refilled 3467 // by the next scheduled repaint. 3468 private boolean _needPlotRefill = false; 3469 3470 /** 3471 * Points in bins have an absolute index within the virtual array 3472 * of all points that once existed in Plot. This is done to avoid 3473 * having to change all bins when points in the beginning are removed. 3474 * The offset _pointInBinOffset is a positive number that denotes the 3475 * difference between the index of the point in the current point arraylist 3476 * and the virtual one containing all points. 3477 */ 3478 private ArrayList<Integer> _pointInBinOffset = new ArrayList<Integer>(); 3479 3480 /** @serial Number of points to persist for. */ 3481 private int _pointsPersistence = 0; 3482 3483 /** @serial Information about the previously plotted point. */ 3484 private ArrayList<Long> _prevxpos = new ArrayList<Long>(); 3485 3486 /** @serial Information about the previously plotted point. */ 3487 private ArrayList<Long> _prevypos = new ArrayList<Long>(); 3488 3489 /** @serial Information about the previously erased point. */ 3490 private ArrayList<Long> _prevErasedxpos = new ArrayList<Long>(); 3491 3492 /** @serial Information about the previously erased point. */ 3493 private ArrayList<Long> _prevErasedypos = new ArrayList<Long>(); 3494 3495 /** @serial Give the radius of a point for efficiency. */ 3496 private int _radius = 3; 3497 3498 /** @serial True if we saw 'reusedatasets: on' in the file. */ 3499 private boolean _reuseDatasets = false; 3500 3501 /** @serial Have we seen a DataSet line in the current data file? */ 3502 private boolean _sawFirstDataSet = false; 3503 3504 // _scheduledBinsToAdd a a list a bins that should be added by the scheduled 3505 // repaint. 3506 private ArrayList<Integer> _scheduledBinsToAdd = new ArrayList<Integer>(); 3507 3508 // _scheduledBinsToAdd a a list a bins that should be erased by the scheduled 3509 // repaint. 3510 private ArrayList<Integer> _scheduledBinsToErase = new ArrayList<Integer>(); 3511 3512 /** @serial Set by _drawPlot(), and reset by clear(). */ 3513 private boolean _showing = false; 3514 3515 /** @serial Persistence in units of the horizontal axis. */ 3516 private double _xPersistence = 0.0; 3517 3518 /** @serial Flag indicating validity of _xBottom, _xTop, 3519 * _yBottom, and _yTop. 3520 */ 3521 private boolean _xyInvalid = true; 3522 3523 /** The width of the current stroke. Only effective if the 3524 * Graphics is a Graphics2D. 3525 * 3526 */ 3527 private float _width = _DEFAULT_WIDTH; 3528 3529 /////////////////////////////////////////////////////////////////// 3530 //// inner classes //// 3531 3532 /** 3533 * A bin is represents a number of points that are all displayed on the same x position. 3534 * A bin is meant to avoid superfluous drawings of lines. So instead of having to draw 3535 * a line between each point, you can draw a line between the minimum and maximum and maximum 3536 * y position. 3537 */ 3538 private class Bin { 3539 public Bin(long xPos, int dataset, double[] derivs) { 3540 _dataset = dataset; 3541 xpos = xPos; 3542 _derivs = derivs; 3543 } 3544 3545 /** 3546 * Add a point to the bin, with a certain pointIndex (the index in the array of all points in a Plot), 3547 * and a certain ypos. 3548 * Precondition: The xpos of the point should be same as other points already within the bin 3549 */ 3550 public void addPoint(PlotPoint point, int pointIndex, long ypos) { 3551 int absolutePointIndex = pointIndex 3552 + _pointInBinOffset.get(_dataset); 3553 //The absolute point index is a index in the list of 3554 // all points that once existed in the plot 3555 3556 if (_maxYPos < ypos) { 3557 _maxYPos = ypos; 3558 _rangeChanged = true; 3559 } 3560 if (_minYPos > ypos) { 3561 _minYPos = ypos; 3562 _rangeChanged = true; 3563 } 3564 3565 if (_firstPointIndex == -1) { 3566 _needConnectionWithPreviousBin = point.connected; 3567 _firstYPos = ypos; 3568 _firstPointIndex = absolutePointIndex; 3569 _nextPointToPlot = _firstPointIndex; 3570 } else { 3571 _isConnected |= point.connected; 3572 // if one point is connected within the bin, all points will be (it is difficult to do this otherwise) 3573 3574 assert _afterLastPointIndex == absolutePointIndex; //Bin intervals should be contiguous intervals 3575 } 3576 3577 _afterLastPointIndex = absolutePointIndex + 1; 3578 _lastYPos = ypos; 3579 3580 _errorBar |= point.errorBar; 3581 } 3582 3583 /** 3584 * Return the position after the last point of the range of points within the bin 3585 * This index is the index within the current points of the plot. 3586 */ 3587 public int afterLastPointIndex() { 3588 assert _firstPointIndex != -1; 3589 return _afterLastPointIndex - _pointInBinOffset.get(_dataset); 3590 } 3591 3592 /** 3593 * Return true in case there is one point that needs an error bar, otherwise false 3594 */ 3595 public boolean errorBar() { 3596 return _errorBar; 3597 } 3598 3599 /** 3600 * Return the position of the first point of the range of points within the bin 3601 * This index is the index within the current points of the plot. 3602 */ 3603 public int firstPointIndex() { 3604 assert _firstPointIndex != -1; 3605 return _firstPointIndex - _pointInBinOffset.get(_dataset); 3606 } 3607 3608 /** 3609 * Return the y position of the first added point in the bin. 3610 */ 3611 // Precondition: only valid in case there is at least one point 3612 public long firstYPos() { 3613 assert _firstPointIndex != -1; 3614 return _firstYPos; 3615 } 3616 3617 /** 3618 * Return the minimum y position of the bin. 3619 * Precondition: only valid in case there is at least one point 3620 */ 3621 public long minYPos() { 3622 assert _firstPointIndex != -1; 3623 return _minYPos; 3624 } 3625 3626 /** 3627 * Return the y position of the last added point in the bin. 3628 * Precondition: only valid in case there is at least one point 3629 */ 3630 public long lastYPos() { 3631 assert _firstPointIndex != -1; 3632 return _lastYPos; 3633 } 3634 3635 /** 3636 * Return the maximum y position of the bin. 3637 * Precondition: only valid in case there is at least one point 3638 */ 3639 public long maxYPos() { 3640 assert _firstPointIndex != -1; 3641 return _maxYPos; 3642 } 3643 3644 /** 3645 * Return the derivatives at this point (in the original coordinate space). 3646 * The array must not be modified. 3647 */ 3648 public double[] getDerivs() { 3649 return _derivs; 3650 } 3651 3652 /** 3653 * Return whether a line should be drawn with the previous bin. 3654 * After you have reset the display state, the boolean return false 3655 */ 3656 public boolean needConnectionWithPreviousBin() { 3657 return _needConnectionWithPreviousBin; 3658 } 3659 3660 /** 3661 * Return true a line has been drawn to the previous bin. 3662 */ 3663 public boolean isConnectedWithPreviousBin() { 3664 return _isConnectedWithPreviousBin; 3665 } 3666 3667 public boolean isConnected() { 3668 return _isConnected; 3669 } 3670 3671 /** 3672 * Return true when the bin should be plotted (again) 3673 */ 3674 public boolean needReplot() { 3675 return _needConnectionWithPreviousBin || _rangeChanged 3676 || _nextPointToPlot != _afterLastPointIndex; 3677 } 3678 3679 /** 3680 * Return the position of the next point of the bin that should be plotted 3681 * This index is the index within the current points of the plot. 3682 */ 3683 public int nextPointToPlot() { 3684 return _nextPointToPlot - _pointInBinOffset.get(_dataset); 3685 } 3686 3687 /** 3688 * Return true when the rangeChanged 3689 */ 3690 public boolean rangeChanged() { 3691 return _rangeChanged; 3692 } 3693 3694 /** 3695 * Reset the plot state for this bin when you have 3696 * plotted this bin/ 3697 */ 3698 public void resetDisplayStateAfterPlot() { 3699 if (_needConnectionWithPreviousBin) { 3700 _isConnectedWithPreviousBin = true; 3701 _needConnectionWithPreviousBin = false; 3702 } 3703 _rangeChanged = false; 3704 _nextPointToPlot = _afterLastPointIndex; 3705 } 3706 3707 /** 3708 * Disconnect this bin with the previous bin. 3709 */ 3710 public void setNotConnectedWithPreviousBin() { 3711 _needConnectionWithPreviousBin = false; 3712 _isConnectedWithPreviousBin = false; 3713 _points.get(_dataset).get(_firstPointIndex 3714 - _pointInBinOffset.get(_dataset)).connected = false; 3715 } 3716 3717 public final long xpos; 3718 3719 public final double[] _derivs; 3720 3721 private int _afterLastPointIndex = 0; 3722 3723 private int _dataset = 0; 3724 3725 // _errorBar is true in case there is one point that needs an error bar, otherwise false 3726 private boolean _errorBar = false; 3727 3728 private int _firstPointIndex = -1; 3729 3730 private long _firstYPos = java.lang.Long.MIN_VALUE; 3731 3732 private boolean _isConnected = false; 3733 private boolean _isConnectedWithPreviousBin = false; 3734 3735 private long _lastYPos = java.lang.Long.MIN_VALUE; 3736 3737 private long _maxYPos = java.lang.Long.MIN_VALUE; 3738 private long _minYPos = java.lang.Long.MAX_VALUE; 3739 3740 // Indicate whether a line has to be added with previous bin or not 3741 // Once the line has been drawn, the boolean should be switched to false 3742 private boolean _needConnectionWithPreviousBin = false; 3743 3744 private boolean _rangeChanged = false; 3745 private int _nextPointToPlot = 0; 3746 } 3747 3748 private static class Format implements Serializable { 3749 // FindBugs suggests making this class static so as to decrease 3750 // the size of instances and avoid dangling references. 3751 3752 // Indicate whether the current dataset is connected. 3753 public boolean connected; 3754 3755 // Indicate whether the above variable should be ignored. 3756 public boolean connectedUseDefault = true; 3757 3758 // Indicate whether a stem plot should be drawn for this data set. 3759 // This is ignored unless the following variable is set to false. 3760 public boolean impulses; 3761 3762 // Indicate whether the above variable should be ignored. 3763 public boolean impulsesUseDefault = true; 3764 3765 // The stroke used for lines 3766 // This is ignored unless strokeUseDefault is true 3767 public BasicStroke lineStroke; 3768 3769 // The name of the stroke, see Plot.setLineStyle() 3770 // This is ignored unless strokeUseDefault is true 3771 public String lineStyle; 3772 3773 // Indicate whether lineStroke should be used 3774 public boolean lineStyleUseDefault = true; 3775 3776 // Indicate what type of mark to use. 3777 // This is ignored unless the following variable is set to false. 3778 public int marks; 3779 3780 // Indicate whether the above variable should be ignored. 3781 public boolean marksUseDefault = true; 3782 } 3783}