root/trunk/matml/src/sphlow/Plot.java

Revision 148, 67.4 kB (checked in by powell, 7 years ago)

New Sphlow Java applet.

  • Property svn:keywords set to Author Date Id Revision
Line 
1/* A signal plotter.
2
3@Copyright (c) 1997-1998 The Regents of the University of California.
4All rights reserved.
5
6Permission is hereby granted, without written agreement and without
7license or royalty fees, to use, copy, modify, and distribute this
8software and its documentation for any purpose, provided that the
9above copyright notice and the following two paragraphs appear in all
10copies of this software.
11
12IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
13FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
15THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
16SUCH DAMAGE.
17
18THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
19INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
20MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
21PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
22CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
23ENHANCEMENTS, OR MODIFICATIONS.
24
25                                                PT_COPYRIGHT_VERSION_2
26                                                COPYRIGHTENDKEY
27*/
28//package ptplot;
29
30// FIXME: To do
31//   - support for oscilloscope-like plots (where x axis wraps around).
32//   - steps between points rather than connected lines.
33//   - cubic spline interpolation
34//   - get rid of all of the deprecated lines and require JDK1.1
35//     
36// NOTE: The XOR drawing mode is needed in order to be able to erase
37// plotted points and restore the grid line, tick marks, and boundary
38// rectangle.  Another alternative would be to put the tick marks
39// outside the rectangle, disallow grid marks, and adjust drawing so
40// it never overlaps the boundary rectangle.  Then erasing could be
41// done by redrawing in white. This would be better.
42
43// NOTE: There are quite a few subjective spacing parameters, all
44// given, unfortunately, in pixels.  This means that as resolutions
45// get better, this program may need to be adjusted.
46
47import java.awt.*;
48import java.io.*;
49import java.util.*;
50
51//////////////////////////////////////////////////////////////////////////
52//// Plot
53/**
54 * A flexible signal plotter.  The plot can be configured and data can
55 * be provided either through a file with commands or through direct
56 * invocation of the public methods of the class or by passing pxgraph
57 * arguments through the "pxgraphargs" parameter.  If a file is used,
58 * the file can be given as a URL through the <code>setDataurl</code>
59 * method in the parent class.  The file contains any number commands,
60 * one per line.  Unrecognized commands and commands with syntax
61 * errors are ignored.  Comments are denoted by a line starting with a
62 * pound sign "#".  The recognized commands include those supported by
63 * the base class, plus a few more.  The commands are case
64 * insensitive, but are usually capitalized.  The following command
65 * defines the number of data sets to be plotted.
66 * <pre>
67 * NumSets: <i>positiveInteger</i>
68 * </pre>
69 * If data is provided for more data sets than this number, those
70 * data are ignored.  Each dataset can be optionally identified with
71 * color (see the base class) or with unique marks.  The style of
72 * marks used to denote a data point is defined by one of the following
73 * commands:
74 * <pre>
75 * Marks: none
76 * Marks: points
77 * Marks: dots
78 * Marks: various
79 * </pre>
80 * Here, "points" are small dots, while "dots" are larger.  If "various"
81 * is specified, then unique marks are used for the first ten data sets,
82 * and then recycled.
83 * Using no marks is useful when lines connect the points in a plot,
84 * which is done by default.  To disable connecting lines, use:
85 * <pre>
86 * Lines: off
87 * </pre>
88 * To reenable them, use
89 * <pre>
90 * Lines: on
91 * </pre>
92 * You can also specify "impulses," which are lines drawn from a plotted point
93 * down to the x axis.  These are off by default, but can be turned on with the
94 * command:
95 * <pre>
96 * Impulses: on
97 * </pre>
98 * or back off with the command
99 * <pre>
100 * Impulses: off
101 * </pre>
102 * To create a bar graph, turn off lines and use any of the following commands:
103 * <pre>
104 * Bars: on
105 * Bars: <i>width</i>
106 * Bars: <i>width, offset</i>
107 * </pre>
108 * The <i>width</i> is a real number specifying the width of the bars
109 * in the units of the x axis.  The <i>offset</i> is a real number
110 * specifying how much the bar of the <i>i</i><sup>th</sup> data set
111 * is offset from the previous one.  This allows bars to "peek out"
112 * from behind the ones in front.  Note that the frontmost data set
113 * will be the last one.  To turn off bars, use
114 * <pre>
115 * Bars: off
116 * </pre>
117 * To specify data to be plotted, start a data set with the following command:
118 * <pre>
119 * DataSet: <i>string</i>
120 * </pre>
121 * Here, <i>string</i> is a label that will appear in the legend.
122 * It is not necessary to enclose the string in quotation marks.
123 * The data itself is given by a sequence of commands with one of the
124 * following forms:
125 * <pre>
126 * <i>x</i>,<i>y</i>
127 * draw: <i>x</i>,<i>y</i>
128 * move: <i>x</i>,<i>y</i>
129 * <i>x</i>,<i>y</i>,<i>yLowErrorBar</i>,<i>yHighErrorBar</i>
130 * </pre>
131 * The "draw" command is optional, so the first two forms are equivalent.
132 * The "move" command causes a break in connected points, if lines are
133 * being drawn between points. The numbers <i>x</i> and <i>y</i> are
134 * arbitrary numbers as supported by the Double parser in Java.
135 * If there are four numbers, then the last two numbers are assumed to
136 * be the lower and upper values for error bars.
137 * The numbers can be separated by commas, spaces or tabs.
138 *
139 * This plotter has some <A NAME="ptplot limitations">limitations</a>:
140 * <ul>
141 * <li> Marks, impulses, and bars are assumed to apply to the entire
142 *      plot, i.e. to all data sets.  Although it is possible to change
143 *      these styles for different data sets, the graph will not be
144 *      correctly redrawn when it gets redrawn due to zooming in or out
145 *      or due to a window exposure event.
146 * <li> If you zoom in far enough, the plot becomes unreliable.
147 *      In particular, if the total extent of the plot is more than
148 *      2<sup>32</sup> times extent of the visible area, quantization
149 *      errors can result in displaying points or lines.
150 *      Note that 2<sup>32</sup> is over 4 billion.
151 * <li> The limitations of the log axis facility are listed in
152 *      the <code>_gridInit()</code> method in the PlotBox class.
153 * <li> The compatibility issues of the <code>pxgraph</code> script are
154 *      list in the
155 *<a href="ptplot.Pxgraph.html#pxgraph script compatibility issues">Pxgraph</a>
156 *      class.
157 * </ul>
158 *
159 * @author Edward A. Lee, Christopher Hylands
160 * @version @(#)Plot.java       1.82 01/27/98
161 */
162public class Plot extends PlotBox {
163
164    //////////////////////////////////////////////////////////////////////////
165    ////                         public methods                           ////
166   
167    /**
168     * In the specified data set, add the specified x,y point to the
169     * plot.  Data set indices begin with zero.  If the dataset
170     * argument is out of range, ignore.  The number of data sets is
171     * given by calling *setNumSets()*.  The fourth argument indicates
172     * whether the point should be connected by a line to the previous
173     * point.
174     */
175    public synchronized void addPoint(int dataset, double x, double y,
176            boolean connected) {
177        if (_xlog) {
178            if (x <= 0.0) {
179                System.err.println("Can't plot non-positive X values "+
180                        "when the logarithmic X axis value is specified: " +
181                        x);
182                return;
183            }
184            x = Math.log(x)*_LOG10SCALE;
185        }
186        if (_ylog) {
187            if (y <= 0.0) {
188                System.err.println("Can't plot non-positive Y values "+
189                        "when the logarithmic Y axis value is specified: " +
190                        y);
191                return;
192            }
193            y = Math.log(y)*_LOG10SCALE;
194        }
195        // This point is not an error bar so we set yLowEB
196        // and yHighEB to 0
197        _addPoint(_graphics, dataset, x, y, 0, 0, connected, false);
198    }
199   
200    /**
201     * In the specified data set, add the specified x,y point to the
202     * plot with error bars.  Data set indices begin with zero.  If
203     * the dataset argument is out of range, ignore.  The number of
204     * data sets is given by calling *setNumSets()*.  yLowEB and
205     * yHighEB are the lower and upper error bars.  The sixth argument
206     * indicates whether the point should be connected by a line to
207     * the previous point.
208     * This method is based on a suggestion by
209     * Michael Altmann <michael@email.labmed.umn.edu>.
210     */
211    public synchronized void addPointWithErrorBars(int dataset,
212            double x, double y, double yLowEB, double yHighEB,
213            boolean connected) {
214        if (dataset >= _numsets || dataset < 0 || _datasetoverflow) return;
215        if (_xlog) {
216            if (x <= 0.0) {
217                System.err.println("Can't plot non-positive X values "+
218                        "when the logarithmic X axis value is specified: " +
219                        x);
220                return;
221            }
222            x = Math.log(x)*_LOG10SCALE;
223        }
224        if (_ylog) {
225            if (y <= 0.0 || yLowEB <= 0.0 || yHighEB <= 0.0) {
226                System.err.println("Can't plot non-positive Y values "+
227                        "when the logarithmic Y axis value is specified: " +
228                        y);
229                return;
230            }
231            y = Math.log(y)*_LOG10SCALE;
232            yLowEB = Math.log(yLowEB)*_LOG10SCALE;
233            yHighEB = Math.log(yHighEB)*_LOG10SCALE;
234        }
235        _addPoint(_graphics, dataset, x, y,
236                yLowEB, yHighEB, connected, true);
237    }
238   
239    /**
240     * Draw the axes and then plot all points.  This is synchronized
241     * to prevent multiple threads from drawing the plot at the same
242     * time.  It calls <code>notify()</code> at the end so that a
243     * thread can use <code>wait()</code> to prevent it plotting
244     * points before the axes have been first drawn.  If the argument
245     * is true, clear the display first.
246     */
247    public synchronized void drawPlot(Graphics graphics,
248            boolean clearfirst) {
249        super.drawPlot(graphics, clearfirst);
250        // Plot the points
251        for (int dataset = 0; dataset < _numsets; dataset++) {
252            // FIXME: Make the following iteration more efficient.
253            Vector data = _points[dataset];
254            for (int pointnum = 0; pointnum < data.size(); pointnum++) {
255                _drawPlotPoint(graphics, dataset, pointnum);
256            }
257        }
258        notify();
259    }
260   
261    /**
262     * Erase the point at the given index in the given dataset.  If
263     * lines are being drawn, also erase the line to the next points
264     * (note: not to the previous point).  The point is not checked to
265     * see whether it is in range, so care must be taken by the caller
266     * to ensure that it is.
267     */
268    public synchronized void erasePoint(int dataset, int index) {
269        _erasePoint(_graphics, dataset, index); 
270    }
271
272    /**
273     * Return the maximum number of datasets
274     */
275    public int getMaxDataSets() {
276        return _MAX_DATASETS;
277    }
278
279    /**
280     * Initialize the plotter, parse any data files.
281     */
282    public synchronized void init() {
283        if (_debug > 8) System.out.println("Plot: init");
284
285        setNumSets(_MAX_DATASETS);
286
287        super.init();
288        if (_dataurls != null ) {
289            // If the pxgraphargs parameter was set, then we might have more
290            // than one file to plot.
291            Enumeration urls = _dataurls.elements();
292            while (urls.hasMoreElements()) {
293                String url = (String) urls.nextElement();
294                if (_debug > 3) System.out.println("Plot: getting "+url);
295                parseFile(url);
296            }
297        }
298    }
299
300    /**
301     * Draw the axes and the accumulated points.
302     */
303    public void paint(Graphics graphics) {
304        drawPlot(graphics,true);
305    }
306
307    /** Parse pxgraph style command line arguments.
308     * This method exists only for backward compatibility with the X11 pxgraph
309     * program.
310     * @exception ptplot.CmdLineArgException if there is a problem parsing
311     * the command line arguments passed in.
312     */ 
313    public int parseArgs(String args[]) throws CmdLineArgException {
314        int i = 0, j, argsread = 0;
315
316
317        // If we see both -nl and -bar, assume we do an impulse plot.
318        boolean sawbararg = false; // Saw -bar arg.
319        boolean sawnlarg = false// Saw -nl arg.
320        int savedmarks = 0;        // Save _marks in case we have -P -bar -nl.
321
322        String arg;
323        String unsupportedOptions[] = {
324            "-bd", "-brb", "-bw", "-gw", "-lw", "-zg", "-zw"
325        };
326        String unsupportedFlags[] = {
327            "-bb"
328        };
329        // Default URL to be opened
330        String dataurl = "";
331
332        String title = "A plot";
333
334        while (i < args.length && (args[i].startsWith("-") ||
335                args[i].startsWith("=")) ) {
336            arg = args[i++];
337            if (_debug > 2) System.out.print("Plot: arg = " + arg + "\n");
338
339            if (arg.startsWith("-")) {
340                // Search for unsupported options that take arguments
341                boolean badarg = false;
342                for(j = 0; j < unsupportedOptions.length; j++) {
343                    if (arg.equals(unsupportedOptions[j])) {
344                        System.err.println("pxgraph: " + arg +
345                                " is not yet supported");
346                        i++;
347                        badarg = true;
348                    }
349                }
350                if (badarg) continue;
351                // Search for unsupported boolean flags
352                for(j = 0; j < unsupportedFlags.length; j++) {
353                    if (arg.equals(unsupportedFlags[j])) {
354                        System.err.println("pxgraph: " + arg +
355                                " is not yet supported");
356                        badarg = true;
357                    }
358
359                }
360                if (badarg) continue;
361                if (arg.equals("-bg")) {
362                    setBackground(getColorByName(args[i++]));
363                    continue;
364                } else if (arg.equals("-brw")) {
365                    // -brw <width> BarWidth Bars:
366                    // We default the baroffset to 0 here if the value does
367                    // not include a comma.
368                    if (arg.indexOf(",") == -1) {
369                        if (!_parseLine("Bars: " + args[i++]+",0")) {
370                            throw new 
371                                CmdLineArgException("Failed to parse `"+
372                                        arg+"'");
373                        }
374                    } else {
375                        if (!_parseLine("Bars: " + args[i++])) {
376                            throw new 
377                                CmdLineArgException("Failed to parse `"+
378                                        arg+"'");
379                        }
380                    }
381                    continue;
382                } else if (arg.equals("-lf")) {
383                    // -lf <labelfont>
384                    setLabelFont(args[i++]);
385                    continue;
386                } else if (arg.equals("-lx")) {
387                    // -lx <xl,xh> XLowLimit, XHighLimit  XRange:
388                    if (!_parseLine("XRange: " + args[i++])) {
389                        throw new 
390                            CmdLineArgException("Failed to parse `"+arg+"'");
391                    }
392                    continue;
393                } else if (arg.equals("-ly")) {
394                    // -ly <yl,yh> YLowLimit, YHighLimit  YRange:
395                    if (!_parseLine("YRange: " + args[i++])) {
396                        throw new 
397                            CmdLineArgException("Failed to parse `"+arg+"'");
398                    }
399                    continue;
400                } else if (arg.equals("-t")) {
401                    // -t <title> TitleText "An X Graph"
402                    title =  args[i++];
403                    continue;
404                } else if (arg.equals("-tf")) {
405                    // -tf <titlefont>
406                    setTitleFont(args[i++]);
407                    continue;
408                } else if (arg.equals("-x")) {
409                    // -x <unitName> XUnitText XLabel:
410                    setXLabel(args[i++]);
411                    continue;
412                } else if (arg.equals("-y")) {
413                    // -y <unitName> YUnitText YLabel:
414                    setYLabel(args[i++]);
415                    continue;               
416                } else if (arg.equals("-bar")) {
417                    //-bar BarGraph Bars: on Marks: none Lines: off
418                    // If we saw the -nl arg, then assume impulses
419                    sawbararg = true;
420                    if (sawnlarg) {
421                        setImpulses(true);
422                    } else {
423                        setBars(true);
424                        // Save _marks in case we did -P -bar -nl.
425                        savedmarks = _marks;
426                        setMarksStyle("none");
427                    }
428                    setConnected(false);
429                    continue;
430                } else if (arg.equals("-binary")) {
431                    setBinary(true);
432                    continue;
433                } else if (arg.equals("-db")) {
434                    _debug = 6;
435                    continue;
436                } else if (arg.equals("-debug")) {
437                    // -debug is not in the original X11 pxgraph.
438                    _debug = (int)Integer.valueOf(args[i++]).intValue();
439                    continue;
440                } else if (arg.equals("-fg")) {
441                    setForeground(getColorByName(args[i++]));
442                    continue;
443                } else if (arg.equals("-help")) {
444                    // -help is not in the original X11 pxgraph.
445                    //_help();
446                    continue;
447                } else if (arg.equals("-impulses")) {
448                    // -impulses is not in the original X11 pxgraph.
449                    setImpulses(true);
450                    setConnected(false);
451                    continue;
452                } else if (arg.equals("-lnx")) {
453                    setXLog(true);
454                    continue;
455                } else if (arg.equals("-lny")) {
456                    setYLog(true);
457                    continue;
458                } else if (arg.equals("-m")) {
459                    // -m Markers Marks: various
460                    setMarksStyle("various");
461                    continue;
462                } else if (arg.equals("-M")) {
463                    // -M StyleMarkers Marks: various
464                    setMarksStyle("various");
465                    continue;
466                } else if (arg.equals("-nl")) {
467                    // -nl NoLines Lines: off
468                    // If we saw the -bar arg, then assume impulses
469                    sawnlarg = true;
470                    if (sawbararg) {
471                        // Restore the _marks in case we did -P -bar -nl
472                        _marks = savedmarks;
473                        setBars(false);
474                        setImpulses(true);
475                    }
476                    setConnected(false);
477                    continue;
478                } else if (arg.equals("-p")) {
479                    // -p PixelMarkers Marks: points
480                    setMarksStyle("points");
481                    continue;
482                } else if (arg.equals("-P")) {
483                    // -P LargePixel Marks: dots\n
484                    setMarksStyle("dots");
485                    continue;
486                } else if (arg.equals("-rv")) {
487                    setBackground(getColorByName("black"));
488                    setForeground(getColorByName("white"));
489                    continue;
490                } else if (arg.equals("-test")) {
491                    // -test is not in the original X11 pxgraph.
492                    //_test = true;
493                    continue;
494                } else if (arg.equals("-tk")) {
495                    setGrid(false);
496                    continue;
497                } else if (arg.equals("-v") || arg.equals("-version")) {
498                    // -version is not in the original X11 pxgraph.
499                    //_version();
500                    continue;
501                } else if (arg.equals("-m")) {
502
503                } if (arg.length() > 1  && arg.charAt(0) == '-') {
504                    // Process '-<digit> <datasetname>'
505                    try {
506                        Integer datasetnumberint = new
507                            Integer(arg.substring(1));
508                        int datasetnumber = datasetnumberint.intValue();
509                        if (datasetnumber >= 0 &&
510                                datasetnumber <= getMaxDataSets()) {
511                            if (_debug > 8)
512                                System.out.println("Plot: parseArgs: "+
513                                        "calling addLegend "+
514                                        datasetnumber+" "+args[i]);
515                            addLegend(datasetnumber, args[i++]);
516                            continue;
517                        }
518                    } catch (NumberFormatException e) {}
519                }
520            } else {
521                if (arg.startsWith("=")) {
522                    // Process =WxH+X+Y
523                    _width = (int)Integer.valueOf(arg.substring(1,
524                            arg.indexOf('x'))).intValue();
525                    if (arg.indexOf('+') != -1) {
526                        _height =
527                            Integer.valueOf(arg.substring(
528                                    arg.indexOf('x')+1,
529                                    arg.indexOf('+'))).intValue();
530                    } else {
531                        if (arg.length() > arg.indexOf('x')) {
532                            _height =
533                                Integer.valueOf(arg.substring(
534                                        arg.indexOf('x')+1,
535                                        arg.length())).intValue();
536                        }
537                    }
538                    // FIXME: it is unclear what X and Y in =WxH+X+Y mean
539                    // in a non-toplevel window, so we don't process
540                    // those here.  See Pxgraph.java for how to process
541                    // X and Y for a toplevel window.
542                    continue;
543                }
544            }
545            // If we got to here, then we failed to parse the arg
546            throw new 
547                CmdLineArgException("Failed to parse `" + arg + "'");
548        }
549        if (i < args.length) {
550            dataurl=args[i];
551        }
552        argsread = i++;
553
554        // Now we've parsed the parameters, so we call parent class methods.
555        setDataurl(dataurl); // Set the dataurl in PlotBox
556        setTitle(title);
557        if (_debug > 9)
558            System.out.println("Plot: parseArgs: resize()"+_width+" "+_height);
559        resize(_width,_height);
560
561        if (_debug > 0) {
562            System.err.println("Plot: dataurl = " + dataurl);
563            System.err.println("Plot: title= " + title);
564        }
565        if (_debug > 3) System.out.println("Plot: argsread = "+ argsread +
566                " args.length = "+args.length);
567        // Copy the file names into the _dataurls Vector for use later.
568        _dataurls = new Vector();
569        for(i = argsread+1; i < args.length; i++) {
570            if (_debug > 3) System.out.println("Plot: saving "+args[i]);
571            _dataurls.addElement(args[i]);
572        }
573        return argsread;
574    }
575
576    /* Split pxgraphargs up into an array and call _parseArgs
577     */       
578    public int parsePxgraphargs(String pxgraphargs) throws CmdLineArgException  {
579        // We convert the String to a Stream and then use a StreamTokenizer
580        // to parse the arguments into a Vector and then copy
581        // the vector into an array of Strings.  We use a Vector
582        // so that we can handle an arbitrary number of arguments
583        if (_debug > 3) {
584            System.out.println("Plot: parsePxgraphargs "+pxgraphargs);
585        }
586
587        Vector argvector = new Vector();
588        boolean prependdash = false; // true if we need to add a -
589       
590        StringBufferInputStream inp = new StringBufferInputStream(pxgraphargs);
591        // StringBufferInput is deprecated, but StringReader is not in 1.0.2
592
593        //StringReader inp = new StringReader(pxgraphargs);
594
595        try {
596            StreamTokenizer stoken = new StreamTokenizer(inp); // Deprecated.
597
598            // We don't want to parse numbers specially, so we reset
599            // the syntax and then add back what we want.
600            stoken.resetSyntax();
601            stoken.whitespaceChars(0, ' ');
602            stoken.wordChars('(','~');
603            stoken.quoteChar('"');
604            stoken.quoteChar('\'');
605            int c;
606
607        out:
608            while (true) {
609                c = stoken.nextToken();
610                //System.out.print(c + " "+stoken.ttype+" "+stoken.sval+" ");
611                switch (stoken.ttype) {        // same as value of 'c'
612                case StreamTokenizer.TT_EOF:
613                    break out;
614                case StreamTokenizer.TT_WORD:
615                    //System.out.println("Word: " + stoken.sval);
616                    if (prependdash) {
617                        prependdash = false;
618                        argvector.addElement(new String("-"+stoken.sval));
619                    } else {
620                        argvector.addElement(new String(stoken.sval));
621                    }
622
623                    break;
624                case '-':
625                    prependdash = true;
626                    break;
627                case '"':
628                case '\'':
629                    //System.out.println("String: " + stoken.sval);
630                    argvector.addElement(new String(stoken.sval));
631                    break;
632                default:
633                    throw new IOException("Failed to parse: '"+ (char)c +
634                            "' in `" + pxgraphargs + "'");
635                }
636            }
637        } catch (IOException e) {
638            e.printStackTrace();
639        }
640
641
642        // Create a array
643        String args[] = new String[argvector.size()];
644        for(int i = 0; i<argvector.size(); i++) {
645            args[i] = (String)argvector.elementAt(i);
646            if (_debug > 2) System.out.print("<"+args[i]+ "> ");
647        }
648        if (_debug > 2) System.out.println(" ");
649
650        return parseArgs(args);
651    }
652
653    /**
654     * Resize the plot.
655     * @deprecated As of JDK1.1 in java.awt.component, but we need
656     * to compile under 1.0.2 for netscape3.x compatibility.
657     */
658    public void resize(int width, int height) {
659        if (_debug > 8)
660            System.out.println("Plot: resize"+width+" "+height);
661        _width = width;
662        _height = height;
663        super.resize(width,height); // FIXME: resize() is deprecated.
664    }
665
666    /**
667     * Turn bars on or off.
668     */
669    public void setBars (boolean on) {
670        _bars = on;
671    }
672
673    /**
674     * Turn bars on and set the width and offset.  Both are specified
675     * in units of the x axis.  The offset is the amount by which the
676     * i<sup>th</sup> data set is shifted to the right, so that it
677     * peeks out from behind the earlier data sets.
678     */
679    public void setBars (double width, double offset) {
680        _barwidth = width;
681        _baroffset = offset;
682        _bars = true;
683    }
684   
685    /**
686     * If the argument is true, then the default is to connect
687     * subsequent points with a line.  If the argument is false, then
688     * points are not connected.  When points are by default
689     * connected, individual points can be not connected by giving the
690     * appropriate argument to <code>addPoint()</code>.
691     */
692    public void setConnected (boolean on) {
693        _connected = on;
694    }
695   
696    /**
697     * If the argument is true, then a line will be drawn from any
698     * plotted point down to the x axis.  Otherwise, this feature is
699     * disabled.
700     */
701    public void setImpulses (boolean on) {
702        _impulses = on;
703    }
704   
705    /**
706     * Set the marks style to "none", "points", "dots", or "various".
707     * In the last case, unique marks are used for the first ten data
708     * sets, then recycled.
709     */
710    public void setMarksStyle (String style) {
711        if (style.equalsIgnoreCase("none")) {
712            _marks = 0;
713        } else if (style.equalsIgnoreCase("points")) {
714            _marks = 1;
715        } else if (style.equalsIgnoreCase("dots")) {
716            _marks = 2;
717        } else if (style.equalsIgnoreCase("various")) {
718            _marks = 3;
719        }
720    }
721
722    /**
723     * Specify the number of data sets to be plotted together.
724     * Allocate a Vector to store each data set.  Note that calling
725     * this causes any previously plotted points to be forgotten.
726     * This method should be called before
727     * <code>setPointsPersistence</code>.
728     * @exception java.io.NumberFormatException if the number is less
729     * than 1 or greater than an internal limit (usually 63).
730     */
731    public void setNumSets (int numsets) throws NumberFormatException {
732        if (numsets < 1) {
733            throw new NumberFormatException("Number of data sets ("+
734                    numsets + ") must be greater than 0.");
735
736        }
737        if (numsets > _MAX_DATASETS) {
738            throw new NumberFormatException("Number of data sets (" +
739                    numsets + ") must be less than the internal limit of " +
740                    _MAX_DATASETS + "To increase this value, edit " +
741                    "_MAX_DATASETS and recompile");
742        }
743
744        _currentdataset = -1;
745        _datasetoverflow = false;
746        _numsets = numsets;
747        _points = new Vector[numsets];
748        _prevx = new long[numsets];
749        _prevy = new long[numsets];
750        for (int i=0; i<numsets; i++) {
751            _points[i] = new Vector();
752        }
753    }
754   
755    /**
756     * Calling this method with a positive argument sets the
757     * persistence of the plot to the given number of points.  Calling
758     * with a zero argument turns off this feature, reverting to
759     * infinite memory (unless sweeps persistence is set).  If both
760     * sweeps and points persistence are set then sweeps take
761     * precedence.  This method should be called after
762     * <code>setNumSets()</code>. 
763     * FIXME: No file format yet.
764     */
765    public void setPointsPersistence (int persistence) {
766        _pointsPersistence = persistence;
767        if (persistence > 0) {
768            for (int i = 0; i < _numsets; i++) {
769                _points[i].setSize(persistence);
770            }
771        }
772    }
773   
774    /**
775     * A sweep is a sequence of points where the value of X is
776     * increasing.  A point that is added with a smaller x than the
777     * previous point increments the sweep count.  Calling this method
778     * with a non-zero argument sets the persistence of the plot to
779     * the given number of sweeps.  Calling with a zero argument turns
780     * off this feature.  If both sweeps and points persistence are
781     * set then sweeps take precedence.
782     * FIXME: No file format yet.
783     * FIXME: Not implemented yet.
784     */
785    public void setSweepsPersistence (int persistence) {
786        _sweepsPersistence = persistence;
787    }
788
789
790    /** Start the plot.
791     * This method is redefined in child classes, such as PlotLive.   
792     */
793    public void start () {
794    }
795
796    /** Stop the plot.
797     * This method is redefined in child classes, such as PlotLive.   
798     */
799    public void stop () {
800    }
801
802    //////////////////////////////////////////////////////////////////////////
803    ////                          protected methods                       ////
804 
805       
806    /**
807     * Draw bar from the specified point to the y axis.
808     * If the specified point is below the y axis or outside the
809     * x range, do nothing.  If the <i>clip</i> argument is true,
810     * then do not draw above the y range.
811     */
812    protected void _drawBar (Graphics graphics, int dataset,
813            long xpos, long ypos, boolean clip) {
814        if (_debug > 21) {
815            System.out.println("Plot: _drawBar("+dataset+" "+
816                    xpos+" "+ypos+" "+clip+")");
817        }
818        if (clip) {
819            if (ypos < _uly) {
820                ypos = _uly;
821            } if (ypos > _lry) {
822                ypos = _lry;
823            }
824        }
825        if (ypos <= _lry && xpos <= _lrx && xpos >= _ulx) {
826            // left x position of bar.
827            int barlx = (int)(xpos - _barwidth * _xscale/2 +
828                    (_currentdataset - dataset - 1) *
829                    _baroffset * _xscale);
830            // right x position of bar
831            int barrx = (int)(barlx + _barwidth * _xscale);
832            if (barlx < _ulx) barlx = _ulx;
833            if (barrx > _lrx) barrx = _lrx;
834            // Make sure that a bar is always at least one pixel wide.
835            if (barlx >= barrx) barrx = barlx+1;
836            // The y position of the zero line.
837            long zeroypos = _lry - (long) ((0-_yMin) * _yscale);
838            if (_lry < zeroypos) zeroypos = _lry;
839            if (_uly > zeroypos) zeroypos = _uly;
840
841            if (_debug > 20) {
842                System.out.println("Plot:_drawBar ("+barlx+" "+ypos+" "+
843                        (barrx - barlx) + " "+(zeroypos-ypos)+") "+barrx+" "+
844                        barlx+" ("+_ulx+" "+_lrx+" "+_uly+" "+_lry+
845                        ") xpos="+xpos+" ypos="+ypos+" zeroypos="+zeroypos+
846                        " "+_barwidth+" "+_xscale+" "+_currentdataset+
847                        " "+_yMin);
848            }
849
850            if (_yMin >= 0 || ypos <= zeroypos) {
851                graphics.fillRect(barlx, (int)ypos,
852                        barrx - barlx, (int)(zeroypos - ypos));
853            } else {
854                graphics.fillRect(barlx, (int)zeroypos,
855                        barrx - barlx, (int)(ypos - zeroypos));
856            }
857        }
858    }
859
860    /**
861     * Draw an error bar for the specified yLowEB and yHighEB values.
862     * If the specified point is below the y axis or outside the
863     * x range, do nothing.  If the <i>clip</i> argument is true,
864     * then do not draw above the y range.
865     */
866    protected void _drawErrorBar (Graphics graphics, int dataset,
867            long xpos, long yLowEBPos, long yHighEBPos,
868            boolean clip) {
869        if (_debug > 20) {
870            System.out.println("Plot: _drawErrorBar("+xpos+" "+
871                    yLowEBPos+" "+yHighEBPos+" "+clip+")");
872        }
873        _drawLine(graphics, dataset, xpos - _ERRORBAR_LEG_LENGTH, yHighEBPos,
874                xpos + _ERRORBAR_LEG_LENGTH, yHighEBPos, clip);
875        _drawLine(graphics, dataset, xpos, yLowEBPos, xpos, yHighEBPos, clip);
876        _drawLine(graphics, dataset, xpos - _ERRORBAR_LEG_LENGTH, yLowEBPos,
877                xpos + _ERRORBAR_LEG_LENGTH, yLowEBPos, clip);
878    }
879
880    /**
881     * Draw an impulse from the specified point to the y axis.
882     * If the specified point is below the y axis or outside the
883     * x range, do nothing.  If the <i>clip</i> argument is true,
884     * then do not draw above the y range.
885     */
886    protected void _drawImpulse (Graphics graphics,
887            long xpos, long ypos, boolean clip) {
888        if (_debug > 20) {
889            System.out.println("Plot: _drawImpulse("+xpos+" "+ypos+" "+
890                    clip+") ("+_ulx+" "+_uly+" "+_lrx+" "+_lry+")");
891        }
892        if (clip) {
893            if (ypos < _uly) {
894                ypos = _uly;
895            } if (ypos > _lry) {
896                ypos = _lry;
897            }
898        }
899        if (ypos <= _lry && xpos <= _lrx && xpos >= _ulx) {
900            // The y position of the zero line.
901            double zeroypos = _lry - (long) ((0-_yMin) * _yscale);
902            if (_lry < zeroypos) zeroypos = _lry;
903            if (_uly > zeroypos) zeroypos = _uly;
904            graphics.drawLine((int)xpos, (int)ypos, (int)xpos,
905                    (int)zeroypos);
906        }
907    }
908
909    /**
910     * Draw a line from the specified starting point to the specified
911     * ending point.  The current color is used.  If the <i>clip</i> argument
912     * is true, then draw only that portion of the line that lies within the
913     * plotting rectangle.
914     */
915    protected void _drawLine (Graphics graphics,
916            int dataset, long startx, long starty, long endx, long endy,
917            boolean clip) {
918
919        if (clip) {
920            // Rule out impossible cases.
921            if (_debug > 20) {
922                System.out.println("Plot: _drawLine: bounds: " + _ulx +", "+
923                        _uly +", "+ _lrx +", "+ _lry);
924                System.out.println("Plot: _drawLine:before: " + startx +", "+
925                        starty +", "+ endx +", "+ endy);
926            }
927            if (!((endx <= _ulx && startx <= _ulx) ||
928                    (endx >= _lrx && startx >= _lrx) ||
929                    (endy <= _uly && starty <= _uly) ||
930                    (endy >= _lry && starty >= _lry))) {
931                // If the end point is out of x range, adjust
932                // end point to boundary.
933                // The integer arithmetic has to be done with longs so as
934                // to not loose precision on extremely close zooms.
935                if (startx != endx) {
936                    if (endx < _ulx) {
937                        endy = (int)(endy + ((long)(starty - endy) *
938                                (_ulx - endx))/(startx - endx));
939                        endx = _ulx;
940                    } else if (endx > _lrx) {
941                        endy = (int)(endy + ((long)(starty - endy) *
942                                (_lrx - endx))/(startx - endx));
943                        endx = _lrx;
944                    }
945                }
946                   
947                // If end point is out of y range, adjust to boundary.
948                // Note that y increases downward
949                if (starty != endy) {
950                    if (endy < _uly) {
951                        endx = (int)(endx + ((long)(startx - endx) *
952                                (_uly - endy))/(starty - endy));
953                        endy = _uly;
954                    } else if (endy > _lry) {
955                        endx = (int)(endx + ((long)(startx - endx) *
956                                (_lry - endy))/(starty - endy));
957                        endy = _lry;
958                    }
959                }
960                   
961                // Adjust current point to lie on the boundary.
962                if (startx != endx) {
963                    if (startx < _ulx) {
964                        starty = (int)(starty + ((long)(endy - starty) *
965                                (_ulx - startx))/(endx - startx));
966                        startx = _ulx;
967                    } else if (startx > _lrx) {
968                        starty = (int)(starty + ((long)(endy - starty) *
969                                (_lrx - startx))/(endx - startx));
970                        startx = _lrx;
971                    }
972                }
973                if (starty != endy) {
974                    if (starty < _uly) {
975                        startx = (int)(startx + ((long)(endx - startx) *
976                                (_uly - starty))/(endy - starty));
977                        starty = _uly;
978                    } else if (starty > _lry) {
979                        startx = (int)(startx + ((long)(endx - startx) *
980                                (_lry - starty))/(endy - starty));
981                        starty = _lry;
982                    }
983                }
984            }
985                 
986            // Are the new points in range?
987            if (endx >= _ulx && endx <= _lrx &&
988                    endy >= _uly && endy <= _lry &&
989                    startx >= _ulx && startx <= _lrx &&
990                    starty >= _uly && starty <= _lry) {
991                if (_debug > 20) {
992                    System.out.println("after: " + startx +", "+ starty +
993                            ", "+ endx +", "+ endy);
994                }
995                graphics.drawLine((int)startx, (int)starty,
996                        (int)endx, (int)endy);
997            }
998        } else {
999            // draw unconditionally.
1000            graphics.drawLine((int)startx, (int)starty,
1001                    (int)endx, (int)endy);
1002        }
1003    }
1004
1005    /**
1006     * Put a mark corresponding to the specified dataset at the
1007     * specified x and y position. The mark is drawn in the current
1008     * color. What kind of mark is drawn depends on the _marks
1009     * variable and the dataset argument. If the fourth argument is
1010     * true, then check the range and plot only points that
1011     * are in range.
1012     */
1013    protected void _drawPoint(Graphics graphics,
1014            int dataset, long xpos, long ypos,
1015            boolean clip) {
1016        if (_debug > 20) {
1017            System.out.println("Plot:_drawPoint "+dataset+" "+xpos+
1018                    " "+ypos+" "+" "+clip);
1019        }
1020
1021        // If the point is not out of range, draw it.
1022        if (!clip || (ypos <= _lry && ypos >= _uly &&
1023                xpos <= _lrx && xpos >= _ulx)) {
1024            int xposi = (int)xpos;
1025            int yposi = (int)ypos;
1026            switch (_marks) {
1027            case 0:
1028                // If no mark style is given, draw a filled rectangle.
1029                // This is used, for example, to draw the legend.
1030                graphics.fillRect(xposi-6, yposi-6, 6, 6);
1031                break;
1032            case 1:
1033                // points -- use 3-pixel ovals.
1034                graphics.fillOval(xposi-1, yposi-1, 3, 3);
1035                break;
1036            case 2:
1037                // dots
1038                graphics.fillOval(xposi-_radius, yposi-_radius,
1039                        _diameter, _diameter);
1040                break;
1041            case 3:
1042                // marks
1043                int xpoints[], ypoints[];
1044                // Points are only distinguished up to _MAX_MARKS data sets.
1045                int mark = dataset % _MAX_MARKS;
1046                switch (mark) {
1047                case 0:
1048                    // filled circle
1049                    graphics.fillOval(xposi-_radius, yposi-_radius,
1050                            _diameter, _diameter);
1051                    break;
1052                case 1:
1053                    // cross
1054                    graphics.drawLine(xposi-_radius, yposi-_radius,
1055                            xposi+_radius, yposi+_radius);
1056                    graphics.drawLine(xposi+_radius, yposi-_radius,
1057                            xposi-_radius, yposi+_radius);
1058                    break;
1059                case 2:
1060                    // square
1061                    graphics.drawRect(xposi-_radius, yposi-_radius,
1062                            _diameter, _diameter);
1063                    break;
1064                case 3:
1065                    // filled triangle
1066                    xpoints = new int[4];
1067                    ypoints = new int[4];
1068                    xpoints[0] = xposi; ypoints[0] = yposi-_radius;
1069                    xpoints[1] = xposi+_radius; ypoints[1] = yposi+_radius;
1070                    xpoints[2] = xposi-_radius; ypoints[2] = yposi+_radius;
1071                    xpoints[3] = xposi; ypoints[3] = yposi-_radius;
1072                    graphics.fillPolygon(xpoints, ypoints, 4);
1073                    break;
1074                case 4:
1075                    // diamond
1076                    xpoints = new int[5];
1077                    ypoints = new int[5];
1078                    xpoints[0] = xposi; ypoints[0] = yposi-_radius;
1079                    xpoints[1] = xposi+_radius; ypoints[1] = yposi;
1080                    xpoints[2] = xposi; ypoints[2] = yposi+_radius;
1081                    xpoints[3] = xposi-_radius; ypoints[3] = yposi;
1082                    xpoints[4] = xposi; ypoints[4] = yposi-_radius;
1083                    graphics.drawPolygon(xpoints, ypoints, 5);
1084                    break;
1085                case 5:
1086                    // circle
1087                    graphics.drawOval(xposi-_radius, yposi-_radius,
1088                            _diameter, _diameter);
1089                    break;
1090                case 6:
1091                    // plus sign
1092                    graphics.drawLine(xposi, yposi-_radius, xposi,
1093                            yposi+_radius);
1094                    graphics.drawLine(xposi-_radius, yposi, xposi+_radius,
1095                            yposi);
1096                    break;
1097                case 7:
1098                    // filled square
1099                    graphics.fillRect(xposi-_radius, yposi-_radius,
1100                            _diameter, _diameter);
1101                    break;
1102                case 8:
1103                    // triangle
1104                    xpoints = new int[4];
1105                    ypoints = new int[4];
1106                    xpoints[0] = xposi; ypoints[0] = yposi-_radius;
1107                    xpoints[1] = xposi+_radius; ypoints[1] = yposi+_radius;
1108                    xpoints[2] = xposi-_radius; ypoints[2] = yposi+_radius;
1109                    xpoints[3] = xposi; ypoints[3] = yposi-_radius;
1110                    graphics.drawPolygon(xpoints, ypoints, 4);
1111                    break;
1112                case 9:
1113                    // filled diamond
1114                    xpoints = new int[5];
1115                    ypoints = new int[5];
1116                    xpoints[0] = xposi; ypoints[0] = yposi-_radius;
1117                    xpoints[1] = xposi+_radius; ypoints[1] = yposi;
1118                    xpoints[2] = xposi; ypoints[2] = yposi+_radius;
1119                    xpoints[3] = xposi-_radius; ypoints[3] = yposi;
1120                    xpoints[4] = xposi; ypoints[4] = yposi-_radius;
1121                    graphics.fillPolygon(xpoints, ypoints, 5);
1122                    break;
1123                }
1124                break;
1125            default:
1126                // none
1127            }
1128        }
1129    }
1130
1131    /** Hook for child classes to do any file preprocessing
1132     */ 
1133    protected void _newFile(){
1134        _filecount++;
1135        _firstinset = true;
1136        _sawfirstdataset = false;
1137    }   
1138
1139    /**
1140     * Read in a pxgraph format binary file.
1141     * @exception PlotDataException if there is a serious data format problem.
1142     * @exception java.io.IOException if an I/O error occurs.
1143     */ 
1144    protected void _parseBinaryStream(DataInputStream in) throws PlotDataException,  IOException {
1145        // This method is similar to _parseLine() below, except it parses
1146        // an entire file at a time.
1147        int c;
1148        float x, y;
1149        boolean connected = false;
1150        if (_connected) connected = true;
1151
1152        if (_debug > 8) {
1153            System.out.println("Plot: _parseBinaryStream _connected = "+
1154                    _connected);
1155        }
1156        try {
1157            c = in.readByte();
1158            if ( c != 'd') {
1159                // Assume that the data is one data set, consisting
1160                // of 4 byte floats.  None of the Ptolemy pxgraph
1161                // binary format extensions apply.
1162
1163                // Read 3 more bytes, create the x float.
1164                int bits = c;
1165                bits = bits << 8;
1166                bits += in.readByte();
1167                bits = bits << 8;
1168                bits += in.readByte();
1169                bits = bits << 8;
1170                bits += in.readByte();
1171
1172                x = Float.intBitsToFloat(bits);
1173                y = in.readFloat();
1174                connected = _addLegendIfNecessary(connected);
1175                addPoint(_currentdataset, x, y, connected);
1176                if (_connected) connected = true;
1177
1178                while (true) {
1179                    x = in.readFloat();
1180                    y = in.readFloat();
1181                    connected = _addLegendIfNecessary(connected);
1182                    addPoint(_currentdataset, x, y, connected);
1183                    if (_connected) connected = true;
1184                }
1185            } else {
1186                // Assume that the data is in the pxgraph binary format.
1187                while (true) {
1188                    // For speed reasons, the Ptolemy group extended
1189                    // pxgraph to read binary format data.
1190                    // The format consists of a command character,
1191                    // followed by optional arguments
1192                    // d <4byte float> <4byte float> - Draw a X,Y point
1193                    // e                             - End of a data set
1194                    // n <chars> \n                  - New set name, ends in \n
1195                    // m                             - Move to a point
1196
1197                    switch (c) {
1198                    case 'd':
1199                        {
1200                            // Data point.
1201                            x = in.readFloat();
1202                            y = in.readFloat();
1203                            connected = _addLegendIfNecessary(connected);
1204                            addPoint(_currentdataset, x, y, connected);
1205                            if (_connected) connected = true;
1206                        }
1207                        break;
1208                    case 'e':
1209                        // End of set name.
1210                        connected = false;
1211                        break;
1212                    case 'n':
1213                        {
1214                            StringBuffer datasetname = new StringBuffer();
1215                            _firstinset = true;
1216                            _sawfirstdataset = true;
1217                            // FIXME: we allow more than _numsets datasets here
1218                            _currentdataset++;
1219                            if (_currentdataset >= _MAX_MARKS)
1220                                _currentdataset = 0;
1221                            // New set name, ends in \n.
1222                            while (c != '\n')
1223                                datasetname.append(in.readChar());
1224                            addLegend(_currentdataset, datasetname.toString());
1225                            setConnected(true);
1226                        }
1227                        break;
1228                    case 'm':
1229                        // a disconnected point
1230                        connected = false;
1231                        break;
1232                    default:
1233                        throw new PlotDataException("Don't understand `" +
1234                                (char)c + "' character " +
1235                                "(decimal value = " + c +
1236                                ") in binary file");
1237                    }
1238                    c = in.readByte();
1239                }
1240            }
1241        } catch (EOFException e) {}         
1242    }
1243
1244    /**
1245     * Parse a line that gives plotting information. Return true if
1246     * the line is recognized.  Lines with syntax errors are ignored.
1247     */
1248    protected boolean _parseLine (String line) {
1249        boolean connected = false;
1250        if (_debug> 8) System.out.println("Plot: _parseLine " + line);
1251        if (_connected) connected = true;
1252        // parse only if the super class does not recognize the line.
1253        if (super._parseLine(line)) {
1254            return true;
1255        } else {
1256            // We convert the line to lower case so that the command
1257            // names are case insensitive
1258            String lcLine = new String(line.toLowerCase());
1259            if (lcLine.startsWith("marks:")) {
1260                String style = (line.substring(6)).trim();
1261                setMarksStyle(style);
1262                return true;
1263            } else if (lcLine.startsWith("numsets:")) {
1264                String num = (line.substring(8)).trim();
1265                try {
1266                    setNumSets(Integer.parseInt(num));
1267                }
1268                catch (NumberFormatException e) {
1269                    // ignore bogons
1270                }
1271                return true;
1272            } else if (lcLine.startsWith("dataset:")) {
1273                // new data set
1274                _firstinset = true;
1275                _sawfirstdataset = true;
1276                if (!_datasetoverflow)
1277                    _currentdataset++;
1278                // If we provide more data than _numsets, ignore it.
1279                if (_currentdataset >= _numsets || _datasetoverflow) {
1280                    // We need _datasetoverflow to stop from reading more
1281                    // datasets.  If we don't have it then we skip reading
1282                    // only one dataset, and then start reading again.
1283                    _datasetoverflow = true;
1284                    _currentdataset = -1;
1285                }
1286                String legend = (line.substring(8)).trim();
1287                addLegend(_currentdataset, legend);
1288                return true;
1289            } else if (lcLine.startsWith("lines:")) {
1290                if (lcLine.indexOf("off",6) >= 0) {
1291                    setConnected(false);
1292                } else {
1293                    setConnected(true);
1294                }
1295                return true;
1296            } else if (lcLine.startsWith("impulses:")) {
1297                if (lcLine.indexOf("off",9) >= 0) {
1298                    setImpulses(false);
1299                } else {
1300                    setImpulses(true);
1301                }
1302                return true;
1303            } else if (lcLine.startsWith("bars:")) {
1304                if (lcLine.indexOf("off",5) >= 0) {
1305                    setBars(false);
1306                } else {
1307                    setBars(true);
1308                    if (! _yRangeGiven) {
1309                        _yBottom = 0;
1310                    }
1311                    int comma = line.indexOf(",", 5);
1312                    String barwidth;
1313                    String baroffset = null;
1314                    if (comma > 0) {
1315                        barwidth = (line.substring(5, comma)).trim();
1316                        baroffset = (line.substring(comma+1)).trim();
1317                    } else {
1318                        barwidth = (line.substring(5)).trim();
1319                    }
1320                    try {
1321                        Double bwidth = new Double(barwidth);
1322                        double boffset = _baroffset;
1323                        if (baroffset != null) {
1324                            boffset = (new Double(baroffset)).
1325                                doubleValue();
1326                        }
1327                        setBars(bwidth.doubleValue(), boffset);
1328                    } catch (NumberFormatException e) {
1329                        // ignore if format is bogus.
1330                    }
1331                }
1332                return true;
1333            } else if (line.startsWith("move:")) {
1334                // a disconnected point
1335                connected = false;
1336                // deal with 'move: 1 2' and 'move:2 2'
1337                line = line.substring(5, line.length()).trim();
1338            } else if (line.startsWith("move")) {
1339                // a disconnected point
1340                connected = false;
1341                // deal with 'move 1 2' and 'move2 2'
1342                line = line.substring(4, line.length()).trim();
1343            } else if (line.startsWith("draw:")) {
1344                // a connected point, if connect is enabled.
1345                line = line.substring(5, line.length()).trim();
1346            } else if (line.startsWith("draw")) {
1347                // a connected point, if connect is enabled.
1348                line = line.substring(4, line.length()).trim();
1349            }
1350            line = line.trim();
1351
1352            // We can't use StreamTokenizer here because it can't
1353            // process numbers like 1E-01. 
1354            // This code is somewhat optimized for speed, since
1355            // most data consists of two data points, we want
1356            // to handle that case as efficiently as possible.
1357
1358            int fieldsplit = line.indexOf(",");
1359            if (fieldsplit == -1) {
1360                fieldsplit = line.indexOf(" ");
1361            }
1362            if (fieldsplit == -1) {
1363                fieldsplit = line.indexOf(" ");  // a tab
1364            }
1365
1366            if (fieldsplit > 0) {
1367                String x = (line.substring(0, fieldsplit)).trim();
1368                String y = (line.substring(fieldsplit+1)).trim();
1369                // Any more separators?
1370                int fieldsplit2 = y.indexOf(",");
1371                if (fieldsplit2 == -1) {
1372                        fieldsplit2 = y.indexOf(" ");
1373                    }
1374                if (fieldsplit2 == -1) {
1375                        fieldsplit2 = y.indexOf(" ");  // a tab
1376                }
1377                if (fieldsplit2 > 0) {
1378                    line = (y.substring(fieldsplit2+1)).trim();
1379                    y = (y.substring(0,fieldsplit2)).trim();
1380                }
1381                try {
1382                    Double xpt = new Double(x);
1383                    Double ypt = new Double(y);
1384                    if (fieldsplit2 > 0) {
1385                        // There was one separator after the y value, now
1386                        // look for another separator.
1387                        int fieldsplit3 = line.indexOf(",");
1388                        if (fieldsplit3 == -1) {
1389                            fieldsplit3 = line.indexOf(" ");
1390                        }
1391                        if (fieldsplit3 == -1) {
1392                            fieldsplit2 = line.indexOf(" ");  // a tab
1393                        }
1394
1395                        if (fieldsplit3 > 0) {
1396                            // We have more numbers, assume that this is
1397                            // an error bar
1398                            String yl = (line.substring(0,
1399                                    fieldsplit3)).trim();
1400                            String yh = (line.substring(fieldsplit3+1)).trim();
1401                            Double yLowEB = new Double(yl);
1402                            Double yHighEB = new Double(yh);
1403                            connected = _addLegendIfNecessary(connected);
1404                            addPointWithErrorBars(_currentdataset,
1405                                    xpt.doubleValue(),
1406                                    ypt.doubleValue(),
1407                                    yLowEB.doubleValue(),
1408                                    yHighEB.doubleValue(),
1409                                    connected);
1410                            return true;
1411                        } else {
1412                            // It is unlikely that we have a fieldsplit2 >0
1413                            // but not fieldsplit3 >0, but just in case:
1414
1415                            connected = _addLegendIfNecessary(connected);
1416                            addPoint(_currentdataset, xpt.doubleValue(),
1417                                    ypt.doubleValue(), connected);
1418                            return true;
1419                        }
1420                    } else {
1421                        // There were no more fields, so this is
1422                        // a regular pt.
1423                        connected = _addLegendIfNecessary(connected);
1424                        addPoint(_currentdataset, xpt.doubleValue(),
1425                                ypt.doubleValue(), connected);
1426                        return true;
1427                    }
1428                } catch (NumberFormatException e) {
1429                    // ignore if format is bogus.
1430                }
1431            }
1432        }
1433        return false;
1434    }
1435
1436    //////////////////////////////////////////////////////////////////////////
1437    ////                       protected variables                        ////
1438   
1439    // The current dataset.
1440    protected int _currentdataset = -1;
1441   
1442    // True if we tried to read in more datasets than the value of _numsets.
1443    protected boolean _datasetoverflow = false;
1444   
1445    // A vector of datasets.
1446    protected Vector[] _points;
1447
1448    // An indicator of the marks style.  See _parseLine method for
1449    // interpretation.
1450    protected int _marks;
1451   
1452    protected int _numsets = _MAX_DATASETS;
1453
1454    //////////////////////////////////////////////////////////////////////////
1455    ////                       private methods                            ////
1456
1457    /* Add a legend if necessary, return the value of the connected flag.
1458     */
1459    private boolean _addLegendIfNecessary(boolean connected) {
1460        if (_datasetoverflow) return false;
1461        if (! _sawfirstdataset  || _currentdataset < 0) {
1462            // We did not set a DataSet line, but
1463            // we did get called with -<digit> args
1464            _sawfirstdataset = true;
1465            _currentdataset++;
1466        }
1467        if (_debug > 14) {
1468            System.out.println("Plot _addLegendIfNecessary( "+connected+" ) "+
1469                    + (_filecount) + " " + (_currentdataset) +
1470                    "<"+getLegend(0)+">");
1471        }
1472        if (getLegend(_currentdataset) == null) {
1473            // We did not see a "DataSet" string yet,
1474            // nor did we call addLegend().
1475            _firstinset = true;
1476            _sawfirstdataset = true;
1477            addLegend(_currentdataset,
1478                    new String("Set "+ _currentdataset));
1479        }
1480        if (_firstinset) {
1481            connected = false;
1482            _firstinset = false;
1483        }
1484        return connected;
1485    }
1486   
1487    /* In the specified data set, add the specified x,y point to the
1488     * plot.  Data set indices begin with zero.  If the dataset
1489     * argument is out of range, ignore.  The number of data sets is
1490     * given by calling *setNumSets()*.  The fourth argument indicates
1491     * whether the point should be connected by a line to the previous
1492     * point.
1493     */
1494    private synchronized void _addPoint(Graphics graphics,
1495            int dataset, double x, double y, double yLowEB, double yHighEB,
1496            boolean connected, boolean errorBar) {
1497        if (_debug > 100) {
1498            System.out.println("Plot: addPoint " + dataset + " "+
1499                    x+" "+y+" "+connected+" "+yLowEB+" "+yHighEB+" "+errorBar);
1500        }
1501        if (dataset >= _numsets || dataset < 0 || _datasetoverflow) return;
1502       
1503        // For auto-ranging, keep track of min and max.
1504        if (x < _xBottom) _xBottom = x;
1505        if (x > _xTop) _xTop = x;
1506        if (y < _yBottom) _yBottom = y;
1507        if (y > _yTop) _yTop = y;
1508
1509        // FIXME: Ignoring sweeps for now.
1510        PlotPoint pt = new PlotPoint();
1511        pt.x = x;
1512        pt.y = y;
1513        pt.connected = connected;
1514       
1515        if (errorBar) {
1516            if (yLowEB < _yBottom) _yBottom = yLowEB;
1517            if (yLowEB > _yTop) _yTop = yLowEB;
1518            if (yHighEB < _yBottom) _yBottom = yHighEB;
1519            if (yHighEB > _yTop) _yTop = yHighEB;
1520            pt.yLowEB = yLowEB;
1521            pt.yHighEB = yHighEB;
1522            pt.errorBar = true;
1523        }
1524
1525        Vector pts = _points[dataset];
1526        pts.addElement(pt);
1527        if (_pointsPersistence > 0) {
1528            if (pts.size() > _pointsPersistence) erasePoint(dataset,0);
1529        }
1530        _drawPlotPoint(graphics, dataset, pts.size() - 1);
1531    }
1532
1533    /* Draw the specified point and associated lines, if any.
1534     */
1535    private synchronized void _drawPlotPoint(Graphics graphics,
1536            int dataset, int index) {
1537        if (_debug > 20)
1538            System.out.println("Plot: _drawPlotPoint("+dataset+" "+index+")");
1539        // Set the color
1540        if (_pointsPersistence > 0) {
1541            // To allow erasing to work by just redrawing the points.
1542            graphics.setXORMode(_background);
1543        }
1544        if (_graphics == null) {
1545            System.out.println("Plot::_drawPlotPoint(): Internal error: " +
1546                    "_graphic was null, be sure to call show()\n"+
1547                    "before calling init()");
1548        }
1549
1550        if (_usecolor) {
1551            int color = dataset % _colors.length;
1552            graphics.setColor(_colors[color]);
1553        } else {
1554            graphics.setColor(_foreground);
1555        }
1556
1557        Vector pts = _points[dataset];
1558        PlotPoint pt = (PlotPoint)pts.elementAt(index);
1559        // Use long here because these numbers can be quite large
1560        // (when we are zoomed out a lot).
1561        long ypos = _lry - (long)((pt.y - _yMin) * _yscale);
1562        long xpos = _ulx + (long)((pt.x - _xMin) * _xscale);
1563
1564        // Draw the line to the previous point.
1565        if (pt.connected) _drawLine(graphics, dataset, xpos, ypos,
1566                _prevx[dataset], _prevy[dataset], true);
1567
1568        // Save the current point as the "previous" point for future
1569        // line drawing.
1570        _prevx[dataset] = xpos;
1571        _prevy[dataset] = ypos;
1572
1573        // Draw the point & associated decorations, if appropriate.
1574        if (_marks != 0) _drawPoint(graphics, dataset, xpos, ypos, true);
1575        if (_impulses) _drawImpulse(graphics, xpos, ypos, true);
1576        if (_bars) _drawBar(graphics, dataset, xpos, ypos, true);
1577        if (pt.errorBar)
1578            _drawErrorBar(graphics, dataset, xpos,
1579                _lry - (long)((pt.yLowEB - _yMin) * _yscale),
1580                _lry - (long)((pt.yHighEB - _yMin) * _yscale), true);
1581
1582        // Restore the color, in case the box gets redrawn.
1583        graphics.setColor(_foreground);
1584        if (_pointsPersistence > 0) {
1585            // Restore paint mode in case axes get redrawn.
1586            graphics.setPaintMode();
1587        }
1588    }
1589   
1590    /**
1591     * Erase the point at the given index in the given dataset.  If
1592     * lines are being drawn, also erase the line to the next points
1593     * (note: not to the previous point).
1594     */
1595    private synchronized void _erasePoint(Graphics graphics,
1596            int dataset, int index) {
1597        // Set the color
1598        if (_pointsPersistence > 0) {
1599            // To allow erasing to work by just redrawing the points.
1600            graphics.setXORMode(_background);
1601        }
1602        if (_usecolor) {
1603            int color = dataset % _colors.length;
1604            graphics.setColor(_colors[color]);
1605        } else {
1606            graphics.setColor(_foreground);
1607        }
1608
1609        Vector pts = _points[dataset];
1610        PlotPoint pt = (PlotPoint)pts.elementAt(index);
1611        long ypos = _lry - (long) ((pt.y - _yMin) * _yscale);
1612        long xpos = _ulx + (long) ((pt.x - _xMin) * _xscale);
1613
1614        // Erase line to the next point, if appropriate.
1615        if (index < pts.size() - 1) {
1616            PlotPoint nextp = (PlotPoint)pts.elementAt(index+1);
1617            int nextx = _ulx + (int) ((nextp.x - _xMin) * _xscale);
1618            int nexty = _lry - (int) ((nextp.y - _yMin) * _yscale);
1619            // NOTE: I have no idea why I have to give this point backwards.
1620            if (nextp.connected) _drawLine(graphics, dataset,
1621                    nextx, nexty,  xpos, ypos, true);
1622            nextp.connected = false;
1623        }
1624
1625        // Draw the point & associated lines, if appropriate.
1626        if (_marks != 0) _drawPoint(graphics, dataset, xpos, ypos, true);
1627        if (_impulses) _drawImpulse(graphics, xpos, ypos, true);
1628        if (_bars) _drawBar(graphics, dataset, xpos, ypos, true);
1629        if (pt.errorBar)
1630            _drawErrorBar(graphics, dataset, xpos,
1631                _lry - (long)((pt.yLowEB - _yMin) * _yscale),
1632                _lry - (long)((pt.yHighEB - _yMin) * _yscale), true);
1633
1634        // Restore the color, in case the box gets redrawn.
1635        graphics.setColor(_foreground);
1636        if (_pointsPersistence > 0) {
1637            // Restore paint mode in case axes get redrawn.
1638            graphics.setPaintMode();
1639        }
1640
1641        pts.removeElementAt(index);
1642    }
1643
1644    //////////////////////////////////////////////////////////////////////////
1645    ////                       private variables                          ////
1646   
1647    private int _pointsPersistence = 0;
1648    private int _sweepsPersistence = 0;
1649    private boolean _bars = false;
1650    private double _barwidth = 0.5;
1651    private double _baroffset = 0.05;
1652    private boolean _connected = true;
1653    private boolean _impulses = false;
1654    private boolean _firstinset = true; // Is this the first datapoint in a set
1655    private int _filecount = 0;         // Number of files read in.
1656    // Have we seen a DataSet line in the current data file?
1657    private boolean _sawfirstdataset = false;
1658   
1659    // Give both radius and diameter of a point for efficiency.
1660    private int _radius = 3;
1661    private int _diameter = 6;
1662   
1663    private Vector _dataurls = null;
1664
1665    // Information about the previously plotted point.
1666    private long _prevx[], _prevy[];
1667
1668    // Half of the length of the error bar horizontal leg length;
1669    private static final int _ERRORBAR_LEG_LENGTH = 5;
1670
1671    // Maximum number of _datasets.
1672    private static final int _MAX_DATASETS = 63;
1673
1674    // Maximum number of different marks
1675    // NOTE: There are 11 colors in the base class.  Combined with 10
1676    // marks, that makes 110 unique signal identities.
1677    private static final int _MAX_MARKS = 10;
1678}
Note: See TracBrowser for help on using the browser.