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}