001/* A labeled box for signal plots. 002 003 @Copyright (c) 1997-2016 The Regents of the University of California. 004 All rights reserved. 005 006 Permission is hereby granted, without written agreement and without 007 license or royalty fees, to use, copy, modify, and distribute this 008 software and its documentation for any purpose, provided that the 009 above copyright notice and the following two paragraphs appear in all 010 copies of this software. 011 012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 016 SUCH DAMAGE. 017 018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 023 ENHANCEMENTS, OR MODIFICATIONS. 024 025 PT_COPYRIGHT_VERSION_2 026 COPYRIGHTENDKEY 027 */ 028package ptolemy.plot; 029 030import java.awt.Color; 031import java.awt.Component; 032import java.awt.Dimension; 033import java.awt.EventQueue; 034import java.awt.FlowLayout; 035import java.awt.Font; 036import java.awt.FontMetrics; 037import java.awt.Graphics; 038import java.awt.Graphics2D; 039import java.awt.Rectangle; 040import java.awt.RenderingHints; 041import java.awt.event.ActionEvent; 042import java.awt.event.ActionListener; 043import java.awt.event.InputEvent; 044import java.awt.event.KeyEvent; 045import java.awt.event.KeyListener; 046import java.awt.event.MouseEvent; 047import java.awt.event.MouseListener; 048import java.awt.event.MouseMotionListener; 049import java.awt.event.MouseWheelEvent; 050import java.awt.event.MouseWheelListener; 051import java.awt.image.BufferedImage; 052import java.awt.print.PageFormat; 053import java.awt.print.Printable; 054import java.awt.print.PrinterException; 055import java.awt.print.PrinterJob; 056import java.io.BufferedOutputStream; 057import java.io.BufferedReader; 058import java.io.BufferedWriter; 059import java.io.DataInputStream; 060import java.io.File; 061import java.io.FileInputStream; 062import java.io.FileNotFoundException; 063import java.io.IOException; 064import java.io.InputStream; 065import java.io.InputStreamReader; 066import java.io.OutputStream; 067import java.io.OutputStreamWriter; 068import java.io.PrintStream; 069import java.io.PrintWriter; 070import java.io.Writer; 071import java.net.MalformedURLException; 072import java.net.URL; 073import java.util.Enumeration; 074import java.util.HashSet; 075import java.util.LinkedList; 076import java.util.Locale; 077import java.util.Set; 078import java.util.Timer; 079import java.util.TimerTask; 080import java.util.Vector; 081 082import javax.imageio.ImageIO; 083import javax.print.attribute.HashPrintRequestAttributeSet; 084import javax.print.attribute.PrintRequestAttributeSet; 085import javax.swing.ImageIcon; 086import javax.swing.JButton; 087import javax.swing.JOptionPane; 088import javax.swing.JPanel; 089import javax.swing.SwingUtilities; 090import javax.swing.UIManager; 091 092import ptolemy.util.FileUtilities; 093import ptolemy.util.StringUtilities; 094 095// TO DO: 096// - Augment getColorByName to support a full complement of colors 097// (get the color list from Tycho). 098/////////////////////////////////////////////////////////////////// 099//// PlotBox 100 101/** 102 A labeled box within which to place a data plot. 103 <p>A title, X and Y axis labels, tick marks, and a legend are all 104 supported. Zooming in and out is supported. To zoom in, click and 105 hold mouse button 1 and drag the mouse downwards to draw a box. To 106 zoom out, click and hold mouse button1 and drag the mouse upward. 107 <p> 108 The box can be configured either through a file with commands or 109 through direct invocation of the public methods of the class. 110 <p> 111 When calling the methods, in most cases the changes will not 112 be visible until paintComponent() has been called. To request that this 113 be done, call repaint(). 114 <p> 115 A small set of key bindings are provided for convenience. 116 They are: 117 <ul> 118 <li> Cntrl-c: Export the plot to the clipboard (in PlotML). 119 <li> D: Dump the plot to standard output (in PlotML). 120 <li> E: Export the plot to standard output in EPS format. 121 <li> F: Fill the plot. 122 <li> H or ?: Display a simple help message. 123 <li> Cntrl-D or Q: quit 124 </ul> 125 These commands are provided in a menu by the PlotFrame class. 126 Note that exporting to the clipboard is not allowed in applets 127 (it used to be), so this will result in an error message. 128 <p> 129 At this time, the two export commands produce encapsulated postscript 130 tuned for black-and-white printers. In the future, more formats may 131 supported. 132 Exporting to the clipboard and to standard output, in theory, 133 is allowed for applets, unlike writing to a file. Thus, these 134 key bindings provide a simple mechanism to obtain a high-resolution 135 image of the plot from an applet, suitable for incorporation in 136 a document. However, in some browsers, exporting to standard out 137 triggers a security violation. You can use the JDK appletviewer instead. 138 <p> 139 To read commands from a file or URL, the preferred technique is 140 to use one of the classes in the plotml package. That package 141 supports both PlotML, an XML extension for plots, and a historical 142 file format specific to ptplot. The historical file format is 143 understood by the read() method in this class. 144 The syntax of the historical format, documented below, is rudimentary, 145 and will probably not be extended as ptplot evolves. Nonetheless, 146 we document it here since it is directly supported by this class. 147 <p> 148 The historical format for the file allows any number 149 commands, one per line. Unrecognized commands and commands with 150 syntax errors are ignored. Comments are denoted by a line starting 151 with a pound sign "#". The recognized commands include: 152 <pre> 153 TitleText: <i>string</i> 154 XLabel: <i>string</i> 155 YLabel: <i>string</i> 156 </pre> 157 These commands provide a title and labels for the X (horizontal) and Y 158 (vertical) axes. 159 A <i>string</i> is simply a sequence of characters, possibly 160 including spaces. There is no need here to surround them with 161 quotation marks, and in fact, if you do, the quotation marks will 162 be included in the labels. 163 <p> 164 The ranges of the X and Y axes can be optionally given by commands like: 165 <pre> 166 XRange: <i>min</i>, <i>max</i> 167 YRange: <i>min</i>, <i>max</i> 168 </pre> 169 The arguments <i>min</i> and <i>max</i> are numbers, possibly 170 including a sign and a decimal point. If they are not specified, 171 then the ranges are computed automatically from the data and padded 172 slightly so that datapoints are not plotted on the axes. 173 <p> 174 The tick marks for the axes are usually computed automatically from 175 the ranges. Every attempt is made to choose reasonable positions 176 for the tick marks regardless of the data ranges (powers of 177 ten multiplied by 1, 2, or 5 are used). However, they can also be 178 specified explicitly using commands like: 179 <pre> 180 XTicks: <i>label position, label position, ...</i> 181 YTicks: <i>label position, label position, ...</i> 182 </pre> 183 A <i>label</i> is a string that must be surrounded by quotation 184 marks if it contains any spaces. A <i>position</i> is a number 185 giving the location of the tick mark along the axis. For example, 186 a horizontal axis for a frequency domain plot might have tick marks 187 as follows: 188 <pre> 189 XTicks: -PI -3.14159, -PI/2 -1.570795, 0 0, PI/2 1.570795, PI 3.14159 190 </pre> 191 Tick marks could also denote years, months, days of the week, etc. 192 <p> 193 The X and Y axes can use a logarithmic scale with the following commands: 194 <pre> 195 XLog: on 196 YLog: on 197 </pre> 198 The grid labels represent powers of 10. Note that if a logarithmic 199 scale is used, then the values must be positive. Non-positive values 200 will be silently dropped. Note further that when using logarithmic 201 axes that the log of input data is taken as the data is added to the plot. 202 This means that <pre>XLog: on</pre> or <pre>YLog: on</pre> should 203 appear before any data. Also, the value of the XTicks, YTicks, 204 XRange or YRange directives should be in log units. 205 So, <pre>XTicks: 1K 3</pre> will display the string <pre>1K</pre> 206 at the 1000 mark. 207 <p> 208 By default, tick marks are connected by a light grey background grid. 209 This grid can be turned off with the following command: 210 <pre> 211 Grid: off 212 </pre> 213 It can be turned back on with 214 <pre> 215 Grid: on 216 </pre> 217 Also, by default, the first ten data sets are shown each in a unique color. 218 The use of color can be turned off with the command: 219 <pre> 220 Color: off 221 </pre> 222 It can be turned back on with 223 <pre> 224 Color: on 225 </pre> 226 Finally, the rather specialized command 227 <pre> 228 Wrap: on 229 </pre> 230 enables wrapping of the X (horizontal) axis, which means that if 231 a point is added with X out of range, its X value will be modified 232 modulo the range so that it lies in range. This command only has an 233 effect if the X range has been set explicitly. It is designed specifically 234 to support oscilloscope-like behavior, where the X value of points is 235 increasing, but the display wraps it around to left. A point that lands 236 on the right edge of the X range is repeated on the left edge to give 237 a better sense of continuity. The feature works best when points do land 238 precisely on the edge, and are plotted from left to right, increasing 239 in X. 240 <p> 241 All of the above commands can also be invoked directly by calling the 242 the corresponding public methods from some Java procedure. 243 <p> 244 This class uses features of JDK 1.2, and hence if used in an applet, 245 it can only be viewed by a browser that supports JDK 1.2, or a plugin. 246 247 @author Edward A. Lee, Christopher Brooks, Contributors: Jun Wu (jwu@inin.com.au), William Wu, Robert Kroeger, Tom Peachey, Bert Rodiers, Dirk Bueche 248 249 @version $Id$ 250 @since Ptolemy II 0.2 251 @Pt.ProposedRating Yellow (cxh) 252 @Pt.AcceptedRating Yellow (cxh) 253 */ 254@SuppressWarnings("serial") 255public class PlotBox extends JPanel implements Printable, PlotBoxInterface { 256 /////////////////////////////////////////////////////////////////// 257 //// constructor //// 258 259 /** Construct a plot box with a default configuration. */ 260 public PlotBox() { 261 // If we make this transparent, the background shows through. 262 // However, we assume that the user will set the background. 263 // NOTE: A component is transparent by default (?). 264 // setOpaque(false); 265 setOpaque(true); 266 267 // Create a right-justified layout with spacing of 2 pixels. 268 setLayout(new FlowLayout(FlowLayout.RIGHT, 2, 2)); 269 addMouseListener(new ZoomListener()); 270 addMouseWheelListener(new ZoomListener2()); // Dirk: zooming with the mouse wheel 271 addMouseListener(new MoveListener()); // Dirk: move plotted objects with 3rd mouse button 272 addMouseMotionListener(new MoveMotionListener()); // Dirk 273 addKeyListener(new CommandListener()); 274 addMouseMotionListener(new DragListener()); 275 276 // This is something we want to do only once... 277 _measureFonts(); 278 279 // Request the focus so that key events are heard. 280 // NOTE: no longer needed? 281 // requestFocus(); 282 } 283 284 /////////////////////////////////////////////////////////////////// 285 //// public methods //// 286 287 /** Add a line to the caption (displayed at below graph) . 288 * @param captionLine The string to be added. 289 * @see #getCaptions() 290 */ 291 @Override 292 public synchronized void addCaptionLine(String captionLine) { 293 // Caption code contributed by Tom Peachey. 294 // Changing legend means we need to repaint the offscreen buffer. 295 _plotImage = null; 296 _captionStrings.addElement(captionLine); 297 } 298 299 /** Add a legend (displayed at the upper right) for the specified 300 * data set with the specified string. Short strings generally 301 * fit better than long strings. If the string is empty, or the 302 * argument is null, then no legend is added. 303 * @param dataset The dataset index. 304 * @param legend The label for the dataset. 305 * @see #renameLegend(int, String) 306 */ 307 @Override 308 public synchronized void addLegend(int dataset, String legend) { 309 // Changing legend means we need to repaint the offscreen buffer. 310 _plotImage = null; 311 312 if (legend == null || legend.equals("")) { 313 return; 314 } 315 316 _legendStrings.addElement(legend); 317 _legendDatasets.addElement(Integer.valueOf(dataset)); 318 } 319 320 /** Specify a tick mark for the X axis. The label given is placed 321 * on the axis at the position given by <i>position</i>. If this 322 * is called once or more, automatic generation of tick marks is 323 * disabled. The tick mark will appear only if it is within the X 324 * range. 325 * <p>Note that if {@link #setXLog(boolean)} has been called, then 326 * the position value should be in log units. 327 * So, addXTick("1K", 3) will display the string <pre>1K</pre> 328 * at the 1000 mark. 329 * @param label The label for the tick mark. 330 * @param position The position on the X axis. 331 */ 332 @Override 333 public synchronized void addXTick(String label, double position) { 334 // Changing legend means we need to repaint the offscreen buffer. 335 _plotImage = null; 336 337 if (_xticks == null) { 338 _xticks = new Vector(); 339 _xticklabels = new Vector(); 340 } 341 342 _xticks.addElement(Double.valueOf(position)); 343 _xticklabels.addElement(label); 344 } 345 346 /** Specify a tick mark for the Y axis. The label given is placed 347 * on the axis at the position given by <i>position</i>. If this 348 * is called once or more, automatic generation of tick marks is 349 * disabled. The tick mark will appear only if it is within the Y 350 * range. 351 * <p>Note that if {@link #setYLog(boolean)} has been called, then 352 * the position value should be in log units. 353 * So, addYTick("1K", 3) will display the string <pre>1K</pre> 354 * at the 1000 mark. 355 * @param label The label for the tick mark. 356 * @param position The position on the Y axis. 357 */ 358 @Override 359 public synchronized void addYTick(String label, double position) { 360 // Changing legend means we need to repaint the offscreen buffer. 361 _plotImage = null; 362 363 if (_yticks == null) { 364 _yticks = new Vector(); 365 _yticklabels = new Vector(); 366 } 367 368 _yticks.addElement(Double.valueOf(position)); 369 _yticklabels.addElement(label); 370 } 371 372 /** If the argument is true, clear the axes. I.e., set all parameters 373 * controlling the axes to their initial conditions. 374 * For the change to take effect, call repaint(). If the argument 375 * is false, do nothing. 376 * @param axes If true, clear the axes parameters. 377 */ 378 @Override 379 public synchronized void clear(boolean axes) { 380 // We need to repaint the offscreen buffer. 381 _plotImage = null; 382 383 _xBottom = Double.MAX_VALUE; 384 _xTop = -Double.MAX_VALUE; 385 _yBottom = Double.MAX_VALUE; 386 _yTop = -Double.MAX_VALUE; 387 388 if (axes) { 389 // Protected members first. 390 _yMax = 0; 391 _yMin = 0; 392 _xMax = 0; 393 _xMin = 0; 394 _xRangeGiven = false; 395 _yRangeGiven = false; 396 _originalXRangeGiven = false; 397 _originalYRangeGiven = false; 398 _rangesGivenByZooming = false; 399 _xlog = false; 400 _ylog = false; 401 _grid = true; 402 _wrap = false; 403 _usecolor = true; 404 405 // Private members next... 406 _filespec = null; 407 _xlabel = null; 408 _ylabel = null; 409 _title = null; 410 _legendStrings = new Vector(); 411 _legendDatasets = new Vector(); 412 _xticks = null; 413 _xticklabels = null; 414 _yticks = null; 415 _yticklabels = null; 416 } 417 } 418 419 /** Clear all the captions. 420 * For the change to take effect, call repaint(). 421 * @see #setCaptions(Vector) 422 */ 423 @Override 424 public synchronized void clearCaptions() { 425 // Changing caption means we need to repaint the offscreen buffer. 426 _plotImage = null; 427 _captionStrings = new Vector(); 428 } 429 430 /** Clear all legends. This will show up on the next redraw. 431 */ 432 @Override 433 public synchronized void clearLegends() { 434 // Changing legend means we need to repaint the offscreen buffer. 435 _plotImage = null; 436 437 _legendStrings = new Vector(); 438 _legendDatasets = new Vector(); 439 } 440 441 /** If this method is called in the event thread, then simply 442 * execute the specified action. Otherwise, 443 * if there are already deferred actions, then add the specified 444 * one to the list. Otherwise, create a list of deferred actions, 445 * if necessary, and request that the list be processed in the 446 * event dispatch thread. 447 * 448 * Note that it does not work nearly as well to simply schedule 449 * the action yourself on the event thread because if there are a 450 * large number of actions, then the event thread will not be able 451 * to keep up. By grouping these actions, we avoid this problem. 452 * 453 * This method is not synchronized, so the caller should be. 454 * @param action The Runnable object to execute. 455 */ 456 @Override 457 public void deferIfNecessary(Runnable action) { 458 // In swing, updates to showing graphics must be done in the 459 // event thread. If we are in the event thread, then proceed. 460 // Otherwise, queue a request or add to a pending request. 461 if (EventQueue.isDispatchThread()) { 462 action.run(); 463 } else { 464 465 // Add the specified action to the list of actions to perform. 466 _deferredActions.add(action); 467 468 // If it hasn't already been requested, request that actions 469 // be performed in the event dispatch thread. 470 if (!_actionsDeferred) { 471 Runnable doActions = new Runnable() { 472 @Override 473 public void run() { 474 _executeDeferredActions(); 475 } 476 }; 477 478 try { 479 _actionsDeferred = true; 480 481 // NOTE: Using invokeAndWait() here risks causing 482 // deadlock. Don't do it! 483 SwingUtilities.invokeLater(doActions); 484 } catch (Throwable throwable) { 485 // Ignore InterruptedException. 486 // Other exceptions should not occur. 487 } 488 489 } 490 } 491 } 492 493 /** Destroy the plotter. This method is usually 494 * called by PlotApplet.destroy(). It does 495 * various cleanups to reduce memory usage. 496 */ 497 @Override 498 public void destroy() { 499 clear(true); 500 // Avoid leaking _timerTask; 501 setAutomaticRescale(false); 502 setTimedRepaint(false); 503 504 // Remove the buttons 505 if (_printButton != null) { 506 ActionListener[] listeners = _printButton.getActionListeners(); 507 for (ActionListener listener : listeners) { 508 _printButton.removeActionListener(listener); 509 } 510 _printButton = null; 511 } 512 if (_resetButton != null) { 513 ActionListener[] listeners = _resetButton.getActionListeners(); 514 for (ActionListener listener : listeners) { 515 _resetButton.removeActionListener(listener); 516 } 517 _resetButton = null; 518 } 519 if (_eqAxButton != null) { 520 ActionListener[] listeners = _formatButton.getActionListeners(); 521 for (ActionListener listener : listeners) { 522 _eqAxButton.removeActionListener(listener); 523 } 524 _eqAxButton = null; 525 } 526 if (_formatButton != null) { 527 ActionListener[] listeners = _formatButton.getActionListeners(); 528 for (ActionListener listener : listeners) { 529 _formatButton.removeActionListener(listener); 530 } 531 _formatButton = null; 532 } 533 if (_fillButton != null) { 534 ActionListener[] listeners = _fillButton.getActionListeners(); 535 for (ActionListener listener : listeners) { 536 _fillButton.removeActionListener(listener); 537 } 538 _fillButton = null; 539 } 540 541 removeAll(); 542 } 543 544 /** Export a EPS description of the plot. 545 * If the argument is null, then the description goes 546 * to the clipboard. Otherwise, it goes to the specified file. 547 * To send it to standard output, use 548 * <code>System.out</code> as an argument. 549 * @param out An output stream to which to send the description. 550 */ 551 public synchronized void export(OutputStream out) { 552 try { 553 EPSGraphics g = new EPSGraphics(out, _width, _height); 554 _drawPlot(g, false); 555 g.showpage(); 556 } catch (RuntimeException ex) { 557 String message = "Export failed: " + ex.getMessage(); 558 JOptionPane.showMessageDialog(this, message, "Ptolemy Plot Message", 559 JOptionPane.ERROR_MESSAGE); 560 561 // Rethrow the exception so that we don't report success, 562 // and so the stack trace is displayed on standard out. 563 throw (RuntimeException) ex.fillInStackTrace(); 564 } 565 } 566 567 // CONTRIBUTED CODE. 568 // I wanted the ability to use the Plot object in a servlet and to 569 // write out the resultant images. The following routines, 570 // particularly exportImage(), permit this. I also had to make some 571 // minor changes elsewhere. Rob Kroeger, May 2001. 572 // NOTE: This code has been modified by EAL to conform with Ptolemy II 573 // coding style. 574 575 /** Create a BufferedImage and draw this plot to it. 576 * The size of the returned image matches the current size of the plot. 577 * This method can be used, for 578 * example, by a servlet to produce an image, rather than 579 * requiring an applet to instantiate a PlotBox. 580 * @return An image filled by the plot. 581 */ 582 public synchronized BufferedImage exportImage() { 583 Rectangle rectangle = new Rectangle(_preferredWidth, _preferredHeight); 584 return exportImage( 585 new BufferedImage(rectangle.width, rectangle.height, 586 BufferedImage.TYPE_INT_ARGB), 587 rectangle, _defaultImageRenderingHints(), false); 588 } 589 590 /** Create a BufferedImage the size of the given rectangle and draw 591 * this plot to it at the position specified by the rectangle. 592 * The plot is rendered using anti-aliasing. 593 * @param rectangle The size of the plot. This method can be used, for 594 * example, by a servlet to produce an image, rather than 595 * requiring an applet to instantiate a PlotBox. 596 * @return An image containing the plot. 597 */ 598 public synchronized BufferedImage exportImage(Rectangle rectangle) { 599 return exportImage( 600 new BufferedImage(rectangle.width, rectangle.height, 601 BufferedImage.TYPE_INT_ARGB), 602 rectangle, _defaultImageRenderingHints(), false); 603 } 604 605 /** Draw this plot onto the specified image at the position of the 606 * specified rectangle with the size of the specified rectangle. 607 * The plot is rendered using anti-aliasing. 608 * This can be used to paint a number of different 609 * plots onto a single buffered image. This method can be used, for 610 * example, by a servlet to produce an image, rather than 611 * requiring an applet to instantiate a PlotBox. 612 * @param bufferedImage Image onto which the plot is drawn. 613 * @param rectangle The size and position of the plot in the image. 614 * @param hints Rendering hints for this plot. 615 * @param transparent Indicator that the background of the plot 616 * should not be painted. 617 * @return The modified bufferedImage. 618 */ 619 public synchronized BufferedImage exportImage(BufferedImage bufferedImage, 620 Rectangle rectangle, RenderingHints hints, boolean transparent) { 621 Graphics2D graphics = bufferedImage.createGraphics(); 622 graphics.addRenderingHints(_defaultImageRenderingHints()); 623 624 if (!transparent) { 625 graphics.setColor(Color.white); // set the background color 626 graphics.fill(rectangle); 627 } 628 629 _drawPlot(graphics, false, rectangle); 630 return bufferedImage; 631 } 632 633 /** Draw this plot onto the provided image. 634 * This method does not paint the background, so the plot is 635 * transparent. The plot fills the image, and is rendered 636 * using anti-aliasing. This method can be used to overlay 637 * multiple plots on the same image, although you must use care 638 * to ensure that the axes and other labels are identical. 639 * Hence, it is usually better to simply combine data sets into 640 * a single plot. 641 * @param bufferedImage The image onto which to render the plot. 642 * @return The modified bufferedImage. 643 */ 644 public synchronized BufferedImage exportImage(BufferedImage bufferedImage) { 645 return exportImage(bufferedImage, 646 new Rectangle(bufferedImage.getWidth(), 647 bufferedImage.getHeight()), 648 _defaultImageRenderingHints(), true); 649 } 650 651 /** Export a Latex description of the plot. 652 * @param directory An directory to which to export the Latex. 653 */ 654 public synchronized void exportLatex(File directory) { 655 try { 656 if (!directory.isDirectory()) { 657 if (!directory.mkdir()) { 658 throw new RuntimeException("Failed to create " + directory); 659 } 660 } 661 // Copy the required latex files. 662 // Not currently using anything from pst-sigsys. 663 // _copyFiles("pst-sigsys.sty", directory); 664 // _copyFiles("pst-sigsys.tex", directory); 665 666 // Create a makefile 667 String makefileContents = "# Makefile for Latex files generated by the Ptolemy II plotter.\n" 668 + "# This makes several assumptions:\n" + 669 // "# - The current directory contains pst-sigsys.tex and pst-sigsys.sty\n" + 670 "# - latex, dvips, ps2pdf, and open are all in the path\n" 671 + "# - pstricks is installed on the local latex.\n" 672 + "#\n" + "FILENAME=" + directory.getName() + "\n" 673 + "all: $(FILENAME).tex\n" + "\tlatex $(FILENAME);\n" 674 + "\tdvips $(FILENAME);\n" + "\tps2pdf $(FILENAME).ps;\n" 675 + "\topen $(FILENAME).pdf\n"; 676 File makefile = new File(directory, "makefile"); 677 PrintStream stream = new PrintStream(makefile); 678 stream.print(makefileContents); 679 stream.close(); 680 681 // Now write the latex file. 682 File latexFile = new File(directory, directory.getName() + ".tex"); 683 PrintStream out = new PrintStream(latexFile); 684 out.println("% Plot output generated by ptplot."); 685 // FIXME: probably is a better documentclass. 686 out.println("\\documentclass[12pt]{article}"); 687 out.println("\\usepackage{pstricks}"); 688 // Not currently using anything from pst-sigsys. 689 // out.println("\\usepackage{pst-sigsys}"); 690 out.println("\\begin{document}"); 691 out.println("\\thispagestyle{empty}"); 692 // FIXME: The following fixes the width at 6 in 693 // and the height at 4in. Should instead get these 694 // from the window size. 695 double xScale = 6.0 / (_xMax - _xMin); 696 double yScale = 4.0 / (_yMax - _yMin); 697 // FIXME: The following should be calculated to 698 // position the plot somewhere reasonable. 699 double xOrigin = -3.0; 700 double yOrigin = 0.0; 701 out.println("\\begin{pspicture}[" + "xunit=" + xScale + "in," 702 + "yunit=" + yScale + "in," + "origin={" + xOrigin + "," 703 + yOrigin + "}," + "showgrid=" + getGrid() + "]" + "(" 704 + _xMin + "," + _yMin + ")" + "(" + _xMax + "," + _yMax 705 + ")"); 706 707 out.println(_exportLatexPlotData()); 708 out.println("\\end{pspicture}"); 709 out.println("\\end{document}"); 710 out.close(); 711 712 String message = "Apologies, but export to Latex is not implemented yet."; 713 JOptionPane.showMessageDialog(this, message, "Ptolemy Plot Message", 714 JOptionPane.ERROR_MESSAGE); 715 return; 716 } catch (Throwable throwable) { 717 String message = "Export failed: " + throwable.getMessage(); 718 JOptionPane.showMessageDialog(this, message, "Ptolemy Plot Message", 719 JOptionPane.ERROR_MESSAGE); 720 721 // Rethrow the exception so that we don't report success, 722 // and so the stack trace is displayed on standard out. 723 throw (RuntimeException) throwable.fillInStackTrace(); 724 } 725 } 726 727 /** Export an image of the plot in the specified format. 728 * If the specified format is not supported, then pop up a message 729 * window apologizing. 730 * @param out An output stream to which to send the description. 731 * @param formatName A format name, such as "gif" or "png". 732 */ 733 public synchronized void exportImage(OutputStream out, String formatName) { 734 try { 735 boolean match = false; 736 String[] supportedFormats = ImageIO.getWriterFormatNames(); 737 for (String supportedFormat : supportedFormats) { 738 if (formatName.equalsIgnoreCase(supportedFormat)) { 739 match = true; 740 break; 741 } 742 } 743 if (!match) { 744 // This exception is caught and reported below. 745 throw new Exception("Format " + formatName + " not supported."); 746 } 747 BufferedImage image = exportImage(); 748 if (out == null) { 749 // FIXME: Write image to the clipboard. 750 // final Clipboard clipboard = getToolkit().getSystemClipboard(); 751 String message = "Copy to the clipboard is not implemented yet."; 752 JOptionPane.showMessageDialog(this, message, 753 "Ptolemy Plot Message", JOptionPane.ERROR_MESSAGE); 754 return; 755 } 756 ImageIO.write(image, formatName, out); 757 } catch (Exception ex) { 758 String message = "Export failed: " + ex.getMessage(); 759 JOptionPane.showMessageDialog(this, message, "Ptolemy Plot Message", 760 JOptionPane.ERROR_MESSAGE); 761 762 // Rethrow the exception so that we don't report success, 763 // and so the stack trace is displayed on standard out. 764 throw (RuntimeException) ex.fillInStackTrace(); 765 } 766 } 767 768 /** Rescale so that the data that is currently plotted just fits. 769 * This is done based on the protected variables _xBottom, _xTop, 770 * _yBottom, and _yTop. It is up to derived classes to ensure that 771 * variables are valid. 772 * This method calls repaint(), which eventually causes the display 773 * to be updated. 774 */ 775 @Override 776 public synchronized void fillPlot() { 777 // NOTE: These used to be _setXRange() and _setYRange() to avoid 778 // confusing this with user-specified ranges. But we want to treat 779 // a fill command as a user specified range. 780 // EAL, 6/12/00. 781 setXRange(_xBottom, _xTop); 782 setYRange(_yBottom, _yTop); 783 repaint(); 784 785 // Reacquire the focus so that key bindings work. 786 // NOTE: no longer needed? 787 // requestFocus(); 788 } 789 790 /** Get the captions. 791 * @return the captions 792 * @see #addCaptionLine(String) 793 * @see #setCaptions(Vector) 794 */ 795 @Override 796 public Vector getCaptions() { 797 return _captionStrings; 798 } 799 800 /** Return whether the plot uses color. 801 * @return True if the plot uses color. 802 * @see #setColor(boolean) 803 */ 804 @Override 805 public boolean getColor() { 806 return _usecolor; 807 } 808 809 /** Get the point colors. 810 * @return Array of colors 811 * @see #setColors(Color[]) 812 */ 813 @Override 814 public Color[] getColors() { 815 return _colors; 816 } 817 818 /** Convert a color name into a Color. Currently, only a very limited 819 * set of color names is supported: black, white, red, green, and blue. 820 * @param name A color name, or null if not found. 821 * @return An instance of Color. 822 */ 823 public static Color getColorByName(String name) { 824 try { 825 // Check to see if it is a hexadecimal 826 if (name.startsWith("#")) { 827 name = name.substring(1); 828 } 829 830 Color col = new Color(Integer.parseInt(name, 16)); 831 return col; 832 } catch (NumberFormatException e) { 833 } 834 835 // FIXME: This is a poor excuse for a list of colors and values. 836 // We should use a hash table here. 837 // Note that Color decode() wants the values to start with 0x. 838 String[][] names = { { "black", "00000" }, { "white", "ffffff" }, 839 { "red", "ff0000" }, { "green", "00ff00" }, 840 { "blue", "0000ff" } }; 841 842 for (String[] name2 : names) { 843 if (name.equals(name2[0])) { 844 try { 845 Color col = new Color(Integer.parseInt(name2[1], 16)); 846 return col; 847 } catch (NumberFormatException e) { 848 } 849 } 850 } 851 852 return null; 853 } 854 855 /** Get the file specification that was given by setDataurl(). 856 * @return the file specification 857 * @see #setDataurl(String) 858 * @deprecated Use read() instead. 859 */ 860 @Deprecated 861 @Override 862 public String getDataurl() { 863 return _filespec; 864 } 865 866 /** Get the document base that was set by setDocumentBase(). 867 * @return the document base. 868 * @see #setDocumentBase(URL) 869 * @deprecated Use read() instead. 870 */ 871 @Deprecated 872 @Override 873 public URL getDocumentBase() { 874 return _documentBase; 875 } 876 877 /** Return whether the grid is drawn. 878 * @return True if a grid is drawn. 879 * @see #setGrid(boolean) 880 */ 881 @Override 882 public boolean getGrid() { 883 return _grid; 884 } 885 886 /** Get the legend for a dataset, or null if there is none. 887 * The legend would have been set by addLegend(). 888 * @param dataset The dataset index. 889 * @return The legend label, or null if there is none. 890 */ 891 @Override 892 public synchronized String getLegend(int dataset) { 893 int idx = _legendDatasets.indexOf(Integer.valueOf(dataset), 0); 894 895 if (idx != -1) { 896 return (String) _legendStrings.elementAt(idx); 897 } else { 898 return null; 899 } 900 } 901 902 /** Given a legend string, return the corresponding dataset or -1 if no 903 * legend was added with that legend string 904 * The legend would have been set by addLegend(). 905 * @param legend The String naming the legend 906 * @return The legend dataset, or -1 if not found. 907 * @since Ptplot 5.2p1 908 */ 909 @Override 910 public synchronized int getLegendDataset(String legend) { 911 int index = _legendStrings.indexOf(legend); 912 913 if (index == -1) { 914 return -1; 915 } 916 917 return ((Integer) _legendDatasets.get(index)).intValue(); 918 } 919 920 /** If the size of the plot has been set by setSize(), 921 * then return that size. Otherwise, return what the superclass 922 * returns (which is undocumented, but apparently imposes no maximum size). 923 * Currently (JDK 1.3), only BoxLayout pays any attention to this. 924 * @return The maximum desired size. 925 */ 926 927 // public synchronized Dimension getMaximumSize() { 928 // if (_sizeHasBeenSet) { 929 // return new Dimension(_preferredWidth, _preferredHeight); 930 // } else { 931 // return super.getMaximumSize(); 932 // } 933 // } 934 /** Get the minimum size of this component. 935 * This is simply the dimensions specified by setSize(), 936 * if this has been called. Otherwise, return whatever the base 937 * class returns, which is undocumented. 938 * @return The minimum size. 939 */ 940 941 // public synchronized Dimension getMinimumSize() { 942 // if (_sizeHasBeenSet) { 943 // return new Dimension(_preferredWidth, _preferredHeight); 944 // } else { 945 // return super.getMinimumSize(); 946 // } 947 // } 948 /** Get the current plot rectangle. 949 * Note that Rectangle returned by this method is calculated 950 * from the values of {@link #_ulx}, {@link #_uly}, 951 * {@link #_lrx} and {@link #_lry}. The value passed in by 952 * setPlotRectangle() is not directly used, thus calling 953 * getPlotRectangle() may not return the same rectangle that 954 * was passed in with setPlotRectangle(). 955 * @return Rectangle 956 * @see #setPlotRectangle(Rectangle) 957 */ 958 @Override 959 public Rectangle getPlotRectangle() { 960 return new Rectangle(_ulx, _uly, _lrx - _ulx, _lry - _uly); 961 } 962 963 /** Get the preferred size of this component. 964 * This is simply the dimensions specified by setSize(), 965 * if this has been called, or the default width and height 966 * otherwise (500 by 300). 967 * @return The preferred size. 968 */ 969 @Override 970 public synchronized Dimension getPreferredSize() { 971 return new Dimension(_preferredWidth, _preferredHeight); 972 } 973 974 /** Get the title of the graph, or an empty string if there is none. 975 * @return The title. 976 * @see #setTitle(String) 977 */ 978 @Override 979 public synchronized String getTitle() { 980 if (_title == null) { 981 return ""; 982 } 983 984 return _title; 985 } 986 987 /** Get the range for X values of the data points registered so far. 988 * Usually, derived classes handle managing the range by checking 989 * each new point against the current range. 990 * @return An array of two doubles where the first element is the 991 * minimum and the second element is the maximum. 992 * @see #getXRange() 993 */ 994 @Override 995 public synchronized double[] getXAutoRange() { 996 double[] result = new double[2]; 997 result[0] = _xBottom; 998 result[1] = _xTop; 999 return result; 1000 } 1001 1002 /** Get the label for the X (horizontal) axis, or null if none has 1003 * been set. 1004 * @return The X label. 1005 * @see #setXLabel(String) 1006 */ 1007 @Override 1008 public synchronized String getXLabel() { 1009 return _xlabel; 1010 } 1011 1012 /** Return whether the X axis is drawn with a logarithmic scale. 1013 * @return True if the X axis is logarithmic. 1014 * @see #setXLog(boolean) 1015 */ 1016 @Override 1017 public boolean getXLog() { 1018 return _xlog; 1019 } 1020 1021 /** Get the X range. If {@link #setXRange(double, double)} has been 1022 * called, then this method returns the values passed in as 1023 * arguments to setXRange(double, double). If setXRange(double, 1024 * double) has not been called, then this method returns the 1025 * range of the data to be plotted, which might not be all of the 1026 * data due to zooming. 1027 * @return An array of two doubles where the first element is the 1028 * minimum and the second element is the maximum. 1029 * @see #getXAutoRange() 1030 * @see #setXRange(double, double) 1031 */ 1032 @Override 1033 public synchronized double[] getXRange() { 1034 double[] result = new double[2]; 1035 1036 if (_xRangeGiven) { 1037 result[0] = _xlowgiven; 1038 result[1] = _xhighgiven; 1039 } else { 1040 // Have to first correct for the padding. 1041 result[0] = _xMin + (_xMax - _xMin) * _padding; 1042 result[1] = _xMax - (_xMax - _xMin) * _padding; 1043 ; 1044 } 1045 1046 return result; 1047 } 1048 1049 /** Get the X ticks that have been specified, or null if none. 1050 * The return value is an array with two vectors, the first of 1051 * which specifies the X tick locations (as instances of Double), 1052 * and the second of which specifies the corresponding labels. 1053 * @return The X ticks. 1054 */ 1055 @Override 1056 public synchronized Vector[] getXTicks() { 1057 if (_xticks == null) { 1058 return null; 1059 } 1060 1061 Vector[] result = new Vector[2]; 1062 result[0] = _xticks; 1063 result[1] = _xticklabels; 1064 return result; 1065 } 1066 1067 /** Get the range for Y values of the data points registered so far. 1068 * Usually, derived classes handle managing the range by checking 1069 * each new point against the range. 1070 * @return An array of two doubles where the first element is the 1071 * minimum and the second element is the maximum. 1072 * @see #getYRange() 1073 */ 1074 @Override 1075 public synchronized double[] getYAutoRange() { 1076 double[] result = new double[2]; 1077 result[0] = _yBottom; 1078 result[1] = _yTop; 1079 return result; 1080 } 1081 1082 /** Get the label for the Y (vertical) axis, or null if none has 1083 * been set. 1084 * @return The Y label. 1085 * @see #setYLabel(String) 1086 */ 1087 @Override 1088 public String getYLabel() { 1089 return _ylabel; 1090 } 1091 1092 /** Return whether the Y axis is drawn with a logarithmic scale. 1093 * @return True if the Y axis is logarithmic. 1094 * @see #setYLog(boolean) 1095 */ 1096 @Override 1097 public boolean getYLog() { 1098 return _ylog; 1099 } 1100 1101 /** Get the Y range. If {@link #setYRange(double, double)} has been 1102 * called, then this method returns the values passed in as 1103 * arguments to setYRange(double, double). If setYRange(double, 1104 * double) has not been called, then this method returns the 1105 * range of the data to be plotted, which might not be all of the 1106 * data due to zooming. 1107 * @return An array of two doubles where the first element is the 1108 * minimum and the second element is the maximum. 1109 * @see #getYAutoRange() 1110 * @see #setYRange(double, double) 1111 */ 1112 @Override 1113 public synchronized double[] getYRange() { 1114 double[] result = new double[2]; 1115 1116 if (_yRangeGiven) { 1117 result[0] = _ylowgiven; 1118 result[1] = _yhighgiven; 1119 } else { 1120 // Have to first correct for the padding. 1121 result[0] = _yMin + (_yMax - _yMin) * _padding; 1122 result[1] = _yMax - (_yMax - _yMin) * _padding; 1123 ; 1124 } 1125 1126 return result; 1127 } 1128 1129 /** Get the Y ticks that have been specified, or null if none. 1130 * The return value is an array with two vectors, the first of 1131 * which specifies the Y tick locations (as instances of Double), 1132 * and the second of which specifies the corresponding labels. 1133 * @return The Y ticks. 1134 */ 1135 @Override 1136 public synchronized Vector[] getYTicks() { 1137 if (_yticks == null) { 1138 return null; 1139 } 1140 1141 Vector[] result = new Vector[2]; 1142 result[0] = _yticks; 1143 result[1] = _yticklabels; 1144 return result; 1145 } 1146 1147 /** Initialize the component, creating the fill button and parsing 1148 * an input file, if one has been specified. This is deprecated. 1149 * Call setButtons() and read() instead. 1150 * @deprecated 1151 */ 1152 @Deprecated 1153 @Override 1154 public void init() { 1155 setButtons(true); 1156 1157 if (_filespec != null) { 1158 parseFile(_filespec, _documentBase); 1159 } 1160 } 1161 1162 /** Paint the component contents, which in this base class is 1163 * only the axes. 1164 * @param graphics The graphics context. 1165 */ 1166 @Override 1167 public synchronized void paintComponent(Graphics graphics) { 1168 // super.paintComponent(graphics); 1169 // _drawPlot(graphics, true); 1170 BufferedImage newPlotImage = _plotImage; 1171 1172 if (newPlotImage == null) { 1173 Rectangle bounds = getBounds(); 1174 newPlotImage = new BufferedImage(bounds.width, bounds.height, 1175 BufferedImage.TYPE_3BYTE_BGR); 1176 _plotImage = newPlotImage; 1177 1178 Graphics2D offScreenGraphics = newPlotImage.createGraphics(); 1179 offScreenGraphics.addRenderingHints(_defaultImageRenderingHints()); 1180 super.paintComponent(offScreenGraphics); 1181 _drawPlot(offScreenGraphics, true); 1182 } 1183 1184 // Blit the offscreen image onto the screen. 1185 graphics.drawImage(newPlotImage, 0, 0, null); 1186 1187 // Acquire the focus so that key bindings work. 1188 // NOTE: no longer needed? 1189 // requestFocus(); 1190 } 1191 1192 /** Syntactic sugar for parseFile(filespec, documentBase). 1193 * @param filespec The file to be read. 1194 * @deprecated Use read() to read the old file 1195 * format, or use one of the classes in the plotml package to 1196 * read the XML-based file format. 1197 */ 1198 @Deprecated 1199 @Override 1200 public void parseFile(String filespec) { 1201 parseFile(filespec, (URL) null); 1202 } 1203 1204 /** Open up the input file, which could be stdin, a URL, or a file. 1205 * @param filespec The file to be read. 1206 * @param documentBase The base of the URL 1207 * @deprecated Use read() instead. 1208 */ 1209 @Deprecated 1210 @Override 1211 public synchronized void parseFile(String filespec, URL documentBase) { 1212 DataInputStream in = null; 1213 1214 if (filespec == null || filespec.length() == 0) { 1215 // Open up stdin 1216 in = new DataInputStream(System.in); 1217 } else { 1218 try { 1219 URL url = null; 1220 1221 if (documentBase == null && _documentBase != null) { 1222 documentBase = _documentBase; 1223 } 1224 1225 if (documentBase == null) { 1226 url = new URL(filespec); 1227 } else { 1228 try { 1229 url = new URL(documentBase, filespec); 1230 } catch (NullPointerException e) { 1231 // If we got a NullPointerException, then perhaps we 1232 // are calling this as an application, not as an applet 1233 url = new URL(filespec); 1234 } 1235 } 1236 1237 in = new DataInputStream(url.openStream()); 1238 } catch (MalformedURLException e) { 1239 try { 1240 // Just try to open it as a file. 1241 in = new DataInputStream(new FileInputStream(filespec)); 1242 } catch (FileNotFoundException me) { 1243 _errorMsg = new String[2]; 1244 _errorMsg[0] = "File not found: " + filespec; 1245 _errorMsg[1] = me.getMessage(); 1246 return; 1247 } catch (SecurityException me) { 1248 _errorMsg = new String[2]; 1249 _errorMsg[0] = "Security Exception: " + filespec; 1250 _errorMsg[1] = me.getMessage(); 1251 return; 1252 } 1253 } catch (IOException ioe) { 1254 _errorMsg = new String[3]; 1255 _errorMsg[0] = "Failure opening URL: "; 1256 _errorMsg[1] = " " + filespec; 1257 _errorMsg[2] = ioe.getMessage(); 1258 return; 1259 } 1260 } 1261 1262 // At this point, we've opened the data source, now read it in 1263 try { 1264 BufferedReader din = new BufferedReader(new InputStreamReader(in)); 1265 String line = din.readLine(); 1266 1267 while (line != null) { 1268 _parseLine(line); 1269 line = din.readLine(); 1270 } 1271 } catch (MalformedURLException e) { 1272 _errorMsg = new String[2]; 1273 _errorMsg[0] = "Malformed URL: " + filespec; 1274 _errorMsg[1] = e.getMessage(); 1275 return; 1276 } catch (IOException e) { 1277 _errorMsg = new String[2]; 1278 _errorMsg[0] = "Failure reading data: " + filespec; 1279 _errorMsg[1] = e.getMessage(); 1280 _errorMsg[1] = e.getMessage(); 1281 } finally { 1282 try { 1283 in.close(); 1284 } catch (IOException me) { 1285 } 1286 } 1287 } 1288 1289 /** Print the plot to a printer, represented by the specified graphics 1290 * object. 1291 * @param graphics The context into which the page is drawn. 1292 * @param format The size and orientation of the page being drawn. 1293 * @param index The zero based index of the page to be drawn. 1294 * @return PAGE_EXISTS if the page is rendered successfully, or 1295 * NO_SUCH_PAGE if pageIndex specifies a non-existent page. 1296 * @exception PrinterException If the print job is terminated. 1297 */ 1298 @Override 1299 public synchronized int print(Graphics graphics, PageFormat format, 1300 int index) throws PrinterException { 1301 1302 if (graphics == null) { 1303 return Printable.NO_SUCH_PAGE; 1304 } 1305 1306 // We only print on one page. 1307 if (index >= 1) { 1308 return Printable.NO_SUCH_PAGE; 1309 } 1310 1311 Graphics2D graphics2D = (Graphics2D) graphics; 1312 1313 // Scale the printout to fit the pages. 1314 // Contributed by Laurent ETUR, Schlumberger Riboud Product Center 1315 double scalex = format.getImageableWidth() / getWidth(); 1316 double scaley = format.getImageableHeight() / getHeight(); 1317 double scale = Math.min(scalex, scaley); 1318 graphics2D.translate((int) format.getImageableX(), 1319 (int) format.getImageableY()); 1320 graphics2D.scale(scale, scale); 1321 _drawPlot(graphics, true); 1322 return Printable.PAGE_EXISTS; 1323 } 1324 1325 /** Read commands and/or plot data from an input stream in the old 1326 * (non-XML) file syntax. 1327 * To update the display, call repaint(), or make the plot visible with 1328 * setVisible(true). 1329 * <p> 1330 * To read from standard input, use: 1331 * <pre> 1332 * read(System.in); 1333 * </pre> 1334 * To read from a url, use: 1335 * <pre> 1336 * read(url.openStream()); 1337 * </pre> 1338 * To read a URL from within an applet, use: 1339 * <pre> 1340 * URL url = new URL(getDocumentBase(), urlSpec); 1341 * read(url.openStream()); 1342 * </pre> 1343 * Within an application, if you have an absolute URL, use: 1344 * <pre> 1345 * URL url = new URL(urlSpec); 1346 * read(url.openStream()); 1347 * </pre> 1348 * To read from a file, use: 1349 * <pre> 1350 * read(new FileInputStream(filename)); 1351 * </pre> 1352 * @param in The input stream. 1353 * @exception IOException If the stream cannot be read. 1354 */ 1355 @Override 1356 public synchronized void read(InputStream in) throws IOException { 1357 try { 1358 // NOTE: I tried to use exclusively the jdk 1.1 Reader classes, 1359 // but they provide no support like DataInputStream, nor 1360 // support for URL accesses. So I use the older classes 1361 // here in a strange mixture. 1362 BufferedReader din = new BufferedReader(new InputStreamReader(in)); 1363 1364 try { 1365 String line = din.readLine(); 1366 1367 while (line != null) { 1368 _parseLine(line); 1369 line = din.readLine(); 1370 } 1371 } finally { 1372 din.close(); 1373 } 1374 } catch (IOException e) { 1375 _errorMsg = new String[2]; 1376 _errorMsg[0] = "Failure reading input data."; 1377 _errorMsg[1] = e.getMessage(); 1378 throw e; 1379 } 1380 } 1381 1382 /** Read a single line command provided as a string. 1383 * The commands can be any of those in the ASCII file format. 1384 * @param command A command. 1385 */ 1386 @Override 1387 public synchronized void read(String command) { 1388 _parseLine(command); 1389 } 1390 1391 /** Remove the legend (displayed at the upper right) for the specified 1392 * data set. If the dataset is not found, nothing will occur. 1393 * The PlotBox must be repainted in order for this to take effect. 1394 * @param dataset The dataset index. 1395 */ 1396 @Override 1397 public synchronized void removeLegend(int dataset) { 1398 final int len = _legendDatasets.size(); 1399 int foundIndex = -1; 1400 boolean found = false; 1401 1402 for (int i = 0; i < len && !found; ++i) { 1403 if (((Integer) _legendDatasets.get(i)).intValue() == dataset) { 1404 foundIndex = i; 1405 found = true; 1406 } 1407 } 1408 1409 if (found) { 1410 _legendDatasets.remove(foundIndex); 1411 _legendStrings.remove(foundIndex); 1412 } 1413 } 1414 1415 /** Rename a legend. 1416 * @param dataset The dataset of the legend to be renamed. 1417 * If there is no dataset with this value, then nothing happens. 1418 * @param newName The new name of legend. 1419 * @see #addLegend(int, String) 1420 */ 1421 @Override 1422 public synchronized void renameLegend(int dataset, String newName) { 1423 int index = _legendDatasets.indexOf(Integer.valueOf(dataset), 0); 1424 1425 if (index != -1) { 1426 _legendStrings.setElementAt(newName, index); 1427 1428 // Changing legend means we need to repaint the offscreen buffer. 1429 _plotImage = null; 1430 } 1431 } 1432 1433 /** Reset the X and Y axes to the ranges that were first specified 1434 * using setXRange() and setYRange(). If these methods have not been 1435 * called, then reset to the default ranges. 1436 * This method calls repaint(), which eventually causes the display 1437 * to be updated. 1438 */ 1439 @Override 1440 public synchronized void resetAxes() { 1441 setXRange(_originalXlow, _originalXhigh); 1442 setYRange(_originalYlow, _originalYhigh); 1443 repaint(); 1444 } 1445 1446 /** Do nothing in this base class. Derived classes might want to override 1447 * this class to give an example of their use. 1448 */ 1449 @Override 1450 public void samplePlot() { 1451 // Empty default implementation. 1452 } 1453 1454 /** 1455 * Set automatic rescale. Automatic rescaling is enabled 1456 * when automaticRescale equals true and disabled when 1457 * automaticRescale equals false. 1458 * @param automaticRescale The boolean that specifies whether 1459 * plots should be automatic rescaled. 1460 */ 1461 @Override 1462 public void setAutomaticRescale(boolean automaticRescale) { 1463 _automaticRescale = automaticRescale; 1464 if (automaticRescale) { 1465 if (_timerTask == null) { 1466 _timerTask = new TimedRepaint(); 1467 } 1468 _timerTask.addListener(this); 1469 } else if (!_timedRepaint) { 1470 _resetScheduledTasks(); 1471 if (_timerTask != null) { 1472 _timerTask.removeListener(this); 1473 _timerTask = null; 1474 } 1475 } 1476 } 1477 1478 /** Set the background color. 1479 * @param background The background color. 1480 */ 1481 @Override 1482 public synchronized void setBackground(Color background) { 1483 // Changing legend means we need to repaint the offscreen buffer. 1484 _plotImage = null; 1485 1486 _background = background; 1487 super.setBackground(_background); 1488 } 1489 1490 /** Set the background color. 1491 * This is syntactic sugar for use in implementations that separate 1492 * the graphical display from the computational engine. 1493 * Implements the {@link PlotBoxInterface}. 1494 * @param background The background Color. 1495 */ 1496 @Override 1497 public void setBackground(Object background) { 1498 setBackground((Color) background); 1499 } 1500 1501 /** Move and resize this component. The new location of the top-left 1502 * corner is specified by x and y, and the new size is specified by 1503 * width and height. This overrides the base class method to make 1504 * a record of the new size. 1505 * @param x The new x-coordinate of this component. 1506 * @param y The new y-coordinate of this component. 1507 * @param width The new width of this component. 1508 * @param height The new height of this component. 1509 */ 1510 @Override 1511 public synchronized void setBounds(int x, int y, int width, int height) { 1512 _width = width; 1513 _height = height; 1514 1515 // Resizing the component means we need to redraw the buffer. 1516 _plotImage = null; 1517 1518 super.setBounds(x, y, _width, _height); 1519 } 1520 1521 /** If the argument is true, make a fill button visible at the upper 1522 * right. This button auto-scales the plot. 1523 * NOTE: The button may infringe on the title space, 1524 * if the title is long. In an application, it is preferable to provide 1525 * a menu with the fill command. This way, when printing the plot, 1526 * the printed plot will not have a spurious button. Thus, this method 1527 * should be used only by applets, which normally do not have menus. 1528 * This method should only be called from within the event dispatch 1529 * thread, since it interacts with swing. 1530 * @param visible If true, make the fill button appear. 1531 * @see #destroy() 1532 */ 1533 @Override 1534 public synchronized void setButtons(boolean visible) { 1535 // Changing legend means we need to repaint the offscreen buffer. 1536 _plotImage = null; 1537 1538 // Netbeans wants this, otherwise the icons won't appear. 1539 ClassLoader classLoader = getClass().getClassLoader(); 1540 if (_printButton == null) { 1541 // Load the image by using the absolute path to the gif. 1542 URL img = null; 1543 try { 1544 // FindBugs: Usage of GetResource may be unsafe if 1545 // class is extended 1546 img = FileUtilities.nameToURL( 1547 "$CLASSPATH/ptolemy/plot/img/print.gif", null, 1548 classLoader); 1549 } catch (IOException ex) { 1550 ex.printStackTrace(); 1551 } 1552 1553 if (img != null) { 1554 ImageIcon printIcon = new ImageIcon(img); 1555 _printButton = new JButton(printIcon); 1556 _printButton.setBorderPainted(false); 1557 } else { 1558 // Backup in case something goes wrong with the 1559 // class loader. 1560 _printButton = new JButton("P"); 1561 } 1562 1563 // FIXME: If we failed to get an image, then the letter "P" 1564 // Is not likely to fit into a 20x20 button. 1565 _printButton.setPreferredSize(new Dimension(20, 20)); 1566 _printButton.setToolTipText("Print the plot."); 1567 _printButton.addActionListener(new ButtonListener()); 1568 add(_printButton); 1569 } 1570 1571 _printButton.setVisible(visible); 1572 1573 if (_resetButton == null) { 1574 // Load the image by using the absolute path to the gif. 1575 URL img = null; 1576 try { 1577 // FindBugs: Usage of GetResource may be unsafe if 1578 // class is extended 1579 img = FileUtilities.nameToURL( 1580 "$CLASSPATH/ptolemy/plot/img/reset.gif", null, 1581 classLoader); 1582 } catch (IOException ex) { 1583 ex.printStackTrace(); 1584 } 1585 if (img != null) { 1586 ImageIcon resetIcon = new ImageIcon(img); 1587 _resetButton = new JButton(resetIcon); 1588 _resetButton.setBorderPainted(false); 1589 } else { 1590 // Backup in case something goes wrong with the 1591 // class loader. 1592 _resetButton = new JButton("R"); 1593 } 1594 1595 // FIXME: If we failed to get an image, then the letter "R" 1596 // Is not likely to fit into a 20x20 button. 1597 _resetButton.setPreferredSize(new Dimension(20, 20)); 1598 _resetButton.setToolTipText( 1599 "Reset X and Y ranges to their original values"); 1600 _resetButton.addActionListener(new ButtonListener()); 1601 add(_resetButton); 1602 } 1603 1604 _resetButton.setVisible(visible); 1605 1606 if (_formatButton == null) { 1607 // Load the image by using the absolute path to the gif. 1608 URL img = null; 1609 try { 1610 // FindBugs: Usage of GetResource may be unsafe if 1611 // class is extended 1612 img = FileUtilities.nameToURL( 1613 "$CLASSPATH/ptolemy/plot/img/format.gif", null, 1614 classLoader); 1615 } catch (IOException ex) { 1616 ex.printStackTrace(); 1617 } 1618 if (img != null) { 1619 ImageIcon formatIcon = new ImageIcon(img); 1620 _formatButton = new JButton(formatIcon); 1621 _formatButton.setBorderPainted(false); 1622 } else { 1623 // Backup in case something goes wrong with the 1624 // class loader. 1625 _formatButton = new JButton("S"); 1626 } 1627 1628 // FIXME: If we failed to get an image, then the letter "S" 1629 // Is not likely to fit into a 20x20 button. 1630 _formatButton.setPreferredSize(new Dimension(20, 20)); 1631 _formatButton.setToolTipText("Set the plot format"); 1632 _formatButton.addActionListener(new ButtonListener()); 1633 add(_formatButton); 1634 } 1635 1636 _formatButton.setVisible(visible); 1637 1638 if (_fillButton == null) { 1639 // Load the image by using the absolute path to the gif. 1640 URL img = null; 1641 try { 1642 // FindBugs: Usage of GetResource may be unsafe if 1643 // class is extended 1644 img = FileUtilities.nameToURL( 1645 "$CLASSPATH/ptolemy/plot/img/fill.gif", null, 1646 classLoader); 1647 } catch (IOException ex) { 1648 ex.printStackTrace(); 1649 } 1650 if (img != null) { 1651 ImageIcon fillIcon = new ImageIcon(img); 1652 _fillButton = new JButton(fillIcon); 1653 _fillButton.setBorderPainted(false); 1654 } else { 1655 // Backup in case something goes wrong with the 1656 // class loader. 1657 _fillButton = new JButton("F"); 1658 } 1659 1660 // FIXME: If we failed to get an image, then the letter "F" 1661 // Is not likely to fit into a 20x20 button. 1662 _fillButton.setPreferredSize(new Dimension(20, 20)); 1663 _fillButton.setToolTipText("Rescale the plot to fit the data"); 1664 _fillButton.addActionListener(new ButtonListener()); 1665 add(_fillButton); 1666 } 1667 1668 _fillButton.setVisible(visible); 1669 1670 // Dirk: a button for equal axis plotting (similar to MATLAB "axis equal") 1671 if (_eqAxButton == null) { 1672 // Load the image by using the absolute path to the gif. 1673 URL img = null; 1674 try { 1675 // FindBugs: Usage of GetResource may be unsafe if 1676 // class is extended 1677 img = FileUtilities.nameToURL( 1678 "$CLASSPATH/ptolemy/plot/img/equal.gif", null, null); 1679 } catch (IOException ex) { 1680 ex.printStackTrace(); 1681 } 1682 if (img != null) { 1683 ImageIcon fillIcon = new ImageIcon(img); 1684 _eqAxButton = new JButton(fillIcon); 1685 _eqAxButton.setBorderPainted(false); 1686 } else { 1687 // Backup in case something goes wrong with the 1688 // class loader. 1689 _eqAxButton = new JButton("E"); 1690 } 1691 _eqAxButton.setPreferredSize(new Dimension(20, 20)); 1692 _eqAxButton.setToolTipText( 1693 "Rescale to equal axis intervals per pixel"); 1694 _eqAxButton.addActionListener(new ButtonListener()); 1695 add(_eqAxButton); 1696 } 1697 _eqAxButton.setVisible(visible); 1698 1699 // Request the focus so that key events are heard. 1700 // NOTE: no longer needed? 1701 // requestFocus(); 1702 } 1703 1704 /** Set the strings of the caption. 1705 * @param captionStrings A Vector where each element contains a String 1706 * that is one line of the caption. 1707 * @see #getCaptions() 1708 * @see #clearCaptions() 1709 */ 1710 @Override 1711 public void setCaptions(Vector captionStrings) { 1712 // Changing caption means we need to repaint the offscreen buffer. 1713 _plotImage = null; 1714 _captionStrings = captionStrings; 1715 } 1716 1717 /** If the argument is false, draw the plot without using color 1718 * (in black and white). Otherwise, draw it in color (the default). 1719 * @param useColor False to draw in back and white. 1720 * @see #getColor() 1721 */ 1722 @Override 1723 public synchronized void setColor(boolean useColor) { 1724 // Changing legend means we need to repaint the offscreen buffer. 1725 _plotImage = null; 1726 1727 _usecolor = useColor; 1728 } 1729 1730 /** Set the point colors. Note that the default colors have been 1731 * carefully selected to maximize readability and that it is easy 1732 * to use colors that result in a very ugly plot. 1733 * @param colors Array of colors to use in succession for data sets. 1734 * @see #getColors() 1735 */ 1736 public synchronized void setColors(Color[] colors) { 1737 // Changing legend means we need to repaint the offscreen buffer. 1738 _plotImage = null; 1739 1740 _colors = colors; 1741 } 1742 1743 /** Set the array of colors 1744 * Implements the {@link PlotBoxInterface}. 1745 * @param colors The array of Colors. 1746 * @see #getColors() 1747 */ 1748 @Override 1749 public void setColors(Object[] colors) { 1750 setColors((Color[]) colors); 1751 } 1752 1753 /** Set the file to read when init() is called. 1754 * @param filespec the file to be read 1755 * @see #getDataurl() 1756 * @deprecated Use read() instead. 1757 */ 1758 @Deprecated 1759 @Override 1760 public void setDataurl(String filespec) { 1761 _filespec = filespec; 1762 } 1763 1764 /** Set the document base to used when init() is called to read a URL. 1765 * @param documentBase The document base to be used. 1766 * @see #getDocumentBase() 1767 * @deprecated Use read() instead. 1768 */ 1769 @Deprecated 1770 @Override 1771 public void setDocumentBase(URL documentBase) { 1772 _documentBase = documentBase; 1773 } 1774 1775 /** Set the foreground color. 1776 * @param foreground The foreground color. 1777 */ 1778 @Override 1779 public synchronized void setForeground(Color foreground) { 1780 // Changing legend means we need to repaint the offscreen buffer. 1781 _plotImage = null; 1782 1783 _foreground = foreground; 1784 super.setForeground(_foreground); 1785 } 1786 1787 /** Set the foreground color. 1788 * Implements the {@link PlotBoxInterface}. 1789 * @param foreground The foreground Color. 1790 */ 1791 @Override 1792 public void setForeground(Object foreground) { 1793 setForeground((Color) foreground); 1794 } 1795 1796 /** Control whether the grid is drawn. 1797 * @param grid If true, a grid is drawn. 1798 * @see #getGrid() 1799 */ 1800 @Override 1801 public synchronized void setGrid(boolean grid) { 1802 // Changing legend means we need to repaint the offscreen buffer. 1803 _plotImage = null; 1804 1805 _grid = grid; 1806 } 1807 1808 /** Set the label font, which is used for axis labels and legend labels. 1809 * The font names understood are those understood by 1810 * java.awt.Font.decode(). 1811 * @param name A font name. 1812 */ 1813 @Override 1814 public synchronized void setLabelFont(String name) { 1815 // Changing legend means we need to repaint the offscreen buffer. 1816 _plotImage = null; 1817 1818 _labelFont = Font.decode(name); 1819 _labelFontMetrics = getFontMetrics(_labelFont); 1820 } 1821 1822 /** Set the plot rectangle inside the axes. This method 1823 * can be used to create two plots that share the same axes. 1824 * @param rectangle Rectangle space inside axes. 1825 * @see #getPlotRectangle() 1826 */ 1827 public synchronized void setPlotRectangle(Rectangle rectangle) { 1828 // Changing legend means we need to repaint the offscreen buffer. 1829 _plotImage = null; 1830 1831 _specifiedPlotRectangle = rectangle; 1832 } 1833 1834 /** Set the plot rectangle. 1835 * Implements the {@link PlotBoxInterface}. 1836 * @param rectangle The Rectangle. 1837 * @see #getPlotRectangle() 1838 */ 1839 @Override 1840 public void setPlotRectangle(Object rectangle) { 1841 setPlotRectangle((Rectangle) rectangle); 1842 } 1843 1844 /** Set the size of the plot. This overrides the base class to make 1845 * it work. In particular, it records the specified size so that 1846 * getMinimumSize() and getPreferredSize() return the specified value. 1847 * However, it only works if the plot is placed in its own JPanel. 1848 * This is because the JPanel asks the contained component for 1849 * its preferred size before determining the size of the panel. 1850 * If the plot is placed directly in the content pane of a JApplet, 1851 * then, mysteriously, this method has no effect. 1852 * @param width The width, in pixels. 1853 * @param height The height, in pixels. 1854 */ 1855 @Override 1856 public synchronized void setSize(int width, int height) { 1857 // Changing legend means we need to repaint the offscreen buffer. 1858 _plotImage = null; 1859 1860 _width = width; 1861 _height = height; 1862 _preferredWidth = width; 1863 _preferredHeight = height; 1864 1865 //_sizeHasBeenSet = true; 1866 super.setSize(width, height); 1867 } 1868 1869 /** 1870 * Set repainting with a certain fixed refresh rate. This timed 1871 * repainting is enabled when timedRepaint equals true and 1872 * disabled when timedRepaint equals false. 1873 * @param timedRepaint The boolean that specifies whether 1874 * repainting should happen with a certain fixed refresh rate. 1875 */ 1876 @Override 1877 public void setTimedRepaint(boolean timedRepaint) { 1878 _timedRepaint = timedRepaint; 1879 if (timedRepaint) { 1880 if (_timerTask == null) { 1881 _timerTask = new TimedRepaint(); 1882 } 1883 _timerTask.addListener(this); 1884 } else if (!_automaticRescale) { 1885 if (_timerTask != null) { 1886 _timerTask.removeListener(this); 1887 _timerTask = null; 1888 } 1889 _resetScheduledTasks(); 1890 } 1891 } 1892 1893 /** Set the title of the graph. 1894 * @param title The title. 1895 * @see #getTitle() 1896 */ 1897 @Override 1898 public synchronized void setTitle(String title) { 1899 // Changing legend means we need to repaint the offscreen buffer. 1900 _plotImage = null; 1901 1902 _title = title; 1903 } 1904 1905 /** Set the title font. 1906 * The font names understood are those understood by 1907 * java.awt.Font.decode(). 1908 * @param name A font name. 1909 */ 1910 @Override 1911 public synchronized void setTitleFont(String name) { 1912 // Changing legend means we need to repaint the offscreen buffer. 1913 _plotImage = null; 1914 1915 _titleFont = Font.decode(name); 1916 _titleFontMetrics = getFontMetrics(_titleFont); 1917 } 1918 1919 /** Specify whether the X axis is wrapped. 1920 * If it is, then X values that are out of range are remapped 1921 * to be in range using modulo arithmetic. The X range is determined 1922 * by the most recent call to setXRange() (or the most recent zoom). 1923 * If the X range has not been set, then use the default X range, 1924 * or if data has been plotted, then the current fill range. 1925 * @param wrap If true, wrapping of the X axis is enabled. 1926 */ 1927 @Override 1928 public synchronized void setWrap(boolean wrap) { 1929 // Changing legend means we need to repaint the offscreen buffer. 1930 _plotImage = null; 1931 1932 _wrap = wrap; 1933 1934 if (!_xRangeGiven) { 1935 if (_xBottom > _xTop) { 1936 // have nothing to go on. 1937 setXRange(0, 0); 1938 } else { 1939 setXRange(_xBottom, _xTop); 1940 } 1941 } 1942 1943 _wrapLow = _xlowgiven; 1944 _wrapHigh = _xhighgiven; 1945 } 1946 1947 /** Set the label for the X (horizontal) axis. 1948 * @param label The label. 1949 * @see #getXLabel() 1950 */ 1951 @Override 1952 public synchronized void setXLabel(String label) { 1953 // Changing legend means we need to repaint the offscreen buffer. 1954 _plotImage = null; 1955 1956 _xlabel = label; 1957 } 1958 1959 /** Specify whether the X axis is drawn with a logarithmic scale. 1960 * If you would like to have the X axis drawn with a 1961 * logarithmic axis, then setXLog(true) should be called before 1962 * adding any data points. 1963 * @param xlog If true, logarithmic axis is used. 1964 * @see #getXLog() 1965 */ 1966 @Override 1967 public synchronized void setXLog(boolean xlog) { 1968 // Changing legend means we need to repaint the offscreen buffer. 1969 _plotImage = null; 1970 1971 _xlog = xlog; 1972 } 1973 1974 /** Set the X (horizontal) range of the plot. If this is not done 1975 * explicitly, then the range is computed automatically from data 1976 * available when the plot is drawn. If min and max 1977 * are identical, then the range is arbitrarily spread by 1. 1978 * <p>Note that if {@link #setXLog(boolean)} has been called, then 1979 * the min and max values should be in log units. 1980 * @param min The left extent of the range. 1981 * @param max The right extent of the range. 1982 * @see #getXRange() 1983 */ 1984 @Override 1985 public synchronized void setXRange(double min, double max) { 1986 // Changing legend means we need to repaint the offscreen buffer. 1987 _plotImage = null; 1988 1989 _xRangeGiven = true; 1990 _xlowgiven = min; 1991 _xhighgiven = max; 1992 _setXRange(min, max); 1993 } 1994 1995 /** Set the label for the Y (vertical) axis. 1996 * @param label The label. 1997 * @see #getYLabel() 1998 */ 1999 @Override 2000 public synchronized void setYLabel(String label) { 2001 // Changing legend means we need to repaint the offscreen buffer. 2002 _plotImage = null; 2003 2004 _ylabel = label; 2005 } 2006 2007 /** Specify whether the Y axis is drawn with a logarithmic scale. 2008 * If you would like to have the Y axis drawn with a 2009 * logarithmic axis, then setYLog(true) should be called before 2010 * adding any data points. 2011 * @param ylog If true, logarithmic axis is used. 2012 * @see #getYLog() 2013 */ 2014 @Override 2015 public synchronized void setYLog(boolean ylog) { 2016 // Changing legend means we need to repaint the offscreen buffer. 2017 _plotImage = null; 2018 2019 _ylog = ylog; 2020 } 2021 2022 /** Set the Y (vertical) range of the plot. If this is not done 2023 * explicitly, then the range is computed automatically from data 2024 * available when the plot is drawn. If min and max are identical, 2025 * then the range is arbitrarily spread by 0.1. 2026 * <p>Note that if {@link #setYLog(boolean)} has been called, then 2027 * the min and max values should be in log units. 2028 * @param min The bottom extent of the range. 2029 * @param max The top extent of the range. 2030 * @see #getYRange() 2031 */ 2032 @Override 2033 public synchronized void setYRange(double min, double max) { 2034 // Changing legend means we need to repaint the offscreen buffer. 2035 _plotImage = null; 2036 2037 _yRangeGiven = true; 2038 _ylowgiven = min; 2039 _yhighgiven = max; 2040 _setYRange(min, max); 2041 } 2042 2043 /** Write the current data and plot configuration to the specified 2044 * stream in PlotML syntax. PlotML is an XML extension for plot 2045 * data. The written information is includes a reference to the 2046 * PlotML dtd on the the Ptolemy website. The output is 2047 * buffered, and is flushed and but not closed before exiting. 2048 * Derived classes should override writeFormat and writeData 2049 * rather than this method. 2050 * @param out An output stream. 2051 */ 2052 @Override 2053 public void write(OutputStream out) { 2054 write(out, null); 2055 } 2056 2057 /** Write the current data and plot configuration to the 2058 * specified stream in PlotML syntax. PlotML is an XML 2059 * scheme for plot data. The URL (relative or absolute) for the DTD is 2060 * given as the second argument. If that argument is null, 2061 * then the PlotML PUBLIC DTD is referenced, resulting in a file 2062 * that can be read by a PlotML parser without any external file 2063 * references, as long as that parser has local access to the DTD. 2064 * The output is buffered, and is flushed but 2065 * not closed before exiting. Derived classes should override 2066 * writeFormat and writeData rather than this method. 2067 * @param out An output stream. 2068 * @param dtd The reference (URL) for the DTD, or null to use the 2069 * PUBLIC DTD. 2070 */ 2071 @Override 2072 public synchronized void write(OutputStream out, String dtd) { 2073 write(new OutputStreamWriter(out, 2074 java.nio.charset.Charset.defaultCharset()), dtd); 2075 } 2076 2077 /** Write the current data and plot configuration to the 2078 * specified stream in PlotML syntax. PlotML is an XML 2079 * scheme for plot data. The URL (relative or absolute) for the DTD is 2080 * given as the second argument. If that argument is null, 2081 * then the PlotML PUBLIC DTD is referenced, resulting in a file 2082 * that can be read by a PlotML parser without any external file 2083 * references, as long as that parser has local access to the DTD. 2084 * The output is buffered, and is flushed before exiting. 2085 * @param out An output writer. 2086 * @param dtd The reference (URL) for the DTD, or null to use the 2087 * PUBLIC DTD. 2088 */ 2089 @Override 2090 public synchronized void write(Writer out, String dtd) { 2091 // Auto-flush is disabled. 2092 PrintWriter output = new PrintWriter(new BufferedWriter(out), false); 2093 2094 if (dtd == null) { 2095 output.println("<?xml version=\"1.0\" standalone=\"yes\"?>"); 2096 output.println( 2097 "<!DOCTYPE plot PUBLIC \"-//UC Berkeley//DTD PlotML 1//EN\""); 2098 output.println( 2099 " \"http://ptolemy.eecs.berkeley.edu/xml/dtd/PlotML_1.dtd\">"); 2100 } else { 2101 output.println("<?xml version=\"1.0\" standalone=\"no\"?>"); 2102 output.println("<!DOCTYPE plot SYSTEM \"" + dtd + "\">"); 2103 } 2104 2105 output.println("<plot>"); 2106 output.println("<!-- Ptolemy plot, version " + PTPLOT_RELEASE 2107 + " , PlotML format. -->"); 2108 writeFormat(output); 2109 writeData(output); 2110 output.println("</plot>"); 2111 output.flush(); 2112 2113 // NOTE: We used to close the stream, but if this is part 2114 // of an exportMoML operation, that is the wrong thing to do. 2115 // if (out != System.out) { 2116 // output.close(); 2117 // } 2118 } 2119 2120 /** Write plot data information to the specified output stream in PlotML. 2121 * In this base class, there is no data to write, so this method 2122 * returns without doing anything. 2123 * @param output A buffered print writer. 2124 */ 2125 @Override 2126 public synchronized void writeData(PrintWriter output) { 2127 } 2128 2129 /** Write plot format information to the specified output stream in PlotML. 2130 * Derived classes should override this method to first call 2131 * the parent class method, then add whatever additional format 2132 * information they wish to add to the stream. 2133 * @param output A buffered print writer. 2134 */ 2135 @Override 2136 public synchronized void writeFormat(PrintWriter output) { 2137 // NOTE: If you modify this, you should change the _DTD variable 2138 // accordingly. 2139 if (_title != null) { 2140 output.println("<title>" + _title + "</title>"); 2141 } 2142 2143 if (_captionStrings != null) { 2144 for (Enumeration captions = _captionStrings.elements(); captions 2145 .hasMoreElements();) { 2146 String captionLine = (String) captions.nextElement(); 2147 output.println("<caption>" + captionLine + "</caption>"); 2148 } 2149 } 2150 2151 if (_xlabel != null) { 2152 output.println("<xLabel>" + _xlabel + "</xLabel>"); 2153 } 2154 2155 if (_ylabel != null) { 2156 output.println("<yLabel>" + _ylabel + "</yLabel>"); 2157 } 2158 2159 if (_xRangeGiven) { 2160 output.println("<xRange min=\"" + _xlowgiven + "\" max=\"" 2161 + _xhighgiven + "\"/>"); 2162 } 2163 2164 if (_yRangeGiven) { 2165 output.println("<yRange min=\"" + _ylowgiven + "\" max=\"" 2166 + _yhighgiven + "\"/>"); 2167 } 2168 2169 if (_xticks != null && _xticks.size() > 0) { 2170 output.println("<xTicks>"); 2171 2172 int last = _xticks.size() - 1; 2173 2174 for (int i = 0; i <= last; i++) { 2175 output.println(" <tick label=\"" 2176 + (String) _xticklabels.elementAt(i) + "\" position=\"" 2177 + _xticks.elementAt(i) + "\"/>"); 2178 } 2179 2180 output.println("</xTicks>"); 2181 } 2182 2183 if (_yticks != null && _yticks.size() > 0) { 2184 output.println("<yTicks>"); 2185 2186 int last = _yticks.size() - 1; 2187 2188 for (int i = 0; i <= last; i++) { 2189 output.println(" <tick label=\"" 2190 + (String) _yticklabels.elementAt(i) + "\" position=\"" 2191 + _yticks.elementAt(i) + "\"/>"); 2192 } 2193 2194 output.println("</yTicks>"); 2195 } 2196 2197 if (_xlog) { 2198 output.println("<xLog/>"); 2199 } 2200 2201 if (_ylog) { 2202 output.println("<yLog/>"); 2203 } 2204 2205 if (!_grid) { 2206 output.println("<noGrid/>"); 2207 } 2208 2209 if (_wrap) { 2210 output.println("<wrap/>"); 2211 } 2212 2213 if (!_usecolor) { 2214 output.println("<noColor/>"); 2215 } 2216 } 2217 2218 /** Write the current data and plot configuration to the 2219 * specified stream in the old PtPlot syntax. 2220 * The output is buffered, and is flushed and 2221 * closed before exiting. Derived classes should override 2222 * _writeOldSyntax() rather than this method. 2223 * @param out An output stream. 2224 * @deprecated 2225 */ 2226 @Deprecated 2227 @Override 2228 public synchronized void writeOldSyntax(OutputStream out) { 2229 // Auto-flush is disabled. 2230 PrintWriter output = new PrintWriter(new BufferedOutputStream(out), 2231 false); 2232 _writeOldSyntax(output); 2233 output.flush(); 2234 2235 // Avoid closing standard out. 2236 if (out != System.out) { 2237 output.close(); 2238 } 2239 } 2240 2241 /** Zoom in or out to the specified rectangle. 2242 * This method calls repaint(). 2243 * @param lowx The low end of the new X range. 2244 * @param lowy The low end of the new Y range. 2245 * @param highx The high end of the new X range. 2246 * @param highy The high end of the new Y range. 2247 */ 2248 @Override 2249 public synchronized void zoom(double lowx, double lowy, double highx, 2250 double highy) { 2251 setXRange(lowx, highx); 2252 setYRange(lowy, highy); 2253 repaint(); 2254 } 2255 2256 /////////////////////////////////////////////////////////////////// 2257 //// public variables //// 2258 2259 // If you change PTPLOT_RELEASE, modify the version numbers in: 2260 // doc/main.htm, doc/changes.htm, doc/install.htm, doc/download/index.htm 2261 /** The version of PtPlot. */ 2262 public static final String PTPLOT_RELEASE = "5.11.devel"; 2263 2264 /////////////////////////////////////////////////////////////////// 2265 //// protected methods //// 2266 2267 /** 2268 * Return whether rescaling of the plot should happen 2269 * automatic. 2270 * @return True when rescaling of the plot should happen 2271 * automatic. 2272 */ 2273 protected boolean _automaticRescale() { 2274 return _automaticRescale; 2275 } 2276 2277 // /** Copy the file with the given name from the $PTII/ptolemy/plot/latex directory 2278 // * to the specified directory. 2279 // * @param filename The name of the file to copy. 2280 // * @param directory The directory into which to copy. 2281 // * @exception FileNotFoundException If the file cannot be found. 2282 // * @exception IOException If the file cannot be copied. 2283 // */ 2284 // private void _copyFiles(String filename, File directory) 2285 // throws FileNotFoundException, IOException { 2286 // URL url = FileUtilities.nameToURL("$CLASSPATH/ptolemy/plot/latex/" + filename, null, null); 2287 // FileUtilities.binaryCopyURLToFile(url, new File(directory, filename)); 2288 // } 2289 2290 /** Draw the axes using the current range, label, and title information. 2291 * If the second argument is true, clear the display before redrawing. 2292 * This method is called by paintComponent(). To cause it to be called 2293 * you would normally call repaint(), which eventually causes 2294 * paintComponent() to be called. 2295 * <p> 2296 * Note that this is synchronized so that points are not added 2297 * by other threads while the drawing is occurring. This method 2298 * should be called only from the event dispatch thread, consistent 2299 * with swing policy. 2300 * @param graphics The graphics context. 2301 * @param clearfirst If true, clear the plot before proceeding. 2302 */ 2303 protected synchronized void _drawPlot(Graphics graphics, 2304 boolean clearfirst) { 2305 Rectangle bounds = getBounds(); 2306 _drawPlot(graphics, clearfirst, bounds); 2307 } 2308 2309 /** Draw the axes using the current range, label, and title information, 2310 * at the size of the specified rectangle. 2311 * If the second argument is true, clear the display before redrawing. 2312 * This method is called by paintComponent(). To cause it to be called 2313 * you would normally call repaint(), which eventually causes 2314 * paintComponent() to be called. 2315 * <p> 2316 * Note that this is synchronized so that points are not added 2317 * by other threads while the drawing is occurring. This method 2318 * should be called only from the event dispatch thread, consistent 2319 * with swing policy. 2320 * @param graphics The graphics context. 2321 * @param clearfirst If true, clear the plot before proceeding. 2322 * @param drawRect A specification of the size. 2323 */ 2324 protected synchronized void _drawPlot(Graphics graphics, boolean clearfirst, 2325 Rectangle drawRect) { 2326 // Ignore if there is no graphics object to draw on. 2327 if (graphics == null) { 2328 return; 2329 } 2330 2331 graphics.setPaintMode(); 2332 2333 /* NOTE: The following seems to be unnecessary with Swing... 2334 if (clearfirst) { 2335 // NOTE: calling clearRect() here permits the background 2336 // color to show through, but it messes up printing. 2337 // Printing results in black-on-black title and axis labels. 2338 graphics.setColor(_background); 2339 graphics.drawRect(0, 0, drawRect.width, drawRect.height); 2340 graphics.setColor(Color.black); 2341 } 2342 */ 2343 2344 // If an error message has been set, display it and return. 2345 if (_errorMsg != null) { 2346 int fheight = _labelFontMetrics.getHeight() + 2; 2347 int msgy = fheight; 2348 graphics.setColor(Color.black); 2349 2350 for (String element : _errorMsg) { 2351 graphics.drawString(element, 10, msgy); 2352 msgy += fheight; 2353 System.err.println(element); 2354 } 2355 2356 return; 2357 } 2358 2359 // Make sure we have an x and y range 2360 if (!_xRangeGiven) { 2361 if (_xBottom > _xTop) { 2362 // have nothing to go on. 2363 _setXRange(0, 0); 2364 } else { 2365 _setXRange(_xBottom, _xTop); 2366 } 2367 } 2368 2369 if (!_yRangeGiven) { 2370 if (_yBottom > _yTop) { 2371 // have nothing to go on. 2372 _setYRange(0, 0); 2373 } else { 2374 _setYRange(_yBottom, _yTop); 2375 } 2376 } 2377 2378 // If user specified a plot rectangle, compute 2379 // a working plot rectangle which lies inside the 2380 // drawRect at the user specified coordinates 2381 Rectangle workingPlotRectangle = null; 2382 2383 if (_specifiedPlotRectangle != null) { 2384 workingPlotRectangle = new Rectangle( 2385 Math.max(0, _specifiedPlotRectangle.x), 2386 Math.max(0, _specifiedPlotRectangle.y), 2387 Math.min(drawRect.width, _specifiedPlotRectangle.width), 2388 Math.min(drawRect.height, _specifiedPlotRectangle.height)); 2389 } 2390 2391 // Vertical space for title, if appropriate. 2392 // NOTE: We assume a one-line title. 2393 int titley = 0; 2394 int titlefontheight = _titleFontMetrics.getHeight(); 2395 2396 if (_title == null) { 2397 // NOTE: If the _title is null, then set it to the empty 2398 // string to solve the problem where the fill button overlaps 2399 // the legend if there is no title. The fix here would 2400 // be to modify the legend printing text so that it takes 2401 // into account the case where there is no title by offsetting 2402 // just enough for the button. 2403 _title = ""; 2404 } 2405 2406 titley = titlefontheight + _topPadding; 2407 2408 int captionHeight = _captionStrings.size() 2409 * _captionFontMetrics.getHeight(); 2410 if (captionHeight > 0) { 2411 captionHeight += 5; //extra padding 2412 } 2413 2414 // Number of vertical tick marks depends on the height of the font 2415 // for labeling ticks and the height of the window. 2416 Font previousFont = graphics.getFont(); 2417 graphics.setFont(_labelFont); 2418 graphics.setColor(_foreground); // foreground color not set here --Rob. 2419 2420 int labelheight = _labelFontMetrics.getHeight(); 2421 int halflabelheight = labelheight / 2; 2422 2423 // Draw scaling annotation for x axis. 2424 // NOTE: 5 pixel padding on bottom. 2425 int ySPos = drawRect.height - captionHeight - 5; 2426 int xSPos = drawRect.width - _rightPadding; 2427 2428 if (_xlog) { 2429 _xExp = (int) Math.floor(_xtickMin); 2430 } 2431 2432 if (_xExp != 0 && _xticks == null) { 2433 String superscript = Integer.toString(_xExp); 2434 xSPos -= _superscriptFontMetrics.stringWidth(superscript); 2435 graphics.setFont(_superscriptFont); 2436 2437 if (!_xlog) { 2438 graphics.drawString(superscript, xSPos, 2439 ySPos - halflabelheight); 2440 xSPos -= _labelFontMetrics.stringWidth("x10"); 2441 graphics.setFont(_labelFont); 2442 graphics.drawString("x10", xSPos, ySPos); 2443 } 2444 2445 // NOTE: 5 pixel padding on bottom 2446 _bottomPadding = 3 * labelheight / 2 + 5; 2447 } 2448 2449 // NOTE: 5 pixel padding on the bottom. 2450 if (_xlabel != null 2451 && _bottomPadding < captionHeight + labelheight + 5) { 2452 _bottomPadding = captionHeight + labelheight + 5; 2453 } 2454 2455 // Compute the space needed around the plot, starting with vertical. 2456 // NOTE: padding of 5 pixels below title. 2457 if (workingPlotRectangle != null) { 2458 _uly = workingPlotRectangle.y; 2459 } else { 2460 _uly = titley + 5; 2461 } 2462 2463 // NOTE: 3 pixels above bottom labels. 2464 if (workingPlotRectangle != null) { 2465 _lry = workingPlotRectangle.y + workingPlotRectangle.height; 2466 } else { 2467 _lry = drawRect.height - labelheight - _bottomPadding - 3; 2468 } 2469 2470 int height = _lry - _uly; 2471 _yscale = height / (_yMax - _yMin); 2472 _ytickscale = height / (_ytickMax - _ytickMin); 2473 2474 ////////////////// vertical axis 2475 // Number of y tick marks. 2476 // NOTE: subjective spacing factor. 2477 int ny = 2 + height / (labelheight + 10); 2478 2479 // Compute y increment. 2480 double yStep = _roundUp((_ytickMax - _ytickMin) / ny); 2481 2482 // Compute y starting point so it is a multiple of yStep. 2483 double yStart = yStep * Math.ceil(_ytickMin / yStep); 2484 2485 // NOTE: Following disables first tick. Not a good idea? 2486 // if (yStart == _ytickMin) yStart += yStep; 2487 // Define the strings that will label the y axis. 2488 // Meanwhile, find the width of the widest label. 2489 // The labels are quantized so that they don't have excess resolution. 2490 int widesty = 0; 2491 2492 // These do not get used unless ticks are automatic, but the 2493 // compiler is not smart enough to allow us to reference them 2494 // in two distinct conditional clauses unless they are 2495 // allocated outside the clauses. 2496 String[] ylabels = new String[ny]; 2497 int[] ylabwidth = new int[ny]; 2498 2499 int ind = 0; 2500 2501 if (_yticks == null) { 2502 Vector ygrid = null; 2503 2504 if (_ylog) { 2505 ygrid = _gridInit(yStart, yStep, true, null); 2506 } 2507 2508 // automatic ticks 2509 // First, figure out how many digits after the decimal point 2510 // will be used. 2511 int numfracdigits = _numFracDigits(yStep); 2512 2513 // NOTE: Test cases kept in case they are needed again. 2514 // System.out.println("0.1 with 3 digits: " + _formatNum(0.1, 3)); 2515 // System.out.println("0.0995 with 3 digits: " + 2516 // _formatNum(0.0995, 3)); 2517 // System.out.println("0.9995 with 3 digits: " + 2518 // _formatNum(0.9995, 3)); 2519 // System.out.println("1.9995 with 0 digits: " + 2520 // _formatNum(1.9995, 0)); 2521 // System.out.println("1 with 3 digits: " + _formatNum(1, 3)); 2522 // System.out.println("10 with 0 digits: " + _formatNum(10, 0)); 2523 // System.out.println("997 with 3 digits: " + _formatNum(997, 3)); 2524 // System.out.println("0.005 needs: " + _numFracDigits(0.005)); 2525 // System.out.println("1 needs: " + _numFracDigits(1)); 2526 // System.out.println("999 needs: " + _numFracDigits(999)); 2527 // System.out.println("999.0001 needs: "+_numFracDigits(999.0001)); 2528 // System.out.println("0.005 integer digits: " + 2529 // _numIntDigits(0.005)); 2530 // System.out.println("1 integer digits: " + _numIntDigits(1)); 2531 // System.out.println("999 integer digits: " + _numIntDigits(999)); 2532 // System.out.println("-999.0001 integer digits: " + 2533 // _numIntDigits(999.0001)); 2534 double yTmpStart = yStart; 2535 2536 if (_ylog) { 2537 yTmpStart = _gridStep(ygrid, yStart, yStep, _ylog); 2538 } 2539 2540 for (double ypos = yTmpStart; ypos <= _ytickMax; ypos = _gridStep( 2541 ygrid, ypos, yStep, _ylog)) { 2542 // Prevent out of bounds exceptions 2543 if (ind >= ny) { 2544 break; 2545 } 2546 2547 String yticklabel; 2548 2549 if (_ylog) { 2550 yticklabel = _formatLogNum(ypos, numfracdigits); 2551 } else { 2552 yticklabel = _formatNum(ypos, numfracdigits); 2553 } 2554 2555 ylabels[ind] = yticklabel; 2556 2557 int lw = _labelFontMetrics.stringWidth(yticklabel); 2558 ylabwidth[ind++] = lw; 2559 2560 if (lw > widesty) { 2561 widesty = lw; 2562 } 2563 } 2564 } else { 2565 // explicitly specified ticks 2566 Enumeration nl = _yticklabels.elements(); 2567 2568 while (nl.hasMoreElements()) { 2569 String label = (String) nl.nextElement(); 2570 int lw = _labelFontMetrics.stringWidth(label); 2571 2572 if (lw > widesty) { 2573 widesty = lw; 2574 } 2575 } 2576 } 2577 2578 // Next we do the horizontal spacing. 2579 if (workingPlotRectangle != null) { 2580 _ulx = workingPlotRectangle.x; 2581 } else { 2582 if (_ylabel != null) { 2583 _ulx = widesty + _labelFontMetrics.stringWidth("W") 2584 + _leftPadding; 2585 } else { 2586 _ulx = widesty + _leftPadding; 2587 } 2588 } 2589 2590 int legendwidth = _drawLegend(graphics, drawRect.width - _rightPadding, 2591 _uly); 2592 2593 if (workingPlotRectangle != null) { 2594 _lrx = workingPlotRectangle.x + workingPlotRectangle.width; 2595 } else { 2596 _lrx = drawRect.width - legendwidth - _rightPadding; 2597 } 2598 2599 int width = _lrx - _ulx; 2600 _xscale = width / (_xMax - _xMin); 2601 2602 _xtickscale = width / (_xtickMax - _xtickMin); 2603 2604 // Background for the plotting rectangle. 2605 // Always use a white background because the dataset colors 2606 // were designed for a white background. 2607 graphics.setColor(Color.white); 2608 graphics.fillRect(_ulx, _uly, width, height); 2609 2610 graphics.setColor(_foreground); 2611 graphics.drawRect(_ulx, _uly, width, height); 2612 2613 // NOTE: subjective tick length. 2614 int tickLength = 5; 2615 int xCoord1 = _ulx + tickLength; 2616 int xCoord2 = _lrx - tickLength; 2617 2618 if (_yticks == null) { 2619 // auto-ticks 2620 Vector ygrid = null; 2621 double yTmpStart = yStart; 2622 2623 if (_ylog) { 2624 ygrid = _gridInit(yStart, yStep, true, null); 2625 yTmpStart = _gridStep(ygrid, yStart, yStep, _ylog); 2626 ny = ind; 2627 } 2628 2629 ind = 0; 2630 2631 // Set to false if we don't need the exponent 2632 boolean needExponent = _ylog; 2633 2634 for (double ypos = yTmpStart; ypos <= _ytickMax; ypos = _gridStep( 2635 ygrid, ypos, yStep, _ylog)) { 2636 // Prevent out of bounds exceptions 2637 if (ind >= ny) { 2638 break; 2639 } 2640 2641 int yCoord1 = _lry - (int) ((ypos - _ytickMin) * _ytickscale); 2642 2643 // The lowest label is shifted up slightly to avoid 2644 // colliding with x labels. 2645 int offset = 0; 2646 2647 if (ind > 0 && !_ylog) { 2648 offset = halflabelheight; 2649 } 2650 2651 graphics.drawLine(_ulx, yCoord1, xCoord1, yCoord1); 2652 graphics.drawLine(_lrx, yCoord1, xCoord2, yCoord1); 2653 2654 if (_grid && yCoord1 != _uly && yCoord1 != _lry) { 2655 graphics.setColor(Color.lightGray); 2656 graphics.drawLine(xCoord1, yCoord1, xCoord2, yCoord1); 2657 graphics.setColor(_foreground); 2658 } 2659 2660 // Check to see if any of the labels printed contain 2661 // the exponent. If we don't see an exponent, then print it. 2662 if (_ylog && ylabels[ind].indexOf('e') != -1) { 2663 needExponent = false; 2664 } 2665 2666 // NOTE: 4 pixel spacing between axis and labels. 2667 graphics.drawString(ylabels[ind], _ulx - ylabwidth[ind++] - 4, 2668 yCoord1 + offset); 2669 } 2670 2671 if (_ylog) { 2672 // Draw in grid lines that don't have labels. 2673 Vector unlabeledgrid = _gridInit(yStart, yStep, false, ygrid); 2674 2675 if (unlabeledgrid.size() > 0) { 2676 // If the step is greater than 1, clamp it to 1 so that 2677 // we draw the unlabeled grid lines for each 2678 //integer interval. 2679 double tmpStep = yStep > 1.0 ? 1.0 : yStep; 2680 2681 for (double ypos = _gridStep(unlabeledgrid, yStart, tmpStep, 2682 _ylog); ypos <= _ytickMax; ypos = _gridStep( 2683 unlabeledgrid, ypos, tmpStep, _ylog)) { 2684 int yCoord1 = _lry 2685 - (int) ((ypos - _ytickMin) * _ytickscale); 2686 2687 if (_grid && yCoord1 != _uly && yCoord1 != _lry) { 2688 graphics.setColor(Color.lightGray); 2689 graphics.drawLine(_ulx + 1, yCoord1, _lrx - 1, 2690 yCoord1); 2691 graphics.setColor(_foreground); 2692 } 2693 } 2694 } 2695 2696 if (needExponent) { 2697 // We zoomed in, so we need the exponent 2698 _yExp = (int) Math.floor(yTmpStart); 2699 } else { 2700 _yExp = 0; 2701 } 2702 } 2703 2704 // Draw scaling annotation for y axis. 2705 if (_yExp != 0) { 2706 graphics.drawString("x10", 2, titley); 2707 graphics.setFont(_superscriptFont); 2708 graphics.drawString(Integer.toString(_yExp), 2709 _labelFontMetrics.stringWidth("x10") + 2, 2710 titley - halflabelheight); 2711 graphics.setFont(_labelFont); 2712 } 2713 } else { 2714 // ticks have been explicitly specified 2715 Enumeration nt = _yticks.elements(); 2716 Enumeration nl = _yticklabels.elements(); 2717 2718 while (nl.hasMoreElements()) { 2719 String label = (String) nl.nextElement(); 2720 double ypos = ((Double) nt.nextElement()).doubleValue(); 2721 2722 if (ypos > _yMax || ypos < _yMin) { 2723 continue; 2724 } 2725 2726 int yCoord1 = _lry - (int) ((ypos - _yMin) * _yscale); 2727 int offset = 0; 2728 2729 if (ypos < _lry - labelheight) { 2730 offset = halflabelheight; 2731 } 2732 2733 graphics.drawLine(_ulx, yCoord1, xCoord1, yCoord1); 2734 graphics.drawLine(_lrx, yCoord1, xCoord2, yCoord1); 2735 2736 if (_grid && yCoord1 != _uly && yCoord1 != _lry) { 2737 graphics.setColor(Color.lightGray); 2738 graphics.drawLine(xCoord1, yCoord1, xCoord2, yCoord1); 2739 graphics.setColor(_foreground); 2740 } 2741 2742 // NOTE: 3 pixel spacing between axis and labels. 2743 graphics.drawString(label, 2744 _ulx - _labelFontMetrics.stringWidth(label) - 3, 2745 yCoord1 + offset); 2746 } 2747 } 2748 2749 //////////////////// horizontal axis 2750 int yCoord1 = _uly + tickLength; 2751 int yCoord2 = _lry - tickLength; 2752 int charwidth = _labelFontMetrics.stringWidth("8"); 2753 2754 if (_xticks == null) { 2755 // auto-ticks 2756 // Number of x tick marks. 2757 // Need to start with a guess and converge on a solution here. 2758 int nx = 10; 2759 double xStep = 0.0; 2760 int numfracdigits = 0; 2761 2762 if (_xlog) { 2763 // X axes log labels will be at most 6 chars: -1E-02 2764 nx = 2 + width / (charwidth * 6 + 10); 2765 } else { 2766 // Limit to 10 iterations 2767 int count = 0; 2768 2769 while (count++ <= 10) { 2770 xStep = _roundUp((_xtickMax - _xtickMin) / nx); 2771 2772 // Compute the width of a label for this xStep 2773 numfracdigits = _numFracDigits(xStep); 2774 2775 // Number of integer digits is the maximum of two endpoints 2776 int intdigits = _numIntDigits(_xtickMax); 2777 int inttemp = _numIntDigits(_xtickMin); 2778 2779 if (intdigits < inttemp) { 2780 intdigits = inttemp; 2781 } 2782 2783 // Allow two extra digits (decimal point and sign). 2784 int maxlabelwidth = charwidth 2785 * (numfracdigits + 2 + intdigits); 2786 2787 // Compute new estimate of number of ticks. 2788 int savenx = nx; 2789 2790 // NOTE: 10 additional pixels between labels. 2791 // NOTE: Try to ensure at least two tick marks. 2792 nx = 2 + width / (maxlabelwidth + 10); 2793 2794 if (nx - savenx <= 1 || savenx - nx <= 1) { 2795 break; 2796 } 2797 } 2798 } 2799 2800 xStep = _roundUp((_xtickMax - _xtickMin) / nx); 2801 numfracdigits = _numFracDigits(xStep); 2802 2803 // Compute x starting point so it is a multiple of xStep. 2804 double xStart = xStep * Math.ceil(_xtickMin / xStep); 2805 2806 // NOTE: Following disables first tick. Not a good idea? 2807 // if (xStart == _xMin) xStart += xStep; 2808 Vector xgrid = null; 2809 double xTmpStart = xStart; 2810 2811 if (_xlog) { 2812 xgrid = _gridInit(xStart, xStep, true, null); 2813 2814 //xgrid = _gridInit(xStart, xStep); 2815 xTmpStart = _gridRoundUp(xgrid, xStart); 2816 } 2817 2818 // Set to false if we don't need the exponent 2819 boolean needExponent = _xlog; 2820 2821 // Label the x axis. The labels are quantized so that 2822 // they don't have excess resolution. 2823 for (double xpos = xTmpStart; xpos <= _xtickMax; xpos = _gridStep( 2824 xgrid, xpos, xStep, _xlog)) { 2825 String xticklabel; 2826 2827 if (_xlog) { 2828 xticklabel = _formatLogNum(xpos, numfracdigits); 2829 2830 if (xticklabel.indexOf('e') != -1) { 2831 needExponent = false; 2832 } 2833 } else { 2834 xticklabel = _formatNum(xpos, numfracdigits); 2835 } 2836 2837 xCoord1 = _ulx + (int) ((xpos - _xtickMin) * _xtickscale); 2838 graphics.drawLine(xCoord1, _uly, xCoord1, yCoord1); 2839 graphics.drawLine(xCoord1, _lry, xCoord1, yCoord2); 2840 2841 if (_grid && xCoord1 != _ulx && xCoord1 != _lrx) { 2842 graphics.setColor(Color.lightGray); 2843 graphics.drawLine(xCoord1, yCoord1, xCoord1, yCoord2); 2844 graphics.setColor(_foreground); 2845 } 2846 2847 int labxpos = xCoord1 2848 - _labelFontMetrics.stringWidth(xticklabel) / 2; 2849 2850 // NOTE: 3 pixel spacing between axis and labels. 2851 graphics.drawString(xticklabel, labxpos, 2852 _lry + 3 + labelheight); 2853 } 2854 2855 if (_xlog) { 2856 // Draw in grid lines that don't have labels. 2857 // If the step is greater than 1, clamp it to 1 so that 2858 // we draw the unlabeled grid lines for each 2859 // integer interval. 2860 double tmpStep = xStep > 1.0 ? 1.0 : xStep; 2861 2862 // Recalculate the start using the new step. 2863 xTmpStart = tmpStep * Math.ceil(_xtickMin / tmpStep); 2864 2865 Vector unlabeledgrid = _gridInit(xTmpStart, tmpStep, false, 2866 xgrid); 2867 2868 if (unlabeledgrid.size() > 0) { 2869 for (double xpos = _gridStep(unlabeledgrid, xTmpStart, 2870 tmpStep, 2871 _xlog); xpos <= _xtickMax; xpos = _gridStep( 2872 unlabeledgrid, xpos, tmpStep, _xlog)) { 2873 xCoord1 = _ulx 2874 + (int) ((xpos - _xtickMin) * _xtickscale); 2875 2876 if (_grid && xCoord1 != _ulx && xCoord1 != _lrx) { 2877 graphics.setColor(Color.lightGray); 2878 graphics.drawLine(xCoord1, _uly + 1, xCoord1, 2879 _lry - 1); 2880 graphics.setColor(_foreground); 2881 } 2882 } 2883 } 2884 2885 if (needExponent) { 2886 _xExp = (int) Math.floor(xTmpStart); 2887 graphics.setFont(_superscriptFont); 2888 graphics.drawString(Integer.toString(_xExp), xSPos, 2889 ySPos - halflabelheight); 2890 xSPos -= _labelFontMetrics.stringWidth("x10"); 2891 graphics.setFont(_labelFont); 2892 graphics.drawString("x10", xSPos, ySPos); 2893 } else { 2894 _xExp = 0; 2895 } 2896 } 2897 } else { 2898 // ticks have been explicitly specified 2899 Enumeration nt = _xticks.elements(); 2900 Enumeration nl = _xticklabels.elements(); 2901 2902 // Code contributed by Jun Wu (jwu@inin.com.au) 2903 double preLength = 0.0; 2904 2905 while (nl.hasMoreElements()) { 2906 String label = (String) nl.nextElement(); 2907 double xpos = ((Double) nt.nextElement()).doubleValue(); 2908 2909 // If xpos is out of range, ignore. 2910 if (xpos > _xMax || xpos < _xMin) { 2911 continue; 2912 } 2913 2914 // Find the center position of the label. 2915 xCoord1 = _ulx + (int) ((xpos - _xMin) * _xscale); 2916 2917 // Find the start position of x label. 2918 int labxpos = xCoord1 2919 - _labelFontMetrics.stringWidth(label) / 2; 2920 2921 // If the labels are not overlapped, proceed. 2922 if (labxpos > preLength) { 2923 // calculate the length of the label 2924 preLength = xCoord1 2925 + _labelFontMetrics.stringWidth(label) / 2 + 10; 2926 2927 // Draw the label. 2928 // NOTE: 3 pixel spacing between axis and labels. 2929 graphics.drawString(label, labxpos, _lry + 3 + labelheight); 2930 2931 // Draw the label mark on the axis 2932 graphics.drawLine(xCoord1, _uly, xCoord1, yCoord1); 2933 graphics.drawLine(xCoord1, _lry, xCoord1, yCoord2); 2934 2935 // Draw the grid line 2936 if (_grid && xCoord1 != _ulx && xCoord1 != _lrx) { 2937 graphics.setColor(Color.lightGray); 2938 graphics.drawLine(xCoord1, yCoord1, xCoord1, yCoord2); 2939 graphics.setColor(_foreground); 2940 } 2941 } 2942 } 2943 } 2944 2945 //////////////////// Draw title and axis labels now. 2946 // Center the title and X label over the plotting region, not 2947 // the window. 2948 graphics.setColor(_foreground); 2949 2950 if (_title != null) { 2951 graphics.setFont(_titleFont); 2952 2953 int titlex = _ulx 2954 + (width - _titleFontMetrics.stringWidth(_title)) / 2; 2955 graphics.drawString(_title, titlex, titley); 2956 } 2957 2958 graphics.setFont(_labelFont); 2959 2960 if (_xlabel != null) { 2961 int labelx = _ulx 2962 + (width - _labelFontMetrics.stringWidth(_xlabel)) / 2; 2963 graphics.drawString(_xlabel, labelx, ySPos); 2964 } 2965 2966 int charcenter = 2 + _labelFontMetrics.stringWidth("W") / 2; 2967 2968 if (_ylabel != null) { 2969 int yl = _ylabel.length(); 2970 2971 if (graphics instanceof Graphics2D) { 2972 int starty = _uly + (_lry - _uly) / 2 2973 + _labelFontMetrics.stringWidth(_ylabel) / 2 2974 - charwidth; 2975 Graphics2D g2d = (Graphics2D) graphics; 2976 2977 // NOTE: Fudge factor so label doesn't touch axis labels. 2978 int startx = charcenter + halflabelheight - 2; 2979 g2d.rotate(Math.toRadians(-90), startx, starty); 2980 g2d.drawString(_ylabel, startx, starty); 2981 g2d.rotate(Math.toRadians(90), startx, starty); 2982 } else { 2983 // Not graphics 2D, no support for rotation. 2984 // Vertical label is fairly complex to draw. 2985 int starty = _uly + (_lry - _uly) / 2 - yl * halflabelheight 2986 + labelheight; 2987 2988 for (int i = 0; i < yl; i++) { 2989 String nchar = _ylabel.substring(i, i + 1); 2990 int cwidth = _labelFontMetrics.stringWidth(nchar); 2991 graphics.drawString(nchar, charcenter - cwidth / 2, starty); 2992 starty += labelheight; 2993 } 2994 } 2995 } 2996 2997 graphics.setFont(_captionFont); 2998 int fontHt = _captionFontMetrics.getHeight(); 2999 int yCapPosn = drawRect.height - captionHeight + 14; 3000 for (Enumeration captions = _captionStrings.elements(); captions 3001 .hasMoreElements();) { 3002 String captionLine = (String) captions.nextElement(); 3003 int labelx = _ulx 3004 + (width - _captionFontMetrics.stringWidth(captionLine)) 3005 / 2; 3006 graphics.drawString(captionLine, labelx, yCapPosn); 3007 yCapPosn += fontHt; 3008 } 3009 graphics.setFont(previousFont); 3010 } 3011 3012 /** Put a mark corresponding to the specified dataset at the 3013 * specified x and y position. The mark is drawn in the 3014 * current color. In this base class, a point is a 3015 * filled rectangle 6 pixels across. Note that marks greater than 3016 * about 6 pixels in size will not look very good since they will 3017 * overlap axis labels and may not fit well in the legend. The 3018 * <i>clip</i> argument, if <code>true</code>, states 3019 * that the point should not be drawn if 3020 * it is out of range. 3021 * 3022 * Note that this method is not synchronized, so the caller should be. 3023 * Moreover this method should always be called from the event thread 3024 * when being used to write to the screen. 3025 * 3026 * @param graphics The graphics context. 3027 * @param dataset The index of the data set. 3028 * @param xpos The X position. 3029 * @param ypos The Y position. 3030 * @param clip If true, do not draw if out of range. 3031 */ 3032 protected void _drawPoint(Graphics graphics, int dataset, long xpos, 3033 long ypos, boolean clip) { 3034 // Ignore if there is no graphics object to draw on. 3035 if (graphics == null) { 3036 return; 3037 } 3038 3039 boolean pointinside = ypos <= _lry && ypos >= _uly && xpos <= _lrx 3040 && xpos >= _ulx; 3041 3042 if (!pointinside && clip) { 3043 return; 3044 } 3045 3046 graphics.fillRect((int) xpos - 6, (int) ypos - 6, 6, 6); 3047 } 3048 3049 /** Return Latex plot data. This base class returns nothing. 3050 * @return A string suitable for inclusion in Latex. 3051 */ 3052 protected String _exportLatexPlotData() { 3053 return ""; 3054 } 3055 3056 /** Display basic information in its own window. 3057 */ 3058 protected void _help() { 3059 String message = "Ptolemy plot package\n" + "By: Edward A. Lee\n" 3060 + "and Christopher Brooks\n" + "Version " + PTPLOT_RELEASE 3061 + ", Build: $Id$\n\n" + "Key bindings:\n" 3062 + " Cntrl-c: copy plot to clipboard (PNG format), if permitted\n" 3063 + " D: dump plot data to standard out\n" 3064 + " E: export plot to standard out (EPS format)\n" 3065 + " F: fill plot\n" 3066 + " H or ?: print help message (this message)\n" 3067 + " Cntrl-D or Q: quit\n" + "For more information, see\n" 3068 + "http://ptolemy.eecs.berkeley.edu/java/ptplot\n"; 3069 JOptionPane.showMessageDialog(this, message, "Ptolemy Plot Help Window", 3070 JOptionPane.INFORMATION_MESSAGE); 3071 } 3072 3073 /** Parse a line that gives plotting information. In this base 3074 * class, only lines pertaining to the title and labels are processed. 3075 * Everything else is ignored. Return true if the line is recognized. 3076 * It is not synchronized, so its caller should be. 3077 * @param line A line of text. 3078 * @return True if the line was recognized. 3079 */ 3080 protected boolean _parseLine(String line) { 3081 // If you modify this method, you should also modify write() 3082 // We convert the line to lower case so that the command 3083 // names are case insensitive. 3084 String lcLine = line.toLowerCase(Locale.getDefault()); 3085 3086 if (lcLine.startsWith("#")) { 3087 // comment character 3088 return true; 3089 } else if (lcLine.startsWith("titletext:")) { 3090 setTitle(line.substring(10).trim()); 3091 return true; 3092 } else if (lcLine.startsWith("title:")) { 3093 // Tolerate alternative tag. 3094 setTitle(line.substring(6).trim()); 3095 return true; 3096 } else if (lcLine.startsWith("xlabel:")) { 3097 setXLabel(line.substring(7).trim()); 3098 return true; 3099 } else if (lcLine.startsWith("ylabel:")) { 3100 setYLabel(line.substring(7).trim()); 3101 return true; 3102 } else if (lcLine.startsWith("xrange:")) { 3103 int comma = line.indexOf(",", 7); 3104 3105 if (comma > 0) { 3106 String min = line.substring(7, comma).trim(); 3107 String max = line.substring(comma + 1).trim(); 3108 3109 try { 3110 Double dmin = Double.valueOf(min); 3111 Double dmax = Double.valueOf(max); 3112 setXRange(dmin.doubleValue(), dmax.doubleValue()); 3113 } catch (NumberFormatException e) { 3114 // ignore if format is bogus. 3115 } 3116 } 3117 3118 return true; 3119 } else if (lcLine.startsWith("yrange:")) { 3120 int comma = line.indexOf(",", 7); 3121 3122 if (comma > 0) { 3123 String min = line.substring(7, comma).trim(); 3124 String max = line.substring(comma + 1).trim(); 3125 3126 try { 3127 Double dmin = Double.valueOf(min); 3128 Double dmax = Double.valueOf(max); 3129 setYRange(dmin.doubleValue(), dmax.doubleValue()); 3130 } catch (NumberFormatException e) { 3131 // ignore if format is bogus. 3132 } 3133 } 3134 3135 return true; 3136 } else if (lcLine.startsWith("xticks:")) { 3137 // example: 3138 // XTicks "label" 0, "label" 1, "label" 3 3139 _parsePairs(line.substring(7), true); 3140 return true; 3141 } else if (lcLine.startsWith("yticks:")) { 3142 // example: 3143 // YTicks "label" 0, "label" 1, "label" 3 3144 _parsePairs(line.substring(7), false); 3145 return true; 3146 } else if (lcLine.startsWith("xlog:")) { 3147 if (lcLine.indexOf("off", 5) >= 0) { 3148 _xlog = false; 3149 } else { 3150 _xlog = true; 3151 } 3152 3153 return true; 3154 } else if (lcLine.startsWith("ylog:")) { 3155 if (lcLine.indexOf("off", 5) >= 0) { 3156 _ylog = false; 3157 } else { 3158 _ylog = true; 3159 } 3160 3161 return true; 3162 } else if (lcLine.startsWith("grid:")) { 3163 if (lcLine.indexOf("off", 5) >= 0) { 3164 _grid = false; 3165 } else { 3166 _grid = true; 3167 } 3168 3169 return true; 3170 } else if (lcLine.startsWith("wrap:")) { 3171 if (lcLine.indexOf("off", 5) >= 0) { 3172 _wrap = false; 3173 } else { 3174 _wrap = true; 3175 } 3176 3177 return true; 3178 } else if (lcLine.startsWith("color:")) { 3179 if (lcLine.indexOf("off", 6) >= 0) { 3180 _usecolor = false; 3181 } else { 3182 _usecolor = true; 3183 } 3184 3185 return true; 3186 } else if (lcLine.startsWith("captions:")) { 3187 addCaptionLine(line.substring(10)); 3188 return true; 3189 } 3190 3191 return false; 3192 } 3193 3194 /** Reset a scheduled redraw tasks. This base class does nothing. 3195 * Derived classes should define the correct behavior. 3196 */ 3197 protected void _resetScheduledTasks() { 3198 // This method should be implemented by the derived classes. 3199 } 3200 3201 /** Perform a scheduled redraw. This base class does nothing. 3202 * Derived classes should define the correct behavior. 3203 */ 3204 protected void _scheduledRedraw() { 3205 // Does nothing on this level 3206 } 3207 3208 /** Set the visibility of the Fill button. 3209 * @param visibility True if the fill button is to be visible. 3210 * @deprecated Use #setButtons(boolean) instead. 3211 */ 3212 @Deprecated 3213 protected void _setButtonsVisibility(boolean visibility) { 3214 // Changing legend means we need to repaint the offscreen buffer. 3215 _plotImage = null; 3216 3217 _printButton.setVisible(visibility); 3218 _fillButton.setVisible(visibility); 3219 _eqAxButton.setVisible(visibility); // Dirk: equal axis button 3220 _formatButton.setVisible(visibility); 3221 _resetButton.setVisible(visibility); 3222 } 3223 3224 /** Set the padding multiple. 3225 * The plot rectangle can be "padded" in each direction -x, +x, -y, and 3226 * +y. If the padding is set to 0.05 (and the padding is used), then 3227 * there is 10% more length on each axis than set by the setXRange() and 3228 * setYRange() methods, 5% in each direction. 3229 * @param padding The padding multiple. 3230 */ 3231 protected void _setPadding(double padding) { 3232 // Changing legend means we need to repaint the offscreen buffer. 3233 _plotImage = null; 3234 3235 _padding = padding; 3236 } 3237 3238 /** 3239 * Return whether repainting happens by a timed thread. 3240 * @return True when repainting happens by a timer thread. 3241 */ 3242 protected boolean _timedRepaint() { 3243 return _timedRepaint || _automaticRescale; 3244 } 3245 3246 /** Write plot information to the specified output stream in the 3247 * old PtPlot syntax. 3248 * Derived classes should override this method to first call 3249 * the parent class method, then add whatever additional information 3250 * they wish to add to the stream. 3251 * It is not synchronized, so its caller should be. 3252 * @param output A buffered print writer. 3253 * @deprecated 3254 */ 3255 @Deprecated 3256 protected void _writeOldSyntax(PrintWriter output) { 3257 output.println("# Ptolemy plot, version 2.0"); 3258 3259 if (_title != null) { 3260 output.println("TitleText: " + _title); 3261 } 3262 3263 if (_captionStrings != null) { 3264 for (Enumeration captions = _captionStrings.elements(); captions 3265 .hasMoreElements();) { 3266 String captionLine = (String) captions.nextElement(); 3267 output.println("Caption: " + captionLine); 3268 } 3269 } 3270 3271 if (_xlabel != null) { 3272 output.println("XLabel: " + _xlabel); 3273 } 3274 3275 if (_ylabel != null) { 3276 output.println("YLabel: " + _ylabel); 3277 } 3278 3279 if (_xRangeGiven) { 3280 output.println("XRange: " + _xlowgiven + ", " + _xhighgiven); 3281 } 3282 3283 if (_yRangeGiven) { 3284 output.println("YRange: " + _ylowgiven + ", " + _yhighgiven); 3285 } 3286 3287 if (_xticks != null && _xticks.size() > 0) { 3288 output.print("XTicks: "); 3289 3290 int last = _xticks.size() - 1; 3291 3292 for (int i = 0; i < last; i++) { 3293 output.print("\"" + (String) _xticklabels.elementAt(i) + "\" " 3294 + _xticks.elementAt(i) + ", "); 3295 } 3296 3297 output.println("\"" + (String) _xticklabels.elementAt(last) + "\" " 3298 + _xticks.elementAt(last)); 3299 } 3300 3301 if (_yticks != null && _yticks.size() > 0) { 3302 output.print("YTicks: "); 3303 3304 int last = _yticks.size() - 1; 3305 3306 for (int i = 0; i < last; i++) { 3307 output.print("\"" + (String) _yticklabels.elementAt(i) + "\" " 3308 + _yticks.elementAt(i) + ", "); 3309 } 3310 3311 output.println("\"" + (String) _yticklabels.elementAt(last) + "\" " 3312 + _yticks.elementAt(last)); 3313 } 3314 3315 if (_xlog) { 3316 output.println("XLog: on"); 3317 } 3318 3319 if (_ylog) { 3320 output.println("YLog: on"); 3321 } 3322 3323 if (!_grid) { 3324 output.println("Grid: off"); 3325 } 3326 3327 if (_wrap) { 3328 output.println("Wrap: on"); 3329 } 3330 3331 if (!_usecolor) { 3332 output.println("Color: off"); 3333 } 3334 } 3335 3336 /////////////////////////////////////////////////////////////////// 3337 //// protected variables //// 3338 3339 /** The maximum y value of the range of the data to be plotted. */ 3340 protected transient volatile double _yMax = 0; 3341 3342 /** The minimum y value of the range of the data to be plotted. */ 3343 protected transient volatile double _yMin = 0; 3344 3345 /** The maximum x value of the range of the data to be plotted. */ 3346 protected transient volatile double _xMax = 0; 3347 3348 /** The minimum y value of the range of the data to be plotted. */ 3349 protected transient volatile double _xMin = 0; 3350 3351 /** The factor we pad by so that we don't plot points on the axes. 3352 */ 3353 protected volatile double _padding = 0.05; 3354 3355 /** An offscreen buffer for improving plot performance. */ 3356 protected transient BufferedImage _plotImage = null; 3357 3358 /** True if the x range have been given. */ 3359 protected transient boolean _xRangeGiven = false; 3360 3361 /** True if the y range have been given. */ 3362 protected transient boolean _yRangeGiven = false; 3363 3364 /** True if the ranges were given by zooming. */ 3365 protected transient boolean _rangesGivenByZooming = false; 3366 3367 /** The given X and Y ranges. 3368 * If they have been given the top and bottom of the x and y ranges. 3369 * This is different from _xMin and _xMax, which actually represent 3370 * the range of data that is plotted. This represents the range 3371 * specified (which may be different due to zooming). 3372 */ 3373 protected double _xlowgiven; 3374 3375 /** The given X and Y ranges. 3376 * If they have been given the top and bottom of the x and y ranges. 3377 * This is different from _xMin and _xMax, which actually represent 3378 * the range of data that is plotted. This represents the range 3379 * specified (which may be different due to zooming). 3380 */ 3381 protected double _xhighgiven; 3382 3383 /** The given X and Y ranges. 3384 * If they have been given the top and bottom of the x and y ranges. 3385 * This is different from _xMin and _xMax, which actually represent 3386 * the range of data that is plotted. This represents the range 3387 * specified (which may be different due to zooming). 3388 */ 3389 protected double _ylowgiven; 3390 3391 /** The given X and Y ranges. 3392 * If they have been given the top and bottom of the x and y ranges. 3393 * This is different from _xMin and _xMax, which actually represent 3394 * the range of data that is plotted. This represents the range 3395 * specified (which may be different due to zooming). 3396 */ 3397 protected double _yhighgiven; 3398 3399 /** The minimum X value registered so for, for auto ranging. */ 3400 protected double _xBottom = Double.MAX_VALUE; 3401 3402 /** The maximum X value registered so for, for auto ranging. */ 3403 protected double _xTop = -Double.MAX_VALUE; 3404 3405 /** The minimum Y value registered so for, for auto ranging. */ 3406 protected double _yBottom = Double.MAX_VALUE; 3407 3408 /** The maximum Y value registered so for, for auto ranging. */ 3409 protected double _yTop = -Double.MAX_VALUE; 3410 3411 /** Whether to draw the axes using a logarithmic scale. */ 3412 protected boolean _xlog = false; 3413 3414 /** Whether to draw the axes using a logarithmic scale. */ 3415 protected boolean _ylog = false; 3416 3417 /** For use in calculating log base 10. A log times this is a log base 10. */ 3418 protected static final double _LOG10SCALE = 1 / Math.log(10); 3419 3420 /** Whether to draw a background grid. */ 3421 protected boolean _grid = true; 3422 3423 /** Whether to wrap the X axis. */ 3424 protected boolean _wrap = false; 3425 3426 /** The high range of the X axis for wrapping. */ 3427 protected double _wrapHigh; 3428 3429 /** The low range of the X axis for wrapping. */ 3430 protected double _wrapLow; 3431 3432 /** Color of the background, settable from HTML. */ 3433 protected Color _background = Color.white; 3434 3435 /** Color of the foreground, settable from HTML. */ 3436 protected Color _foreground = Color.black; 3437 3438 /** Top padding. 3439 * Derived classes can increment these to make space around the plot. 3440 */ 3441 protected int _topPadding = 10; 3442 3443 /** Bottom padding. 3444 * Derived classes can increment these to make space around the plot. 3445 */ 3446 protected int _bottomPadding = 5; 3447 3448 /** Right padding. 3449 * Derived classes can increment these to make space around the plot. 3450 */ 3451 protected int _rightPadding = 10; 3452 3453 /** Left padding. 3454 * Derived classes can increment these to make space around the plot. 3455 */ 3456 protected int _leftPadding = 10; 3457 3458 // The naming convention is: "_ulx" = "upper left x", where "x" is 3459 // the horizontal dimension. 3460 3461 /** The x value of the upper left corner of the plot rectangle in pixels. 3462 * Given a mouse click at x0, to convert to data coordinates, use: 3463 * (_xMin + (x0 - _ulx) / _xscale). 3464 */ 3465 protected int _ulx = 1; 3466 3467 /** The y value of the upper left corner of the plot rectangle in pixels. 3468 * Given a mouse click at y0, to convert to data coordinates, use: 3469 * (_yMax - (y0 - _uly) / _yscale). 3470 */ 3471 protected int _uly = 1; 3472 3473 /** The x value of the lower right corner of 3474 * the plot rectangle in pixels. */ 3475 protected int _lrx = 100; 3476 3477 /** The y value of the lower right corner of 3478 * the plot rectangle in pixels. */ 3479 protected int _lry = 100; 3480 3481 /** User specified plot rectangle, null if none specified. 3482 * @see #setPlotRectangle(Rectangle) 3483 */ 3484 protected Rectangle _specifiedPlotRectangle = null; 3485 3486 /** Scaling used for the vertical axis in plotting points. 3487 * The units are pixels/unit, where unit is the units of the Y axis. 3488 */ 3489 protected double _yscale = 1.0; 3490 3491 /** Scaling used for the horizontal axis in plotting points. 3492 * The units are pixels/unit, where unit is the units of the X axis. 3493 */ 3494 protected double _xscale = 1.0; 3495 3496 /** Indicator whether to use _colors. */ 3497 protected volatile boolean _usecolor = true; 3498 3499 /** The default colors, by data set. 3500 * There are 11 colors so that combined with the 3501 * 10 marks of the Plot class, we can distinguish 110 3502 * distinct data sets. 3503 */ 3504 static protected Color[] _colors = { new Color(0xff0000), // red 3505 new Color(0x0000ff), // blue 3506 new Color(0x00aaaa), // cyan-ish 3507 new Color(0x000000), // black 3508 new Color(0xffa500), // orange 3509 new Color(0x53868b), // cadetblue4 3510 new Color(0xff7f50), // coral 3511 new Color(0x45ab1f), // dark green-ish 3512 new Color(0x90422d), // sienna-ish 3513 new Color(0xa0a0a0), // grey-ish 3514 new Color(0x14ff14), // green-ish 3515 }; 3516 3517 /** Width and height of component in pixels. */ 3518 protected int _width = 500; 3519 3520 /** Width and height of component in pixels. */ 3521 protected int _height = 300; 3522 3523 /** Width and height of component in pixels. */ 3524 protected int _preferredWidth = 500; 3525 3526 /** Width and height of component in pixels. */ 3527 protected int _preferredHeight = 300; 3528 3529 /** Indicator that size has been set. */ 3530 3531 //protected boolean _sizeHasBeenSet = false; 3532 /** The document base we use to find the _filespec. 3533 * NOTE: Use of this variable is deprecated. But it is made available 3534 * to derived classes for backward compatibility. 3535 * FIXME: Sun's appletviewer gives an exception if this is protected. 3536 * Why?? So we make it temporarily public. 3537 */ 3538 public URL _documentBase = null; 3539 3540 /////////////////////////////////////////////////////////////////// 3541 //// private methods //// 3542 3543 /* 3544 * Draw the legend in the upper right corner and return the width 3545 * (in pixels) used up. The arguments give the upper right corner 3546 * of the region where the legend should be placed. 3547 */ 3548 private int _drawLegend(Graphics graphics, int urx, int ury) { 3549 // Ignore if there is no graphics object to draw on. 3550 if (graphics == null) { 3551 return 0; 3552 } 3553 3554 // FIXME: consolidate all these for efficiency 3555 Font previousFont = graphics.getFont(); 3556 graphics.setFont(_labelFont); 3557 3558 int spacing = _labelFontMetrics.getHeight(); 3559 3560 Enumeration v = _legendStrings.elements(); 3561 Enumeration i = _legendDatasets.elements(); 3562 int ypos = ury + spacing; 3563 int maxwidth = 0; 3564 3565 while (v.hasMoreElements()) { 3566 String legend = (String) v.nextElement(); 3567 3568 // NOTE: relies on _legendDatasets having the same num. of entries. 3569 int dataset = ((Integer) i.nextElement()).intValue(); 3570 3571 if (dataset >= 0) { 3572 if (_usecolor) { 3573 // Points are only distinguished up to the number of colors 3574 int color = dataset % _colors.length; 3575 graphics.setColor(_colors[color]); 3576 } 3577 3578 _drawPoint(graphics, dataset, urx - 3, ypos - 3, false); 3579 3580 graphics.setColor(_foreground); 3581 3582 int width = _labelFontMetrics.stringWidth(legend); 3583 3584 if (width > maxwidth) { 3585 maxwidth = width; 3586 } 3587 3588 graphics.drawString(legend, urx - 15 - width, ypos); 3589 ypos += spacing; 3590 } 3591 } 3592 3593 graphics.setFont(previousFont); 3594 return 22 + maxwidth; // NOTE: subjective spacing parameter. 3595 } 3596 3597 // Execute all actions pending on the deferred action list. 3598 // The list is cleared and the _actionsDeferred variable is set 3599 // to false, even if one of the deferred actions fails. 3600 // This method should only be invoked in the event dispatch thread. 3601 // It is synchronized, so the integrity of the deferred actions list 3602 // is ensured, since modifications to that list occur only in other 3603 // synchronized methods. 3604 private synchronized void _executeDeferredActions() { 3605 try { 3606 for (Runnable action : _deferredActions) { 3607 action.run(); 3608 } 3609 } finally { 3610 _actionsDeferred = false; 3611 _deferredActions.clear(); 3612 } 3613 } 3614 3615 /* 3616 * Return the number as a String for use as a label on a 3617 * logarithmic axis. 3618 * Since this is a log plot, number passed in will not have too many 3619 * digits to cause problems. 3620 * If the number is an integer, then we print 1e<num>. 3621 * If the number is not an integer, then print only the fractional 3622 * components. 3623 */ 3624 private String _formatLogNum(double num, int numfracdigits) { 3625 String results; 3626 int exponent = (int) num; 3627 3628 // Determine the exponent, prepending 0 or -0 if necessary. 3629 if (exponent >= 0 && exponent < 10) { 3630 results = "0" + exponent; 3631 } else { 3632 if (exponent < 0 && exponent > -10) { 3633 results = "-0" + -exponent; 3634 } else { 3635 results = Integer.toString(exponent); 3636 } 3637 } 3638 3639 // Handle the mantissa. 3640 if (num >= 0.0) { 3641 if (num - (int) num < 0.001) { 3642 results = "1e" + results; 3643 } else { 3644 results = _formatNum(Math.pow(10.0, num - (int) num), 3645 numfracdigits); 3646 } 3647 } else { 3648 if (-num - (int) -num < 0.001) { 3649 results = "1e" + results; 3650 } else { 3651 results = _formatNum(Math.pow(10.0, num - (int) num) * 10, 3652 numfracdigits); 3653 } 3654 } 3655 3656 return results; 3657 } 3658 3659 /* 3660 * Return a string for displaying the specified number 3661 * using the specified number of digits after the decimal point. 3662 * NOTE: java.text.NumberFormat in Netscape 4.61 has a bug 3663 * where it fails to round numbers instead it truncates them. 3664 * As a result, we don't use java.text.NumberFormat, instead 3665 * We use the method from Ptplot1.3 3666 */ 3667 private String _formatNum(double num, int numfracdigits) { 3668 // When java.text.NumberFormat works under Netscape, 3669 // uncomment the next block of code and remove 3670 // the code after it. 3671 // Ptplot developers at UCB can access a test case at: 3672 // http://ptolemy.eecs.berkeley.edu/~ptII/ptIItree/ptolemy/plot/adm/trunc/trunc-jdk11.html 3673 // The plot will show two 0.7 values on the x axis if the bug 3674 // continues to exist. 3675 //if (_numberFormat == null) { 3676 // // Cache the number format so that we don't have to get 3677 // // info about local language etc. from the OS each time. 3678 // _numberFormat = NumberFormat.getInstance(); 3679 //} 3680 //_numberFormat.setMinimumFractionDigits(numfracdigits); 3681 //_numberFormat.setMaximumFractionDigits(numfracdigits); 3682 //return _numberFormat.format(num); 3683 // The section below is from Ptplot1.3 3684 // First, round the number. 3685 double fudge = 0.5; 3686 3687 if (num < 0.0) { 3688 fudge = -0.5; 3689 } 3690 3691 String numString = Double 3692 .toString(num + fudge * Math.pow(10.0, -numfracdigits)); 3693 3694 // Next, find the decimal point. 3695 int dpt = numString.lastIndexOf("."); 3696 StringBuffer result = new StringBuffer(); 3697 3698 if (dpt < 0) { 3699 // The number we are given is an integer. 3700 if (numfracdigits <= 0) { 3701 // The desired result is an integer. 3702 result.append(numString); 3703 return result.toString(); 3704 } 3705 3706 // Append a decimal point and some zeros. 3707 result.append("."); 3708 3709 for (int i = 0; i < numfracdigits; i++) { 3710 result.append("0"); 3711 } 3712 3713 return result.toString(); 3714 } else { 3715 // There are two cases. First, there may be enough digits. 3716 int shortby = numfracdigits - (numString.length() - dpt - 1); 3717 3718 if (shortby <= 0) { 3719 int numtocopy = dpt + numfracdigits + 1; 3720 3721 if (numfracdigits == 0) { 3722 // Avoid copying over a trailing decimal point. 3723 numtocopy -= 1; 3724 } 3725 3726 result.append(numString.substring(0, numtocopy)); 3727 return result.toString(); 3728 } else { 3729 result.append(numString); 3730 3731 for (int i = 0; i < shortby; i++) { 3732 result.append("0"); 3733 } 3734 3735 return result.toString(); 3736 } 3737 } 3738 } 3739 3740 /* 3741 * Determine what values to use for log axes. 3742 * Based on initGrid() from xgraph.c by David Harrison. 3743 */ 3744 private Vector _gridInit(double low, double step, boolean labeled, 3745 Vector oldgrid) { 3746 // How log axes work: 3747 // _gridInit() creates a vector with the values to use for the 3748 // log axes. For example, the vector might contain 3749 // {0.0 0.301 0.698}, which could correspond to 3750 // axis labels {1 1.2 1.5 10 12 15 100 120 150} 3751 // 3752 // _gridStep() gets the proper value. _gridInit is cycled through 3753 // for each integer log value. 3754 // 3755 // Bugs in log axes: 3756 // * Sometimes not enough grid lines are displayed because the 3757 // region is small. This bug is present in the oriignal xgraph 3758 // binary, which is the basis of this code. The problem is that 3759 // as ratio gets closer to 1.0, we need to add more and more 3760 // grid marks. 3761 Vector grid = new Vector(10); 3762 3763 //grid.addElement(Double.valueOf(0.0)); 3764 double ratio = Math.pow(10.0, step); 3765 int ngrid = 1; 3766 3767 if (labeled) { 3768 // Set up the number of grid lines that will be labeled 3769 if (ratio <= 3.5) { 3770 if (ratio > 2.0) { 3771 ngrid = 2; 3772 } else if (ratio > 1.26) { 3773 ngrid = 5; 3774 } else if (ratio > 1.125) { 3775 ngrid = 10; 3776 } else { 3777 ngrid = (int) Math.rint(1.0 / step); 3778 } 3779 } 3780 } else { 3781 // Set up the number of grid lines that will not be labeled 3782 if (ratio > 10.0) { 3783 ngrid = 1; 3784 } else if (ratio > 3.0) { 3785 ngrid = 2; 3786 } else if (ratio > 2.0) { 3787 ngrid = 5; 3788 } else if (ratio > 1.125) { 3789 ngrid = 10; 3790 } else { 3791 ngrid = 100; 3792 } 3793 3794 // Note: we should keep going here, but this increases the 3795 // size of the grid array and slows everything down. 3796 } 3797 3798 int oldgridi = 0; 3799 3800 for (int i = 0; i < ngrid; i++) { 3801 double gridval = i * 1.0 / ngrid * 10; 3802 double logval = _LOG10SCALE * Math.log(gridval); 3803 3804 if (logval == Double.NEGATIVE_INFINITY) { 3805 logval = 0.0; 3806 } 3807 3808 // If oldgrid is not null, then do not draw lines that 3809 // were already drawn in oldgrid. This is necessary 3810 // so we avoid obliterating the tick marks on the plot borders. 3811 if (oldgrid != null && oldgridi < oldgrid.size()) { 3812 // Cycle through the oldgrid until we find an element 3813 // that is equal to or greater than the element we are 3814 // trying to add. 3815 while (oldgridi < oldgrid.size() 3816 && ((Double) oldgrid.elementAt(oldgridi)) 3817 .doubleValue() < logval) { 3818 oldgridi++; 3819 } 3820 3821 if (oldgridi < oldgrid.size()) { 3822 // Using == on doubles is bad if the numbers are close, 3823 // but not exactly equal. 3824 if (Math.abs( 3825 ((Double) oldgrid.elementAt(oldgridi)).doubleValue() 3826 - logval) > 0.00001) { 3827 grid.addElement(Double.valueOf(logval)); 3828 } 3829 } else { 3830 grid.addElement(Double.valueOf(logval)); 3831 } 3832 } else { 3833 grid.addElement(Double.valueOf(logval)); 3834 } 3835 } 3836 3837 // _gridCurJuke and _gridBase are used in _gridStep(); 3838 _gridCurJuke = 0; 3839 3840 if (low == -0.0) { 3841 low = 0.0; 3842 } 3843 3844 _gridBase = Math.floor(low); 3845 3846 double x = low - _gridBase; 3847 3848 // Set gridCurJuke so that the value in grid is greater than 3849 // or equal to x. This sets us up to process the first point. 3850 for (_gridCurJuke = -1; _gridCurJuke + 1 < grid.size() 3851 && x >= ((Double) grid.elementAt(_gridCurJuke + 1)) 3852 .doubleValue(); _gridCurJuke++) { 3853 } 3854 3855 return grid; 3856 } 3857 3858 /* 3859 * Round pos up to the nearest value in the grid. 3860 */ 3861 private double _gridRoundUp(Vector grid, double pos) { 3862 double x = pos - Math.floor(pos); 3863 int i; 3864 3865 for (i = 0; i < grid.size() 3866 && x >= ((Double) grid.elementAt(i)).doubleValue(); i++) { 3867 } 3868 3869 if (i >= grid.size()) { 3870 return pos; 3871 } else { 3872 return Math.floor(pos) + ((Double) grid.elementAt(i)).doubleValue(); 3873 } 3874 } 3875 3876 /* 3877 * Used to find the next value for the axis label. 3878 * For non-log axes, we just return pos + step. 3879 * For log axes, we read the appropriate value in the grid Vector, 3880 * add it to _gridBase and return the sum. We also take care 3881 * to reset _gridCurJuke if necessary. 3882 * Note that for log axes, _gridInit() must be called before 3883 * calling _gridStep(). 3884 * Based on stepGrid() from xgraph.c by David Harrison. 3885 */ 3886 private double _gridStep(Vector grid, double pos, double step, 3887 boolean logflag) { 3888 if (logflag) { 3889 if (++_gridCurJuke >= grid.size()) { 3890 _gridCurJuke = 0; 3891 _gridBase += Math.ceil(step); 3892 } 3893 3894 if (_gridCurJuke >= grid.size()) { 3895 return pos + step; 3896 } 3897 3898 return _gridBase 3899 + ((Double) grid.elementAt(_gridCurJuke)).doubleValue(); 3900 } else { 3901 return pos + step; 3902 } 3903 } 3904 3905 /* 3906 * Measure the various fonts. You only want to call this once. 3907 */ 3908 private void _measureFonts() { 3909 // We only measure the fonts once, and we do it from addNotify(). 3910 // For maintainability, keep the fonts alphabetized here. 3911 if (_captionFont == null) { 3912 _captionFont = new Font("Helvetica", Font.PLAIN, 12); 3913 } 3914 3915 if (_labelFont == null) { 3916 _labelFont = new Font("Helvetica", Font.PLAIN, 12); 3917 } 3918 3919 if (_superscriptFont == null) { 3920 _superscriptFont = new Font("Helvetica", Font.PLAIN, 9); 3921 } 3922 3923 if (_titleFont == null) { 3924 _titleFont = new Font("Helvetica", Font.BOLD, 14); 3925 } 3926 3927 _captionFontMetrics = getFontMetrics(_captionFont); 3928 _labelFontMetrics = getFontMetrics(_labelFont); 3929 _superscriptFontMetrics = getFontMetrics(_superscriptFont); 3930 _titleFontMetrics = getFontMetrics(_titleFont); 3931 } 3932 3933 /* 3934 * Return the number of fractional digits required to display the 3935 * given number. No number larger than 15 is returned (if 3936 * more than 15 digits are required, 15 is returned). 3937 */ 3938 private int _numFracDigits(double num) { 3939 int numdigits = 0; 3940 3941 while (numdigits <= 15 && num != Math.floor(num)) { 3942 num *= 10.0; 3943 numdigits += 1; 3944 } 3945 3946 return numdigits; 3947 } 3948 3949 /* 3950 * Return the number of integer digits required to display the 3951 * given number. No number larger than 15 is returned (if 3952 * more than 15 digits are required, 15 is returned). 3953 */ 3954 private int _numIntDigits(double num) { 3955 int numdigits = 0; 3956 3957 while (numdigits <= 15 && (int) num != 0.0) { 3958 num /= 10.0; 3959 numdigits += 1; 3960 } 3961 3962 return numdigits; 3963 } 3964 3965 /* 3966 * Parse a string of the form: "word num, word num, word num, ..." 3967 * where the word must be enclosed in quotes if it contains spaces, 3968 * and the number is interpreted as a floating point number. Ignore 3969 * any incorrectly formatted fields. I <i>xtick</i> is true, then 3970 * interpret the parsed string to specify the tick labels on the x axis. 3971 * Otherwise, do the y axis. 3972 */ 3973 private void _parsePairs(String line, boolean xtick) { 3974 // Clear current ticks first. 3975 if (xtick) { 3976 _xticks = null; 3977 _xticklabels = null; 3978 } else { 3979 _yticks = null; 3980 _yticklabels = null; 3981 } 3982 3983 int start = 0; 3984 boolean cont = true; 3985 3986 while (cont) { 3987 int comma = line.indexOf(",", start); 3988 String pair = null; 3989 3990 if (comma > start) { 3991 pair = line.substring(start, comma).trim(); 3992 } else { 3993 pair = line.substring(start).trim(); 3994 cont = false; 3995 } 3996 3997 int close = -1; 3998 int open = 0; 3999 4000 if (pair.startsWith("\"")) { 4001 close = pair.indexOf("\"", 1); 4002 open = 1; 4003 } else { 4004 close = pair.indexOf(" "); 4005 } 4006 4007 if (close > 0) { 4008 String label = pair.substring(open, close); 4009 String index = pair.substring(close + 1).trim(); 4010 4011 try { 4012 double idx = Double.valueOf(index).doubleValue(); 4013 4014 if (xtick) { 4015 addXTick(label, idx); 4016 } else { 4017 addYTick(label, idx); 4018 } 4019 } catch (NumberFormatException e) { 4020 System.err.println("Warning from PlotBox: " 4021 + "Unable to parse ticks: " + e.getMessage()); 4022 4023 // ignore if format is bogus. 4024 } 4025 } 4026 4027 start = comma + 1; 4028 comma = line.indexOf(",", start); 4029 } 4030 } 4031 4032 /** Return a default set of rendering hints for image export, which 4033 * specifies the use of anti-aliasing. 4034 */ 4035 private RenderingHints _defaultImageRenderingHints() { 4036 RenderingHints hints = new RenderingHints(null); 4037 hints.put(RenderingHints.KEY_ANTIALIASING, 4038 RenderingHints.VALUE_ANTIALIAS_ON); 4039 return hints; 4040 } 4041 4042 /* 4043 * Given a number, round up to the nearest power of ten 4044 * times 1, 2, or 5. 4045 * 4046 * Note: The argument must be strictly positive. 4047 */ 4048 private double _roundUp(double val) { 4049 int exponent = (int) Math.floor(Math.log(val) * _LOG10SCALE); 4050 val *= Math.pow(10, -exponent); 4051 4052 if (val > 5.0) { 4053 val = 10.0; 4054 } else if (val > 2.0) { 4055 val = 5.0; 4056 } else if (val > 1.0) { 4057 val = 2.0; 4058 } 4059 4060 val *= Math.pow(10, exponent); 4061 return val; 4062 } 4063 4064 /* 4065 * Internal implementation of setXRange, so that it can be called when 4066 * autoranging. 4067 */ 4068 private void _setXRange(double min, double max) { 4069 // We check to see if the original range has been given here 4070 // because if we check in setXRange(), then we will not catch 4071 // the case where we have a simple plot file that consists of just 4072 // data points 4073 // 4074 // 1. Create a file that consists of two data points 4075 // 1 1 4076 // 2 3 4077 // 2. Start up plot on it 4078 // $PTII/bin/ptplot foo.plt 4079 // 3. Zoom in 4080 // 4. Hit reset axes 4081 // 5. The bug is that the axes do not reset to the initial settings 4082 // Changing the range means we have to replot. 4083 _plotImage = null; 4084 4085 if (!_originalXRangeGiven) { 4086 _originalXlow = min; 4087 _originalXhigh = max; 4088 _originalXRangeGiven = true; 4089 } 4090 4091 // If values are invalid, try for something reasonable. 4092 if (min > max) { 4093 min = -1.0; 4094 max = 1.0; 4095 } else if (min == max) { 4096 min -= 1.0; 4097 max += 1.0; 4098 } 4099 4100 //if (_xRangeGiven) { 4101 // The user specified the range, so don't pad. 4102 // _xMin = min; 4103 // _xMax = max; 4104 //} else { 4105 // Pad slightly so that we don't plot points on the axes. 4106 _xMin = min - (max - min) * _padding; 4107 _xMax = max + (max - min) * _padding; 4108 4109 //} 4110 // Find the exponent. 4111 double largest = Math.max(Math.abs(_xMin), Math.abs(_xMax)); 4112 _xExp = (int) Math.floor(Math.log(largest) * _LOG10SCALE); 4113 4114 // Use the exponent only if it's larger than 1 in magnitude. 4115 if (_xExp > 1 || _xExp < -1) { 4116 double xs = 1.0 / Math.pow(10.0, _xExp); 4117 _xtickMin = _xMin * xs; 4118 _xtickMax = _xMax * xs; 4119 } else { 4120 _xtickMin = _xMin; 4121 _xtickMax = _xMax; 4122 _xExp = 0; 4123 } 4124 } 4125 4126 /* 4127 * Internal implementation of setYRange, so that it can be called when 4128 * autoranging. 4129 */ 4130 private void _setYRange(double min, double max) { 4131 // See comment in _setXRange() about why this is necessary. 4132 // Changing the range means we have to replot. 4133 _plotImage = null; 4134 4135 if (!_originalYRangeGiven) { 4136 _originalYlow = min; 4137 _originalYhigh = max; 4138 _originalYRangeGiven = true; 4139 } 4140 4141 // If values are invalid, try for something reasonable. 4142 if (min > max) { 4143 min = -1.0; 4144 max = 1.0; 4145 } else if (min == max) { 4146 min -= 0.1; 4147 max += 0.1; 4148 } 4149 4150 //if (_yRangeGiven) { 4151 // The user specified the range, so don't pad. 4152 // _yMin = min; 4153 // _yMax = max; 4154 //} else { 4155 // Pad slightly so that we don't plot points on the axes. 4156 _yMin = min - (max - min) * _padding; 4157 _yMax = max + (max - min) * _padding; 4158 4159 //} 4160 // Find the exponent. 4161 double largest = Math.max(Math.abs(_yMin), Math.abs(_yMax)); 4162 _yExp = (int) Math.floor(Math.log(largest) * _LOG10SCALE); 4163 4164 // Use the exponent only if it's larger than 1 in magnitude. 4165 if (_yExp > 1 || _yExp < -1) { 4166 double ys = 1.0 / Math.pow(10.0, _yExp); 4167 _ytickMin = _yMin * ys; 4168 _ytickMax = _yMax * ys; 4169 } else { 4170 _ytickMin = _yMin; 4171 _ytickMax = _yMax; 4172 _yExp = 0; 4173 } 4174 } 4175 4176 /* 4177 * Zoom in or out based on the box that has been drawn. 4178 * The argument gives the lower right corner of the box. 4179 * This method is not synchronized because it is called within 4180 * the UI thread, and making it synchronized causes a deadlock. 4181 * @param x The final x position. 4182 * @param y The final y position. 4183 */ 4184 void _zoom(int x, int y) { 4185 // FIXME: This is friendly because Netscape 4.0.3 cannot access it if 4186 // it is private! 4187 // NOTE: Due to a bug in JDK 1.1.7B, the BUTTON1_MASK does 4188 // not work on mouse drags, thus we have to use this variable 4189 // to determine whether we are actually zooming. It is used only 4190 // in _zoomBox, since calling this method is properly masked. 4191 _zooming = false; 4192 4193 Graphics graphics = getGraphics(); 4194 4195 // Ignore if there is no graphics object to draw on. 4196 if (graphics == null) { 4197 return; 4198 } 4199 4200 if (_zoomin == true && _drawn == true) { 4201 if (_zoomxn != -1 || _zoomyn != -1) { 4202 // erase previous rectangle. 4203 int minx = Math.min(_zoomx, _zoomxn); 4204 int maxx = Math.max(_zoomx, _zoomxn); 4205 int miny = Math.min(_zoomy, _zoomyn); 4206 int maxy = Math.max(_zoomy, _zoomyn); 4207 graphics.setXORMode(_boxColor); 4208 graphics.drawRect(minx, miny, maxx - minx, maxy - miny); 4209 graphics.setPaintMode(); 4210 4211 // constrain to be in range 4212 if (y > _lry) { 4213 y = _lry; 4214 } 4215 4216 if (y < _uly) { 4217 y = _uly; 4218 } 4219 4220 if (x > _lrx) { 4221 x = _lrx; 4222 } 4223 4224 if (x < _ulx) { 4225 x = _ulx; 4226 } 4227 4228 // NOTE: ignore if total drag less than 5 pixels. 4229 if (Math.abs(_zoomx - x) > 5 && Math.abs(_zoomy - y) > 5) { 4230 double a = _xMin + (_zoomx - _ulx) / _xscale; 4231 double b = _xMin + (x - _ulx) / _xscale; 4232 4233 // NOTE: It used to be that it wasproblematic to set 4234 // the X range here because it conflicted with the wrap 4235 // mechanism. But now the wrap mechanism saves the state 4236 // of the X range when the setWrap() method is called, 4237 // so this is safe. 4238 // EAL 6/12/00. 4239 if (a < b) { 4240 setXRange(a, b); 4241 } else { 4242 setXRange(b, a); 4243 } 4244 4245 a = _yMax - (_zoomy - _uly) / _yscale; 4246 b = _yMax - (y - _uly) / _yscale; 4247 4248 if (a < b) { 4249 setYRange(a, b); 4250 } else { 4251 setYRange(b, a); 4252 } 4253 } 4254 4255 repaint(); 4256 } 4257 } else if (_zoomout == true && _drawn == true) { 4258 // Erase previous rectangle. 4259 graphics.setXORMode(_boxColor); 4260 4261 int x_diff = Math.abs(_zoomx - _zoomxn); 4262 int y_diff = Math.abs(_zoomy - _zoomyn); 4263 graphics.drawRect(_zoomx - 15 - x_diff, _zoomy - 15 - y_diff, 4264 30 + x_diff * 2, 30 + y_diff * 2); 4265 graphics.setPaintMode(); 4266 4267 // Calculate zoom factor. 4268 double a = Math.abs(_zoomx - x) / 30.0; 4269 double b = Math.abs(_zoomy - y) / 30.0; 4270 double newx1 = _xMax + (_xMax - _xMin) * a; 4271 double newx2 = _xMin - (_xMax - _xMin) * a; 4272 4273 // NOTE: To limit zooming out to the fill area, uncomment this... 4274 // if (newx1 > _xTop) newx1 = _xTop; 4275 // if (newx2 < _xBottom) newx2 = _xBottom; 4276 double newy1 = _yMax + (_yMax - _yMin) * b; 4277 double newy2 = _yMin - (_yMax - _yMin) * b; 4278 4279 // NOTE: To limit zooming out to the fill area, uncomment this... 4280 // if (newy1 > _yTop) newy1 = _yTop; 4281 // if (newy2 < _yBottom) newy2 = _yBottom; 4282 zoom(newx2, newy2, newx1, newy1); 4283 repaint(); 4284 } else if (_drawn == false) { 4285 repaint(); 4286 } 4287 4288 _drawn = false; 4289 _zoomin = _zoomout = false; 4290 _zoomxn = _zoomyn = _zoomx = _zoomy = -1; 4291 } 4292 4293 /** 4294 * Zoom to that equal interval widths are on x and y axis. 4295 * For example, a miss-scaled circle will look circular afterwords. 4296 * @author Dirk Bueche 4297 */ 4298 public synchronized void zoomEqual() { 4299 4300 double temp = _padding; 4301 _padding = 0; 4302 double rx = (1.0 * _xMax - _xMin) / (_lrx - _ulx); // delta x per pixel 4303 double ry = (1.0 * _yMax - _yMin) / (_lry - _uly); // delta y per pixel 4304 4305 if (ry > rx) { 4306 _xMax = _xMin + ry / rx * (_xMax - _xMin); 4307 } 4308 if (rx > ry) { 4309 _yMax = _yMin + rx / ry * (_yMax - _yMin); 4310 } 4311 zoom(_xMin, _yMin, _xMax, _yMax); 4312 _padding = temp; 4313 } 4314 4315 /* 4316 * Draw a box for an interactive zoom box. The starting point (the 4317 * upper left corner of the box) is taken 4318 * to be that specified by the startZoom() method. The argument gives 4319 * the lower right corner of the box. If a previous box 4320 * has been drawn, erase it first. 4321 * This method is not synchronized because it is called within 4322 * the UI thread, and making it synchronized causes a deadlock. 4323 * @param x The x position. 4324 * @param y The y position. 4325 */ 4326 void _zoomBox(int x, int y) { 4327 // FIXME: This is friendly because Netscape 4.0.3 cannot access it if 4328 // it is private! 4329 // NOTE: Due to a bug in JDK 1.1.7B, the BUTTON1_MASK does 4330 // not work on mouse drags, thus we have to use this variable 4331 // to determine whether we are actually zooming. 4332 if (!_zooming) { 4333 return; 4334 } 4335 4336 Graphics graphics = getGraphics(); 4337 4338 // Ignore if there is no graphics object to draw on. 4339 if (graphics == null) { 4340 return; 4341 } 4342 4343 // Bound the rectangle so it doesn't go outside the box. 4344 if (y > _lry) { 4345 y = _lry; 4346 } 4347 4348 if (y < _uly) { 4349 y = _uly; 4350 } 4351 4352 if (x > _lrx) { 4353 x = _lrx; 4354 } 4355 4356 if (x < _ulx) { 4357 x = _ulx; 4358 } 4359 4360 // erase previous rectangle, if there was one. 4361 if (_zoomx != -1 || _zoomy != -1) { 4362 // Ability to zoom out added by William Wu. 4363 // If we are not already zooming, figure out whether we 4364 // are zooming in or out. 4365 if (_zoomin == false && _zoomout == false) { 4366 if (y < _zoomy) { 4367 _zoomout = true; 4368 4369 // Draw reference box. 4370 graphics.setXORMode(_boxColor); 4371 graphics.drawRect(_zoomx - 15, _zoomy - 15, 30, 30); 4372 } else if (y > _zoomy) { 4373 _zoomin = true; 4374 } 4375 } 4376 4377 if (_zoomin == true) { 4378 // Erase the previous box if necessary. 4379 if ((_zoomxn != -1 || _zoomyn != -1) && _drawn == true) { 4380 int minx = Math.min(_zoomx, _zoomxn); 4381 int maxx = Math.max(_zoomx, _zoomxn); 4382 int miny = Math.min(_zoomy, _zoomyn); 4383 int maxy = Math.max(_zoomy, _zoomyn); 4384 graphics.setXORMode(_boxColor); 4385 graphics.drawRect(minx, miny, maxx - minx, maxy - miny); 4386 } 4387 4388 // Draw a new box if necessary. 4389 if (y > _zoomy) { 4390 _zoomxn = x; 4391 _zoomyn = y; 4392 4393 int minx = Math.min(_zoomx, _zoomxn); 4394 int maxx = Math.max(_zoomx, _zoomxn); 4395 int miny = Math.min(_zoomy, _zoomyn); 4396 int maxy = Math.max(_zoomy, _zoomyn); 4397 graphics.setXORMode(_boxColor); 4398 graphics.drawRect(minx, miny, maxx - minx, maxy - miny); 4399 _drawn = true; 4400 return; 4401 } else { 4402 _drawn = false; 4403 } 4404 } else if (_zoomout == true) { 4405 // Erase previous box if necessary. 4406 if ((_zoomxn != -1 || _zoomyn != -1) && _drawn == true) { 4407 int x_diff = Math.abs(_zoomx - _zoomxn); 4408 int y_diff = Math.abs(_zoomy - _zoomyn); 4409 graphics.setXORMode(_boxColor); 4410 graphics.drawRect(_zoomx - 15 - x_diff, 4411 _zoomy - 15 - y_diff, 30 + x_diff * 2, 4412 30 + y_diff * 2); 4413 } 4414 4415 if (y < _zoomy) { 4416 _zoomxn = x; 4417 _zoomyn = y; 4418 4419 int x_diff = Math.abs(_zoomx - _zoomxn); 4420 int y_diff = Math.abs(_zoomy - _zoomyn); 4421 graphics.setXORMode(_boxColor); 4422 graphics.drawRect(_zoomx - 15 - x_diff, 4423 _zoomy - 15 - y_diff, 30 + x_diff * 2, 4424 30 + y_diff * 2); 4425 _drawn = true; 4426 return; 4427 } else { 4428 _drawn = false; 4429 } 4430 } 4431 } 4432 4433 graphics.setPaintMode(); 4434 } 4435 4436 /* 4437 * Set the starting point for an interactive zoom box (the upper left 4438 * corner). 4439 * This method is not synchronized because it is called within 4440 * the UI thread, and making it synchronized causes a deadlock. 4441 * @param x The x position. 4442 * @param y The y position. 4443 */ 4444 void _zoomStart(int x, int y) { 4445 // FIXME: This is friendly because Netscape 4.0.3 cannot access it if 4446 // it is private! 4447 // constrain to be in range 4448 if (y > _lry) { 4449 y = _lry; 4450 } 4451 4452 if (y < _uly) { 4453 y = _uly; 4454 } 4455 4456 if (x > _lrx) { 4457 x = _lrx; 4458 } 4459 4460 if (x < _ulx) { 4461 x = _ulx; 4462 } 4463 4464 _zoomx = x; 4465 _zoomy = y; 4466 _zooming = true; 4467 } 4468 4469 /////////////////////////////////////////////////////////////////// 4470 //// private variables //// 4471 4472 /** Indicator of whether actions are deferred. */ 4473 private volatile boolean _actionsDeferred = false; 4474 4475 // True when repainting happens by a timer thread. 4476 private boolean _automaticRescale = false; 4477 4478 /** List of deferred actions. */ 4479 private LinkedList<Runnable> _deferredActions = new LinkedList<Runnable>(); 4480 4481 /** The file to be opened. */ 4482 private String _filespec = null; 4483 4484 // Call setXORMode with a hardwired color because 4485 // _background does not work in an application, 4486 // and _foreground does not work in an applet. 4487 // NOTE: For some reason, this comes out blue, which is fine... 4488 private static final Color _boxColor = Color.orange; 4489 4490 /** The range of the plot as labeled 4491 * (multiply by 10^exp for actual range. 4492 */ 4493 private double _ytickMax = 0.0; 4494 4495 /** The range of the plot as labeled 4496 * (multiply by 10^exp for actual range. 4497 */ 4498 private double _ytickMin = 0.0; 4499 4500 /** The range of the plot as labeled 4501 * (multiply by 10^exp for actual range. 4502 */ 4503 private double _xtickMax = 0.0; 4504 4505 /** The range of the plot as labeled 4506 * (multiply by 10^exp for actual range. 4507 */ 4508 private double _xtickMin = 0.0; 4509 4510 /** The power of ten by which the range numbers should 4511 * be multiplied. 4512 */ 4513 private int _yExp = 0; 4514 4515 /** The power of ten by which the range numbers should 4516 * be multiplied. 4517 */ 4518 private int _xExp = 0; 4519 4520 /** Scaling used in making tick marks. */ 4521 private double _ytickscale = 0.0; 4522 4523 /** Scaling used in making tick marks. */ 4524 private double _xtickscale = 0.0; 4525 4526 /** Caption font information. */ 4527 private Font _captionFont = null; 4528 4529 /** Font information. */ 4530 private Font _labelFont = null; 4531 4532 /** Font information. */ 4533 private Font _superscriptFont = null; 4534 4535 /** Font information. */ 4536 private Font _titleFont = null; 4537 4538 /** Caption font metric information. */ 4539 private FontMetrics _captionFontMetrics = null; 4540 4541 /** FontMetric information. */ 4542 private FontMetrics _labelFontMetrics = null; 4543 4544 /** FontMetric information. */ 4545 private FontMetrics _superscriptFontMetrics = null; 4546 4547 /** FontMetric information. */ 4548 private FontMetrics _titleFontMetrics = null; 4549 4550 // Number format cache used by _formatNum. 4551 // See the comment in _formatNum for more information. 4552 // private transient NumberFormat _numberFormat = null; 4553 // Used for log axes. Index into vector of axis labels. 4554 private transient int _gridCurJuke = 0; 4555 4556 // Used for log axes. Base of the grid. 4557 private transient double _gridBase = 0.0; 4558 4559 // An array of strings for reporting errors. 4560 private transient String[] _errorMsg; 4561 4562 /** The title and label strings. */ 4563 private String _xlabel; 4564 4565 /** The title and label strings. */ 4566 private String _ylabel; 4567 4568 /** The title and label strings. */ 4569 private String _title; 4570 4571 /** Caption information. */ 4572 private Vector _captionStrings = new Vector(); 4573 4574 /** Legend information. */ 4575 private Vector _legendStrings = new Vector(); 4576 4577 /** Legend information. */ 4578 private Vector _legendDatasets = new Vector(); 4579 4580 /** If XTicks or YTicks are given/ */ 4581 private Vector _xticks = null; 4582 4583 /** If XTicks or YTicks are given/ */ 4584 private Vector _xticklabels = null; 4585 4586 /** If XTicks or YTicks are given/ */ 4587 private Vector _yticks = null; 4588 4589 /** If XTicks or YTicks are given/ */ 4590 private Vector _yticklabels = null; 4591 4592 // A button for filling the plot 4593 private transient JButton _fillButton = null; 4594 4595 // Dirk: a button for equal axis scaling 4596 private transient JButton _eqAxButton = null; 4597 4598 // A button for formatting the plot 4599 private transient JButton _formatButton = null; 4600 4601 // Indicator of whether X and Y range has been first specified. 4602 boolean _originalXRangeGiven = false; 4603 4604 // Indicator of whether X and Y range has been first specified. 4605 boolean _originalYRangeGiven = false; 4606 4607 // First values specified to setXRange() and setYRange(). 4608 double _originalXlow = 0.0; 4609 4610 // First values specified to setXRange() and setYRange(). 4611 double _originalXhigh = 0.0; 4612 4613 // First values specified to setXRange() and setYRange(). 4614 double _originalYlow = 0.0; 4615 4616 // First values specified to setXRange() and setYRange(). 4617 double _originalYhigh = 0.0; 4618 4619 // A button for printing the plot 4620 private transient JButton _printButton = null; 4621 4622 // A button for filling the plot 4623 private transient JButton _resetButton = null; 4624 4625 // Dirk: Variables keeping track of the interactive moving. 4626 // Initialize to impossible values. 4627 private transient int _movex = -1; 4628 private transient int _movey = -1; 4629 4630 // True when repainting should be performed by a timed thread. 4631 private boolean _timedRepaint = false; 4632 4633 // The timer task that does the repainting. 4634 // _timerTask should be volatile because FindBugs says: 4635 // "Incorrect lazy initialization of static field" 4636 static private volatile TimedRepaint _timerTask = null; 4637 4638 // Variables keeping track of the interactive zoom box. 4639 // Initialize to impossible values. 4640 private transient int _zoomx = -1; 4641 4642 private transient int _zoomy = -1; 4643 4644 private transient int _zoomxn = -1; 4645 4646 private transient int _zoomyn = -1; 4647 4648 // Control whether we are zooming in or out. 4649 private transient boolean _zoomin = false; 4650 4651 private transient boolean _zoomout = false; 4652 4653 private transient boolean _drawn = false; 4654 4655 private transient boolean _zooming = false; 4656 4657 private transient boolean _moving = false; 4658 4659 // NOTE: It is unfortunate to have to include the DTD here, but there 4660 // seems to be no other way to ensure that the generated data exactly 4661 // matches the DTD. 4662 // private static final String _DTD = 4663 // "<!-- PlotML DTD, created by Edward A. Lee.\n" 4664 // + " See http://ptolemy.eecs.berkeley.edu/java/ptplot -->\n" 4665 // + "<!ELEMENT plot (barGraph | bin | dataset | default | noColor | \n" 4666 // + " noGrid | title | wrap | xLabel | xLog | xRange | xTicks | yLabel | \n" 4667 // + " yLog | yRange | yTicks)*>\n" 4668 // + " <!ELEMENT barGraph EMPTY>\n" 4669 // + " <!ATTLIST barGraph width CDATA #IMPLIED>\n" 4670 // + " <!ATTLIST barGraph offset CDATA #IMPLIED>\n" 4671 // + " <!ELEMENT bin EMPTY>\n" 4672 // + " <!ATTLIST bin width CDATA #IMPLIED>\n" 4673 // + " <!ATTLIST bin offset CDATA #IMPLIED>\n" 4674 // + " <!ELEMENT dataset (m | move | p | point)*>\n" 4675 // + " <!ATTLIST dataset connected (yes | no) #IMPLIED>\n" 4676 // + " <!ATTLIST dataset marks (none | dots | points | various) #IMPLIED>\n" 4677 // + " <!ATTLIST dataset name CDATA #IMPLIED>\n" 4678 // + " <!ATTLIST dataset stems (yes | no) #IMPLIED>\n" 4679 // + " <!ELEMENT default EMPTY>\n" 4680 // + " <!ATTLIST default connected (yes | no) \"yes\">\n" 4681 // + " <!ATTLIST default marks (none | dots | points | various) \"none\">\n" 4682 // + " <!ATTLIST default stems (yes | no) \"no\">\n" 4683 // + " <!ELEMENT noColor EMPTY>\n" 4684 // + " <!ELEMENT noGrid EMPTY>\n" 4685 // + " <!ELEMENT title (#PCDATA)>\n" 4686 // + " <!ELEMENT wrap EMPTY>\n" 4687 // + " <!ELEMENT xLabel (#PCDATA)>\n" 4688 // + " <!ELEMENT xLog EMPTY>\n" 4689 // + " <!ELEMENT xRange EMPTY>\n" 4690 // + " <!ATTLIST xRange min CDATA #REQUIRED>\n" 4691 // + " <!ATTLIST xRange max CDATA #REQUIRED>\n" 4692 // + " <!ELEMENT xTicks (tick)+>\n" 4693 // + " <!ELEMENT yLabel (#PCDATA)>\n" 4694 // + " <!ELEMENT yLog EMPTY>\n" 4695 // + " <!ELEMENT yRange EMPTY>\n" 4696 // + " <!ATTLIST yRange min CDATA #REQUIRED>\n" 4697 // + " <!ATTLIST yRange max CDATA #REQUIRED>\n" 4698 // + " <!ELEMENT yTicks (tick)+>\n" 4699 // + " <!ELEMENT tick EMPTY>\n" 4700 // + " <!ATTLIST tick label CDATA #REQUIRED>\n" 4701 // + " <!ATTLIST tick position CDATA #REQUIRED>\n" 4702 // + " <!ELEMENT m EMPTY>\n" 4703 // + " <!ATTLIST m x CDATA #IMPLIED>\n" 4704 // + " <!ATTLIST m x CDATA #REQUIRED>\n" 4705 // + " <!ATTLIST m lowErrorBar CDATA #IMPLIED>\n" 4706 // + " <!ATTLIST m highErrorBar CDATA #IMPLIED>\n" 4707 // + " <!ELEMENT move EMPTY>\n" 4708 // + " <!ATTLIST move x CDATA #IMPLIED>\n" 4709 // + " <!ATTLIST move x CDATA #REQUIRED>\n" 4710 // + " <!ATTLIST move lowErrorBar CDATA #IMPLIED>\n" 4711 // + " <!ATTLIST move highErrorBar CDATA #IMPLIED>\n" 4712 // + " <!ELEMENT p EMPTY>\n" 4713 // + " <!ATTLIST p x CDATA #IMPLIED>\n" 4714 // + " <!ATTLIST p x CDATA #REQUIRED>\n" 4715 // + " <!ATTLIST p lowErrorBar CDATA #IMPLIED>\n" 4716 // + " <!ATTLIST p highErrorBar CDATA #IMPLIED>\n" 4717 // + " <!ELEMENT point EMPTY>\n" 4718 // + " <!ATTLIST point x CDATA #IMPLIED>\n" 4719 // + " <!ATTLIST point x CDATA #REQUIRED>\n" 4720 // + " <!ATTLIST point lowErrorBar CDATA #IMPLIED>\n" 4721 // + " <!ATTLIST point highErrorBar CDATA #IMPLIED>"; 4722 /////////////////////////////////////////////////////////////////// 4723 //// inner classes //// 4724 class ButtonListener implements ActionListener { 4725 @Override 4726 public void actionPerformed(ActionEvent event) { 4727 if (event.getSource() == _fillButton) { 4728 fillPlot(); 4729 } else if (event.getSource() == _eqAxButton) { // Dirk: equal axis zooming 4730 zoomEqual(); 4731 } else if (event.getSource() == _printButton) { 4732 // If you are using $PTII/bin/vergil, under bash, 4733 // set this property: 4734 // export JAVAFLAGS=-Dptolemy.ptII.print.platform=CrossPlatform 4735 // and then run $PTII/bin/vergil 4736 String printPlatformProperty = ""; 4737 4738 try { 4739 printPlatformProperty = StringUtilities 4740 .getProperty("ptolemy.ptII.print.platform"); 4741 } catch (SecurityException ex) { 4742 if (!_printedSecurityExceptionMessage) { 4743 _printedSecurityExceptionMessage = true; 4744 System.out.println("Warning: Failed to get the " 4745 + "ptolemy.ptII.print.platform property " 4746 + "(-sandbox always causes this)"); 4747 } 4748 } 4749 if (printPlatformProperty.equals("CrossPlatform") || UIManager 4750 .getLookAndFeel().getName().startsWith("Mac OS")) { 4751 _printCrossPlatform(); 4752 } else { 4753 _printNative(); 4754 } 4755 } else if (event.getSource() == _resetButton) { 4756 resetAxes(); 4757 } else if (event.getSource() == _formatButton) { 4758 PlotFormatter fmt = new PlotFormatter(PlotBox.this); 4759 fmt.openModal(); 4760 } 4761 } 4762 4763 private void _printCrossPlatform() { 4764 // FIXME: Code duplication with PlotFrame and Top. 4765 // See PlotFrame for notes. 4766 PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet(); 4767 PrinterJob job = PrinterJob.getPrinterJob(); 4768 4769 // No need to call pageDialog, printDialog has that same tab 4770 // rbeyer@LPL.Arizona.EDU: Get the Page Format and use it. 4771 //PageFormat format = job.pageDialog(aset); 4772 //job.setPrintable(PlotBox.this, format); 4773 4774 job.setPrintable(PlotBox.this); 4775 4776 if (job.printDialog(aset)) { 4777 try { 4778 job.print(aset); 4779 } catch (Exception ex) { 4780 Component ancestor = getTopLevelAncestor(); 4781 JOptionPane.showMessageDialog(ancestor, 4782 "Printing failed:\n" + ex.toString(), "Print Error", 4783 JOptionPane.WARNING_MESSAGE); 4784 } 4785 } 4786 } 4787 4788 private void _printNative() { 4789 // FIXME: Code duplication with PlotFrame and Top. 4790 // See PlotFrame for notes. 4791 4792 // Native printing used not honor the user's 4793 // choice of portrait vs. landscape. 4794 4795 PrinterJob job = PrinterJob.getPrinterJob(); 4796 PageFormat pageFormat = job.pageDialog(job.defaultPage()); 4797 job.setPrintable(PlotBox.this, pageFormat); 4798 4799 if (job.printDialog()) { 4800 try { 4801 // job.print() eventually 4802 // calls PlotBox.print(Graphics, PageFormat) 4803 job.print(); 4804 } catch (Exception ex) { 4805 Component ancestor = getTopLevelAncestor(); 4806 JOptionPane.showMessageDialog(ancestor, 4807 "Printing failed:\n" + ex.toString(), "Print Error", 4808 JOptionPane.WARNING_MESSAGE); 4809 } 4810 } 4811 } 4812 } 4813 4814 /** Handle mouse pressed events to provide zoom functionality. */ 4815 public class ZoomListener implements MouseListener { 4816 /** Request the focus. 4817 * @param event The event, ignored by this method. 4818 */ 4819 @Override 4820 public void mouseClicked(MouseEvent event) { 4821 requestFocus(); 4822 } 4823 4824 /** Ignored. 4825 * @param event The event, ignored by this method. 4826 */ 4827 @Override 4828 public void mouseEntered(MouseEvent event) { 4829 } 4830 4831 /** Ignored. 4832 * @param event The event, ignored by this method. 4833 */ 4834 @Override 4835 public void mouseExited(MouseEvent event) { 4836 } 4837 4838 /** Handle mouse button 1 events. See the class comment for details. 4839 * @param event The event. 4840 */ 4841 @Override 4842 public void mousePressed(MouseEvent event) { 4843 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4072703 4844 // BUTTON1_MASK still not set for MOUSE_PRESSED events 4845 // suggests: 4846 // Workaround 4847 // Assume that a press event with no modifiers must be button 1. 4848 // This has the serious drawback that it is impossible to be sure 4849 // that button 1 hasn't been pressed along with one of the other 4850 // buttons. 4851 // This problem affects Netscape 4.61 under Digital Unix and 4852 // 4.51 under Solaris 4853 // 4854 // Mac OS X 10.5: we want BUTTON1_MASK set and BUTTON3_MASK not set 4855 // so that when we edit a dataset we don't get the zoom box 4856 // https://chess.eecs.berkeley.edu/bugzilla/show_bug.cgi?id=300 4857 if ((event.getModifiers() & InputEvent.BUTTON1_MASK) != 0 4858 && (event.getModifiers() & InputEvent.BUTTON3_MASK) == 0 4859 || event.getModifiers() == 0) { 4860 PlotBox.this._zoomStart(event.getX(), event.getY()); 4861 } 4862 // Want to convert from mouse presses to data points? 4863 // Comment out the if clause above and uncomment this println: 4864 //System.out.println("PlotBox.mousePressed(): (" 4865 // + event.getX() + ", " + event.getY() 4866 // + ") = (" 4867 // + (_xMin + ((event.getX() - _ulx) / _xscale)) 4868 // + ", " 4869 // + (_yMax - ((event.getY() - _uly) / _yscale)) 4870 // + ")"); 4871 } 4872 4873 /** Handle mouse button 1 events. See the class comment for details. 4874 * @param event The event. 4875 */ 4876 @Override 4877 public void mouseReleased(MouseEvent event) { 4878 if ((event.getModifiers() & InputEvent.BUTTON1_MASK) != 0 4879 && (event.getModifiers() & InputEvent.BUTTON3_MASK) == 0 4880 || event.getModifiers() == 0) { 4881 PlotBox.this._zoom(event.getX(), event.getY()); 4882 } 4883 } 4884 } 4885 4886 /** Move the items in the plot. */ 4887 public class MoveListener implements MouseListener { 4888 // Author: Dirk Bueche. 4889 4890 /** Ignored. 4891 * @param event Ignored. 4892 */ 4893 @Override 4894 public void mouseClicked(MouseEvent event) { 4895 } 4896 4897 /** Ignored. 4898 * @param event Ignored. 4899 */ 4900 @Override 4901 public void mouseEntered(MouseEvent event) { 4902 } 4903 4904 /** Ignored. 4905 * @param event Ignored. 4906 */ 4907 @Override 4908 public void mouseExited(MouseEvent event) { 4909 } 4910 4911 /** If the third button is pressed, then 4912 * save the X and Y values. 4913 * @param event The event 4914 */ 4915 @Override 4916 public void mousePressed(MouseEvent event) { 4917 if (event.getButton() == MouseEvent.BUTTON3) { 4918 _movex = event.getX(); // starting point for moving 4919 _movey = event.getY(); 4920 _moving = true; // flag for moving is on 4921 } 4922 } 4923 4924 /** Note that the moving has stopped. 4925 * @param event Ignored. 4926 */ 4927 @Override 4928 public void mouseReleased(MouseEvent event) { 4929 _moving = false; // flag for moving is off 4930 } 4931 } 4932 4933 /** Track how the mouse with button 3 pressed is moved. */ 4934 public class MoveMotionListener implements MouseMotionListener { 4935 // Author: Dirk Bueche. 4936 4937 /** If the mouse is dragged after clicking the third 4938 * button, then shift what is displayed. 4939 * @param event Ignored. 4940 */ 4941 @Override 4942 public void mouseDragged(MouseEvent event) { 4943 4944 if (_moving == false) { 4945 return; 4946 } 4947 4948 double dx = _xMax - _xMin; // actual x range shown in plot 4949 double dy = _yMax - _yMin; // actual y range shown in plot 4950 4951 // pixel 4952 int px = event.getX(); // current position 4953 int py = event.getY(); 4954 double mpx = px - _movex; // movement 4955 double mpy = py - _movey; 4956 4957 // do moving 4958 synchronized (this) { 4959 _xMin = _xMin - dx * mpx / (_lrx - _ulx); 4960 _xMax = _xMax - dx * mpx / (_lrx - _ulx); 4961 _yMin = _yMin - dy * mpy / (_uly - _lry); 4962 _yMax = _yMax - dy * mpy / (_uly - _lry); 4963 } 4964 4965 _movex = px; 4966 _movey = py; 4967 4968 // plot new condition 4969 double temp = _padding; // no padding for this zooming 4970 _padding = 0; 4971 zoom(_xMin, _yMin, _xMax, _yMax); 4972 _padding = temp; 4973 4974 } 4975 4976 /** Ignored. 4977 * @param event Ignored. 4978 */ 4979 @Override 4980 public void mouseMoved(MouseEvent event) { 4981 } 4982 4983 } 4984 4985 /** Zoom with the mouse wheel. */ 4986 public class ZoomListener2 implements MouseWheelListener { 4987 // Author: Dirk Bueche. 4988 4989 /** If the mouse wheel is moved, then zoom accordingly. 4990 * @param e The mouse wheel event. 4991 */ 4992 @Override 4993 public void mouseWheelMoved(MouseWheelEvent e) { 4994 String message; 4995 int notches = e.getWheelRotation(); 4996 double factor; 4997 if (notches < 0) { 4998 message = "Mouse wheel moved UP by " + -notches + " notch(es)"; 4999 factor = 0.02; // zoom in 5000 } else { 5001 message = "Mouse wheel moved DOWN by " + notches + " notch(es)"; 5002 factor = -0.02; // zoom out 5003 } 5004 if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) { 5005 message += " Scroll type: WHEEL_UNIT_SCROLL\n"; 5006 message += " Scroll amount: " + e.getScrollAmount() 5007 + " unit increments per notch\n"; 5008 message += " Units to scroll: " + e.getUnitsToScroll() 5009 + " unit increments\n"; 5010 } else { //scroll type == MouseWheelEvent.WHEEL_BLOCK_SCROLL 5011 message += " Scroll type: WHEEL_BLOCK_SCROLL\n"; 5012 } 5013 // output for debugging 5014 //System.out.println(message); 5015 5016 synchronized (this) { 5017 // Mouse position - this is the center for zooming 5018 double cx = Math.max(Math.min(e.getX(), _lrx), _ulx); 5019 double cy = Math.max(Math.min(e.getY(), _lry), _uly); 5020 5021 double dx = _xMax - _xMin; // actual x range shown in plot 5022 double dy = _yMax - _yMin; // actual y range shown in plot 5023 5024 // do zooming around center for zooming 5025 _xMin = _xMin + dx * (cx - _ulx) / (_lrx - _ulx) * factor; 5026 _xMax = _xMax - dx * (_lrx - cx) / (_lrx - _ulx) * factor; 5027 _yMin = _yMin + dy * (cy - _lry) / (_uly - _lry) * factor; 5028 _yMax = _yMax - dy * (_uly - cy) / (_uly - _lry) * factor; 5029 } 5030 5031 double temp = _padding; // no padding for this zooming 5032 _padding = 0; 5033 zoom(_xMin, _yMin, _xMax, _yMax); 5034 _padding = temp; 5035 } 5036 } 5037 5038 /** Draw the zoom box. 5039 */ 5040 public class DragListener implements MouseMotionListener { 5041 /** Handle mouse drag events. See the class comment for details. 5042 * @param event The event. 5043 */ 5044 @Override 5045 public void mouseDragged(MouseEvent event) { 5046 // NOTE: Due to a bug in JDK 1.1.7B, the BUTTON1_MASK does 5047 // not work on mouse drags. It does work on MouseListener 5048 // methods, so those methods set a variable _zooming that 5049 // is used by _zoomBox to determine whether to draw a box. 5050 if ((event.getModifiers() & InputEvent.BUTTON1_MASK) != 0 5051 && (event.getModifiers() & InputEvent.BUTTON3_MASK) == 0) { 5052 PlotBox.this._zoomBox(event.getX(), event.getY()); 5053 } 5054 } 5055 5056 /** Ignored. 5057 * @param event The event, ignored by this method. 5058 */ 5059 @Override 5060 public void mouseMoved(MouseEvent event) { 5061 } 5062 } 5063 5064 /** Handle key pressed events. 5065 */ 5066 class CommandListener implements KeyListener { 5067 /** Handle key pressed events. See the class comment for details. 5068 * @param e The event. 5069 */ 5070 @Override 5071 public void keyPressed(KeyEvent e) { 5072 int keycode = e.getKeyCode(); 5073 5074 switch (keycode) { 5075 case KeyEvent.VK_CONTROL: 5076 _control = true; 5077 break; 5078 5079 case KeyEvent.VK_SHIFT: 5080 _shift = true; 5081 break; 5082 5083 case KeyEvent.VK_C: 5084 5085 if (_control) { 5086 // The "null" sends the output to the clipboard. 5087 exportImage(null, "gif"); 5088 5089 String message = "GIF image exported to clipboard."; 5090 JOptionPane.showMessageDialog(PlotBox.this, message, 5091 "Ptolemy Plot Message", 5092 JOptionPane.INFORMATION_MESSAGE); 5093 } 5094 5095 break; 5096 5097 case KeyEvent.VK_D: 5098 5099 if (!_control && _shift) { 5100 write(System.out); 5101 5102 String message = "Plot data sent to standard out."; 5103 JOptionPane.showMessageDialog(PlotBox.this, message, 5104 "Ptolemy Plot Message", 5105 JOptionPane.INFORMATION_MESSAGE); 5106 } 5107 5108 if (_control) { 5109 // xgraph and many other Unix apps use Control-D to exit 5110 StringUtilities.exit(1); 5111 } 5112 5113 break; 5114 5115 case KeyEvent.VK_E: 5116 5117 if (!_control && _shift) { 5118 export(System.out); 5119 5120 String message = "Encapsulated PostScript (EPS) " 5121 + "exported to standard out."; 5122 JOptionPane.showMessageDialog(PlotBox.this, message, 5123 "Ptolemy Plot Message", 5124 JOptionPane.INFORMATION_MESSAGE); 5125 } 5126 5127 break; 5128 5129 case KeyEvent.VK_F: 5130 5131 if (!_control && _shift) { 5132 fillPlot(); 5133 } 5134 5135 break; 5136 5137 case KeyEvent.VK_H: 5138 5139 if (!_control && _shift) { 5140 _help(); 5141 } 5142 5143 break; 5144 5145 case KeyEvent.VK_Q: 5146 5147 if (!_control) { 5148 // xv uses q to quit. 5149 StringUtilities.exit(1); 5150 } 5151 5152 break; 5153 5154 case KeyEvent.VK_SLASH: 5155 5156 if (_shift) { 5157 // Question mark is SHIFT-SLASH 5158 _help(); 5159 } 5160 5161 break; 5162 5163 default: 5164 // None 5165 break; 5166 } 5167 } 5168 5169 /** Handle key released events. See the class comment for details. 5170 * @param e The event. 5171 */ 5172 @Override 5173 public void keyReleased(KeyEvent e) { 5174 int keycode = e.getKeyCode(); 5175 5176 switch (keycode) { 5177 case KeyEvent.VK_CONTROL: 5178 _control = false; 5179 break; 5180 5181 case KeyEvent.VK_SHIFT: 5182 _shift = false; 5183 break; 5184 5185 default: 5186 // None 5187 break; 5188 } 5189 } 5190 5191 /** Ignored by this class. 5192 * The keyTyped method is broken in jdk 1.1.4. 5193 * It always gets "unknown key code". 5194 * @param e Ignored by this method. 5195 */ 5196 @Override 5197 public void keyTyped(KeyEvent e) { 5198 } 5199 5200 private boolean _control = false; 5201 5202 private boolean _shift = false; 5203 } 5204 5205 /** 5206 * TimedRepaint is a timer thread that will schedule a 5207 * redraw each _REPAINT_TIME_INTERVAL milliseconds. 5208 */ 5209 private static class TimedRepaint extends Timer { 5210 static int _REPAINT_TIME_INTERVAL = 30; 5211 5212 public synchronized void addListener(PlotBox plotBox) { 5213 _listeners.add(plotBox); 5214 if (_listeners.size() == 1) { 5215 scheduleAtFixedRate(new TimerTask() { 5216 @Override 5217 public void run() { 5218 synchronized (this) { 5219 // synchronized (this) to avoid changes to 5220 // _listeners while repainting. 5221 for (PlotBox plot : _listeners) { 5222 if (plot._timedRepaint()) { 5223 plot._scheduledRedraw(); 5224 } 5225 } 5226 } 5227 } 5228 }, 0, _REPAINT_TIME_INTERVAL); 5229 } 5230 } 5231 5232 public synchronized void removeListener(PlotBox plotBox) { 5233 _listeners.remove(plotBox); 5234 if (_listeners.isEmpty()) { 5235 purge(); 5236 } 5237 } 5238 5239 private Set<PlotBox> _listeners = new HashSet<PlotBox>(); 5240 } 5241 5242 /** True if we have printed the securityExceptionMessage. */ 5243 private static boolean _printedSecurityExceptionMessage = false; 5244}