Back to index

wims  3.65+svn20090927
Plot.java
Go to the documentation of this file.
00001 /* A signal plotter.
00002 
00003  @Copyright (c) 1997-2007 The Regents of the University of California.
00004  All rights reserved.
00005 
00006  Permission is hereby granted, without written agreement and without
00007  license or royalty fees, to use, copy, modify, and distribute this
00008  software and its documentation for any purpose, provided that the
00009  above copyright notice and the following two paragraphs appear in all
00010  copies of this software.
00011 
00012  IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
00013  FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
00014  ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
00015  THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
00016  SUCH DAMAGE.
00017 
00018  THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
00019  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
00020  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
00021  PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
00022  CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
00023  ENHANCEMENTS, OR MODIFICATIONS.
00024 
00025  PT_COPYRIGHT_VERSION_2
00026  COPYRIGHTENDKEY
00027  */
00028 package ptolemy.plot;
00029 
00030 // TO DO:
00031 //   - steps between points rather than connected lines.
00032 //   - cubic spline interpolation
00033 //
00034 // NOTE: The XOR drawing mode is needed in order to be able to erase
00035 // plotted points and restore the grid line, tick marks, and boundary
00036 // rectangle.  This introduces a number of artifacts, particularly
00037 // where lines cross.  A better alternative in the long run would be
00038 // use Java 2-D, which treats each notation on the screen as an object,
00039 // and supports redrawing only damaged regions of the screen.
00040 // NOTE: There are quite a few subjective spacing parameters, all
00041 // given, unfortunately, in pixels.  This means that as resolutions
00042 // get better, this program may need to be adjusted.
00043 import java.awt.BasicStroke;
00044 import java.awt.Component;
00045 import java.awt.Graphics;
00046 import java.awt.Graphics2D;
00047 import java.awt.Rectangle;
00048 import java.io.IOException;
00049 import java.io.InputStream;
00050 import java.io.PrintWriter;
00051 import java.io.Serializable;
00052 import java.net.URL;
00053 import java.util.Enumeration;
00054 import java.util.Vector;
00055 
00056 import javax.swing.JComponent;
00057 
00060 
00212 public class Plot extends PlotBox {
00215 
00221     public synchronized void addLegend(int dataset, String legend) {
00222         _checkDatasetIndex(dataset);
00223 
00224         if (!_reuseDatasets) {
00225             super.addLegend(dataset, legend);
00226         } else {
00227             // If _reuseDataSets is true, then look to see if we
00228             // already have a dataset with the same legend.
00229             String possibleLegend = getLegend(dataset);
00230 
00231             if ((possibleLegend == null)
00232                     || ((possibleLegend != null) && !possibleLegend
00233                             .equals(legend))) {
00234                 super.addLegend(dataset, legend);
00235             }
00236         }
00237     }
00238 
00261     public synchronized void addPoint(final int dataset, final double x,
00262             final double y, final boolean connected) {
00263         Runnable doAddPoint = new Runnable() {
00264             public void run() {
00265                 _addPoint(dataset, x, y, 0, 0, connected, false);
00266             }
00267         };
00268 
00269         deferIfNecessary(doAddPoint);
00270     }
00271 
00300     public synchronized void addPointWithErrorBars(final int dataset,
00301             final double x, final double y, final double yLowEB,
00302             final double yHighEB, final boolean connected) {
00303         Runnable doAddPoint = new Runnable() {
00304             public void run() {
00305                 _addPoint(dataset, x, y, yLowEB, yHighEB, connected, true);
00306             }
00307         };
00308 
00309         deferIfNecessary(doAddPoint);
00310     }
00311 
00326     public synchronized void clear(final boolean format) {
00327         Runnable doClear = new Runnable() {
00328             public void run() {
00329                 _clear(format);
00330             }
00331         };
00332 
00333         deferIfNecessary(doClear);
00334     }
00335 
00349     public synchronized void clear(final int dataset) {
00350         Runnable doClear = new Runnable() {
00351             public void run() {
00352                 _clear(dataset);
00353             }
00354         };
00355 
00356         deferIfNecessary(doClear);
00357     }
00358 
00376     public synchronized void erasePoint(final int dataset, final int index) {
00377         Runnable doErasePoint = new Runnable() {
00378             public void run() {
00379                 _erasePoint(dataset, index);
00380             }
00381         };
00382 
00383         deferIfNecessary(doErasePoint);
00384     }
00385 
00400     public synchronized void fillPlot() {
00401         Runnable doFill = new Runnable() {
00402             public void run() {
00403                 _fillPlot();
00404             }
00405         };
00406 
00407         deferIfNecessary(doFill);
00408     }
00409 
00417     public boolean getConnected() {
00418         return _connected;
00419     }
00420 
00425     public boolean getImpulses() {
00426         return _impulses;
00427     }
00428 
00433     public synchronized String getMarksStyle() {
00434         // NOTE: If the number of marks increases, we will need to do
00435         // something better here...
00436         if (_marks == 0) {
00437             return "none";
00438         } else if (_marks == 1) {
00439             return "points";
00440         } else if (_marks == 2) {
00441             return "dots";
00442         } else if (_marks == 3) {
00443             return "various";
00444         } else {
00445             return "pixels";
00446         }
00447     }
00448 
00453     public int getMaxDataSets() {
00454         return Integer.MAX_VALUE;
00455     }
00456 
00460     public synchronized int getNumDataSets() {
00461         return _points.size();
00462     }
00463 
00471     public boolean getReuseDatasets() {
00472         return _reuseDatasets;
00473     }
00474 
00481     public void parseFile(String filespec, URL documentBase) {
00482         _firstInSet = true;
00483         _sawFirstDataSet = false;
00484         super.parseFile(filespec, documentBase);
00485     }
00486 
00493     public synchronized void read(InputStream inputStream) throws IOException {
00494         super.read(inputStream);
00495         _firstInSet = true;
00496         _sawFirstDataSet = false;
00497     }
00498 
00506     public synchronized void samplePlot() {
00507         // This needs to be done in the event thread.
00508         Runnable sample = new Runnable() {
00509             public void run() {
00510                 synchronized (Plot.this) {
00511                     // Create a sample plot.
00512                     clear(true);
00513 
00514                     setTitle("Sample plot");
00515                     setYRange(-4, 4);
00516                     setXRange(0, 100);
00517                     setXLabel("time");
00518                     setYLabel("value");
00519                     addYTick("-PI", -Math.PI);
00520                     addYTick("-PI/2", -Math.PI / 2);
00521                     addYTick("0", 0);
00522                     addYTick("PI/2", Math.PI / 2);
00523                     addYTick("PI", Math.PI);
00524                     setMarksStyle("none");
00525                     setImpulses(true);
00526 
00527                     boolean first = true;
00528 
00529                     for (int i = 0; i <= 100; i++) {
00530                         double xvalue = i;
00531 
00532                         // NOTE: jdk 1.3beta has a bug exhibited here.
00533                         // The value of the second argument in the calls
00534                         // to addPoint() below is corrupted the second
00535                         // time that this method is called.  The print
00536                         // statement below shows that the value is
00537                         // correct before the call.
00538                         // System.out.println("x value: " + xvalue);
00539                         // For some bizarre reason, this problem goes
00540                         // away when this code is executed in the event
00541                         // dispatch thread.
00542                         addPoint(0, xvalue, 5 * Math.cos((Math.PI * i) / 20),
00543                                 !first);
00544                         addPoint(1, xvalue, 4.5 * Math.cos((Math.PI * i) / 25),
00545                                 !first);
00546                         addPoint(2, xvalue, 4 * Math.cos((Math.PI * i) / 30),
00547                                 !first);
00548                         addPoint(3, xvalue, 3.5 * Math.cos((Math.PI * i) / 35),
00549                                 !first);
00550                         addPoint(4, xvalue, 3 * Math.cos((Math.PI * i) / 40),
00551                                 !first);
00552                         addPoint(5, xvalue, 2.5 * Math.cos((Math.PI * i) / 45),
00553                                 !first);
00554                         addPoint(6, xvalue, 2 * Math.cos((Math.PI * i) / 50),
00555                                 !first);
00556                         addPoint(7, xvalue, 1.5 * Math.cos((Math.PI * i) / 55),
00557                                 !first);
00558                         addPoint(8, xvalue, 1 * Math.cos((Math.PI * i) / 60),
00559                                 !first);
00560                         addPoint(9, xvalue, 0.5 * Math.cos((Math.PI * i) / 65),
00561                                 !first);
00562                         first = false;
00563                     } // for
00564                 } // synchronized
00565 
00566                 repaint();
00567             } // run method
00568         }; // Runnable class
00569 
00570         deferIfNecessary(sample);
00571     }
00572 
00577     public void setBars(boolean on) {
00578         // Ensure replot of offscreen buffer.
00579         _plotImage = null;
00580         _bars = on;
00581     }
00582 
00590     public synchronized void setBars(double width, double offset) {
00591         // Ensure replot of offscreen buffer.
00592         _plotImage = null;
00593         barWidth = width;
00594         _barOffset = offset;
00595         _bars = true;
00596     }
00597 
00607     public void setConnected(boolean on) {
00608         // Ensure replot of offscreen buffer.
00609         _plotImage = null;
00610         _connected = on;
00611     }
00612 
00625     public synchronized void setConnected(boolean on, int dataset) {
00626         // Ensure replot of offscreen buffer.
00627         _plotImage = null;
00628         _checkDatasetIndex(dataset);
00629 
00630         Format fmt = (Format) _formats.elementAt(dataset);
00631         fmt.connected = on;
00632         fmt.connectedUseDefault = false;
00633     }
00634 
00640     public synchronized void setImpulses(boolean on) {
00641         // Ensure replot of offscreen buffer.
00642         _plotImage = null;
00643         _impulses = on;
00644     }
00645 
00653     public synchronized void setImpulses(boolean on, int dataset) {
00654         // Ensure replot of offscreen buffer.
00655         _plotImage = null;
00656         _checkDatasetIndex(dataset);
00657 
00658         Format fmt = (Format) _formats.elementAt(dataset);
00659         fmt.impulses = on;
00660         fmt.impulsesUseDefault = false;
00661     }
00662 
00669     public synchronized void setMarksStyle(String style) {
00670         // Ensure replot of offscreen buffer.
00671         _plotImage = null;
00672 
00673         if (style.equalsIgnoreCase("none")) {
00674             _marks = 0;
00675         } else if (style.equalsIgnoreCase("points")) {
00676             _marks = 1;
00677         } else if (style.equalsIgnoreCase("dots")) {
00678             _marks = 2;
00679         } else if (style.equalsIgnoreCase("various")) {
00680             _marks = 3;
00681         } else if (style.equalsIgnoreCase("pixels")) {
00682             _marks = 4;
00683         }
00684     }
00685 
00693     public synchronized void setMarksStyle(String style, int dataset) {
00694         // Ensure replot of offscreen buffer.
00695         _plotImage = null;
00696         _checkDatasetIndex(dataset);
00697 
00698         Format fmt = (Format) _formats.elementAt(dataset);
00699 
00700         if (style.equalsIgnoreCase("none")) {
00701             fmt.marks = 0;
00702         } else if (style.equalsIgnoreCase("points")) {
00703             fmt.marks = 1;
00704         } else if (style.equalsIgnoreCase("dots")) {
00705             fmt.marks = 2;
00706         } else if (style.equalsIgnoreCase("various")) {
00707             fmt.marks = 3;
00708         } else if (style.equalsIgnoreCase("pixels")) {
00709             fmt.marks = 4;
00710         }
00711 
00712         fmt.marksUseDefault = false;
00713     }
00714 
00721     public void setNumSets(int numSets) {
00722         // Ensure replot of offscreen buffer.
00723         _plotImage = null;
00724 
00725         if (numSets < 1) {
00726             throw new IllegalArgumentException("Number of data sets ("
00727                     + numSets + ") must be greater than 0.");
00728         }
00729 
00730         _currentdataset = -1;
00731         _points.removeAllElements();
00732         _formats.removeAllElements();
00733         _prevx.removeAllElements();
00734         _prevy.removeAllElements();
00735 
00736         for (int i = 0; i < numSets; i++) {
00737             _points.addElement(new Vector());
00738             _formats.addElement(new Format());
00739             _prevx.addElement(_initialPreviousValue);
00740             _prevy.addElement(_initialPreviousValue);
00741         }
00742     }
00743 
00760     public void setPointsPersistence(int persistence) {
00761         // Ensure replot of offscreen buffer.
00762         _plotImage = null;
00763 
00764         // NOTE: No file format.  It's not clear it makes sense to have one.
00765         _pointsPersistence = persistence;
00766     }
00767 
00773     public void setReuseDatasets(boolean on) {
00774         // Ensure replot of offscreen buffer.
00775         _plotImage = null;
00776         _reuseDatasets = on;
00777     }
00778 
00797     public void setXPersistence(double persistence) {
00798         // Ensure replot of offscreen buffer.
00799         _plotImage = null;
00800 
00801         // NOTE: No file format.  It's not clear it makes sense to have one.
00802         _xPersistence = persistence;
00803     }
00804 
00808     public synchronized void writeData(PrintWriter output) {
00809         super.writeData(output);
00810 
00811         for (int dataset = 0; dataset < _points.size(); dataset++) {
00812             StringBuffer options = new StringBuffer();
00813 
00814             Format fmt = (Format) _formats.elementAt(dataset);
00815 
00816             if (!fmt.connectedUseDefault) {
00817                 if (_isConnected(dataset)) {
00818                     options.append(" connected=\"yes\"");
00819                 } else {
00820                     options.append(" connected=\"no\"");
00821                 }
00822             }
00823 
00824             if (!fmt.impulsesUseDefault) {
00825                 if (fmt.impulses) {
00826                     options.append(" stems=\"yes\"");
00827                 } else {
00828                     output.println(" stems=\"no\"");
00829                 }
00830             }
00831 
00832             if (!fmt.marksUseDefault) {
00833                 switch (fmt.marks) {
00834                 case 0:
00835                     options.append(" marks=\"none\"");
00836                     break;
00837 
00838                 case 1:
00839                     options.append(" marks=\"points\"");
00840                     break;
00841 
00842                 case 2:
00843                     options.append(" marks=\"dots\"");
00844                     break;
00845 
00846                 case 3:
00847                     options.append(" marks=\"various\"");
00848                     break;
00849 
00850                 case 4:
00851                     options.append(" marks=\"pixels\"");
00852                     break;
00853                 }
00854             }
00855 
00856             String legend = getLegend(dataset);
00857 
00858             if (legend != null) {
00859                 options.append(" name=\"" + getLegend(dataset) + "\"");
00860             }
00861 
00862             output.println("<dataset" + options.toString() + ">");
00863 
00864             // Write the data
00865             Vector pts = (Vector) _points.elementAt(dataset);
00866 
00867             for (int pointnum = 0; pointnum < pts.size(); pointnum++) {
00868                 PlotPoint pt = (PlotPoint) pts.elementAt(pointnum);
00869 
00870                 if (!pt.connected) {
00871                     output.print("<m ");
00872                 } else {
00873                     output.print("<p ");
00874                 }
00875 
00876                 output.print("x=\"" + pt.x + "\" y=\"" + pt.y + "\"");
00877 
00878                 if (pt.errorBar) {
00879                     output.print(" lowErrorBar=\"" + pt.yLowEB
00880                             + "\" highErrorBar=\"" + pt.yHighEB + "\"");
00881                 }
00882 
00883                 output.println("/>");
00884             }
00885 
00886             output.println("</dataset>");
00887         }
00888     }
00889 
00894     public synchronized void writeFormat(PrintWriter output) {
00895         super.writeFormat(output);
00896 
00897         if (_reuseDatasets) {
00898             output.println("<reuseDatasets/>");
00899         }
00900 
00901         StringBuffer defaults = new StringBuffer();
00902 
00903         if (!_connected) {
00904             defaults.append(" connected=\"no\"");
00905         }
00906 
00907         switch (_marks) {
00908         case 1:
00909             defaults.append(" marks=\"points\"");
00910             break;
00911 
00912         case 2:
00913             defaults.append(" marks=\"dots\"");
00914             break;
00915 
00916         case 3:
00917             defaults.append(" marks=\"various\"");
00918             break;
00919 
00920         case 4:
00921             defaults.append(" marks=\"pixels\"");
00922             break;
00923         }
00924 
00925         // Write the defaults for formats that can be controlled by dataset
00926         if (_impulses) {
00927             defaults.append(" stems=\"yes\"");
00928         }
00929 
00930         if (defaults.length() > 0) {
00931             output.println("<default" + defaults.toString() + "/>");
00932         }
00933 
00934         if (_bars) {
00935             output.println("<barGraph width=\"" + barWidth + "\" offset=\""
00936                     + _barOffset + "\"/>");
00937         }
00938     }
00939 
00942 
00951     protected synchronized void _checkDatasetIndex(int dataset) {
00952         if (dataset < 0) {
00953             throw new IllegalArgumentException(
00954                     "Plot._checkDatasetIndex: Cannot"
00955                             + " give a negative number for the data set index.");
00956         }
00957 
00958         while (dataset >= _points.size()) {
00959             _points.addElement(new Vector());
00960             _formats.addElement(new Format());
00961             _prevx.addElement(_initialPreviousValue);
00962             _prevy.addElement(_initialPreviousValue);
00963         }
00964     }
00965 
00980     protected void _drawBar(Graphics graphics, int dataset, long xpos,
00981             long ypos, boolean clip) {
00982         if (clip) {
00983             if (ypos < _uly) {
00984                 ypos = _uly;
00985             }
00986 
00987             if (ypos > _lry) {
00988                 ypos = _lry;
00989             }
00990         }
00991 
00992         if ((ypos <= _lry) && (xpos <= _lrx) && (xpos >= _ulx)) {
00993             // left x position of bar.
00994             int barlx = (int) (xpos - ((barWidth * _xscale) / 2) + (dataset
00995                     * _barOffset * _xscale));
00996 
00997             // right x position of bar
00998             int barrx = (int) (barlx + (barWidth * _xscale));
00999 
01000             if (barlx < _ulx) {
01001                 barlx = _ulx;
01002             }
01003 
01004             if (barrx > _lrx) {
01005                 barrx = _lrx;
01006             }
01007 
01008             // Make sure that a bar is always at least one pixel wide.
01009             if (barlx >= barrx) {
01010                 barrx = barlx + 1;
01011             }
01012 
01013             // The y position of the zero line.
01014             long zeroypos = _lry - (long) ((0 - _yMin) * _yscale);
01015 
01016             if (_lry < zeroypos) {
01017                 zeroypos = _lry;
01018             }
01019 
01020             if (_uly > zeroypos) {
01021                 zeroypos = _uly;
01022             }
01023 
01024             if ((_yMin >= 0) || (ypos <= zeroypos)) {
01025                 graphics.fillRect(barlx, (int) ypos, barrx - barlx,
01026                         (int) (zeroypos - ypos));
01027             } else {
01028                 graphics.fillRect(barlx, (int) zeroypos, barrx - barlx,
01029                         (int) (ypos - zeroypos));
01030             }
01031         }
01032     }
01033 
01047     protected void _drawErrorBar(Graphics graphics, int dataset, long xpos,
01048             long yLowEBPos, long yHighEBPos, boolean clip) {
01049         _drawLine(graphics, dataset, xpos - _ERRORBAR_LEG_LENGTH, yHighEBPos,
01050                 xpos + _ERRORBAR_LEG_LENGTH, yHighEBPos, clip);
01051         _drawLine(graphics, dataset, xpos, yLowEBPos, xpos, yHighEBPos, clip);
01052         _drawLine(graphics, dataset, xpos - _ERRORBAR_LEG_LENGTH, yLowEBPos,
01053                 xpos + _ERRORBAR_LEG_LENGTH, yLowEBPos, clip);
01054     }
01055 
01067     protected void _drawImpulse(Graphics graphics, long xpos, long ypos,
01068             boolean clip) {
01069         if (clip) {
01070             if (ypos < _uly) {
01071                 ypos = _uly;
01072             }
01073 
01074             if (ypos > _lry) {
01075                 ypos = _lry;
01076             }
01077         }
01078 
01079         if ((ypos <= _lry) && (xpos <= _lrx) && (xpos >= _ulx)) {
01080             // The y position of the zero line.
01081             double zeroypos = _lry - (long) ((0 - _yMin) * _yscale);
01082 
01083             if (_lry < zeroypos) {
01084                 zeroypos = _lry;
01085             }
01086 
01087             if (_uly > zeroypos) {
01088                 zeroypos = _uly;
01089             }
01090 
01091             _setWidth(graphics, 1f);
01092             graphics.drawLine((int) xpos, (int) ypos, (int) xpos,
01093                     (int) zeroypos);
01094         }
01095     }
01096 
01111     protected void _drawLine(Graphics graphics, int dataset, long startx,
01112             long starty, long endx, long endy, boolean clip) {
01113         _drawLine(graphics, dataset, startx, starty, endx, endy, clip, 1f);
01114     }
01115 
01132     protected void _drawLine(Graphics graphics, int dataset, long startx,
01133             long starty, long endx, long endy, boolean clip, float width) {
01134         _setWidth(graphics, width);
01135 
01136         if (clip) {
01137             // Rule out impossible cases.
01138             if (!(((endx <= _ulx) && (startx <= _ulx))
01139                     || ((endx >= _lrx) && (startx >= _lrx))
01140                     || ((endy <= _uly) && (starty <= _uly)) || ((endy >= _lry) && (starty >= _lry)))) {
01141                 // If the end point is out of x range, adjust
01142                 // end point to boundary.
01143                 // The integer arithmetic has to be done with longs so as
01144                 // to not loose precision on extremely close zooms.
01145                 if (startx != endx) {
01146                     if (endx < _ulx) {
01147                         endy = (int) (endy + (((starty - endy) * (_ulx - endx)) / (startx - endx)));
01148                         endx = _ulx;
01149                     } else if (endx > _lrx) {
01150                         endy = (int) (endy + (((starty - endy) * (_lrx - endx)) / (startx - endx)));
01151                         endx = _lrx;
01152                     }
01153                 }
01154 
01155                 // If end point is out of y range, adjust to boundary.
01156                 // Note that y increases downward
01157                 if (starty != endy) {
01158                     if (endy < _uly) {
01159                         endx = (int) (endx + (((startx - endx) * (_uly - endy)) / (starty - endy)));
01160                         endy = _uly;
01161                     } else if (endy > _lry) {
01162                         endx = (int) (endx + (((startx - endx) * (_lry - endy)) / (starty - endy)));
01163                         endy = _lry;
01164                     }
01165                 }
01166 
01167                 // Adjust current point to lie on the boundary.
01168                 if (startx != endx) {
01169                     if (startx < _ulx) {
01170                         starty = (int) (starty + (((endy - starty) * (_ulx - startx)) / (endx - startx)));
01171                         startx = _ulx;
01172                     } else if (startx > _lrx) {
01173                         starty = (int) (starty + (((endy - starty) * (_lrx - startx)) / (endx - startx)));
01174                         startx = _lrx;
01175                     }
01176                 }
01177 
01178                 if (starty != endy) {
01179                     if (starty < _uly) {
01180                         startx = (int) (startx + (((endx - startx) * (_uly - starty)) / (endy - starty)));
01181                         starty = _uly;
01182                     } else if (starty > _lry) {
01183                         startx = (int) (startx + (((endx - startx) * (_lry - starty)) / (endy - starty)));
01184                         starty = _lry;
01185                     }
01186                 }
01187             }
01188 
01189             // Are the new points in range?
01190             if ((endx >= _ulx) && (endx <= _lrx) && (endy >= _uly)
01191                     && (endy <= _lry) && (startx >= _ulx) && (startx <= _lrx)
01192                     && (starty >= _uly) && (starty <= _lry)) {
01193                 graphics.drawLine((int) startx, (int) starty, (int) endx,
01194                         (int) endy);
01195             }
01196         } else {
01197             // draw unconditionally.
01198             graphics.drawLine((int) startx, (int) starty, (int) endx,
01199                     (int) endy);
01200         }
01201     }
01202 
01217     protected synchronized void _drawPlot(Graphics graphics,
01218             boolean clearfirst, Rectangle drawRectangle) {
01219         if (_graphics == null) {
01220             _graphics = graphics;
01221         } else if (graphics != _graphics) {
01222             // If the graphics has changed, then we don't care about
01223             // the previous values.  Exporting to EPS uses a different
01224             // graphics, see test/onePointStem.plt for an example that
01225             // requires this change.
01226             _graphics = graphics;
01227             _prevx = new Vector();
01228             _prevy = new Vector();
01229             for (int dataset = 0; dataset < _points.size(); dataset++) {
01230                 _prevx.addElement(_initialPreviousValue);
01231                 _prevy.addElement(_initialPreviousValue);
01232             }
01233         }
01234 
01235         // We must call PlotBox._drawPlot() before calling _drawPlotPoint
01236         // so that _xscale and _yscale are set.
01237         super._drawPlot(graphics, clearfirst, drawRectangle);
01238 
01239         // Plot the points in reverse order so that the first colors
01240         // appear on top.
01241         for (int dataset = _points.size() - 1; dataset >= 0; dataset--) {
01242             Vector data = (Vector) _points.elementAt(dataset);
01243 
01244             for (int pointnum = 0; pointnum < data.size(); pointnum++) {
01245                 _drawPlotPoint(graphics, dataset, pointnum);
01246             }
01247         }
01248 
01249         _showing = true;
01250     }
01251 
01266     protected void _drawPoint(Graphics graphics, int dataset, long xpos,
01267             long ypos, boolean clip) {
01268         // If the point is not out of range, draw it.
01269         boolean pointinside = (ypos <= _lry) && (ypos >= _uly)
01270                 && (xpos <= _lrx) && (xpos >= _ulx);
01271 
01272         if (!clip || pointinside) {
01273             int xposi = (int) xpos;
01274             int yposi = (int) ypos;
01275 
01276             // Check to see whether the dataset has a marks directive
01277             Format fmt = (Format) _formats.elementAt(dataset);
01278             int marks = _marks;
01279 
01280             if (!fmt.marksUseDefault) {
01281                 marks = fmt.marks;
01282             }
01283 
01284             // If the point is out of range, and being drawn, then it is
01285             // probably a legend point.  When printing in black and white,
01286             // we want to use a line rather than a point for the legend.
01287             // (So that line patterns are visible). The only exception is
01288             // when the marks style uses distinct marks, or if there is
01289             // no line being drawn.
01290             // NOTE: It is unfortunate to have to test the class of graphics,
01291             // but there is no easy way around this that I can think of.
01292             if (!pointinside && (marks != 3) && _isConnected(dataset)
01293                     && (graphics instanceof EPSGraphics)) {
01294                 graphics.drawLine(xposi - 6, yposi, xposi + 6, yposi);
01295             } else {
01296                 // Color display.  Use normal legend.
01297                 switch (marks) {
01298                 case 0:
01299 
01300                     // If no mark style is given, draw a filled rectangle.
01301                     // This is used, for example, to draw the legend.
01302                     graphics.fillRect(xposi - 6, yposi - 6, 6, 6);
01303                     break;
01304 
01305                 case 1:
01306 
01307                     // points -- use 3-pixel ovals.
01308                     graphics.fillOval(xposi - 1, yposi - 1, 3, 3);
01309                     break;
01310 
01311                 case 2:
01312 
01313                     // dots
01314                     graphics.fillOval(xposi - _radius, yposi - _radius,
01315                             _diameter, _diameter);
01316                     break;
01317 
01318                 case 3:
01319 
01320                     // various
01321                     int[] xpoints;
01322 
01323                     // various
01324                     int[] ypoints;
01325 
01326                     // Points are only distinguished up to _MAX_MARKS data sets.
01327                     int mark = dataset % _MAX_MARKS;
01328 
01329                     switch (mark) {
01330                     case 0:
01331 
01332                         // filled circle
01333                         graphics.fillOval(xposi - _radius, yposi - _radius,
01334                                 _diameter, _diameter);
01335                         break;
01336 
01337                     case 1:
01338 
01339                         // cross
01340                         graphics.drawLine(xposi - _radius, yposi - _radius,
01341                                 xposi + _radius, yposi + _radius);
01342                         graphics.drawLine(xposi + _radius, yposi - _radius,
01343                                 xposi - _radius, yposi + _radius);
01344                         break;
01345 
01346                     case 2:
01347 
01348                         // square
01349                         graphics.drawRect(xposi - _radius, yposi - _radius,
01350                                 _diameter, _diameter);
01351                         break;
01352 
01353                     case 3:
01354 
01355                         // filled triangle
01356                         xpoints = new int[4];
01357                         ypoints = new int[4];
01358                         xpoints[0] = xposi;
01359                         ypoints[0] = yposi - _radius;
01360                         xpoints[1] = xposi + _radius;
01361                         ypoints[1] = yposi + _radius;
01362                         xpoints[2] = xposi - _radius;
01363                         ypoints[2] = yposi + _radius;
01364                         xpoints[3] = xposi;
01365                         ypoints[3] = yposi - _radius;
01366                         graphics.fillPolygon(xpoints, ypoints, 4);
01367                         break;
01368 
01369                     case 4:
01370 
01371                         // diamond
01372                         xpoints = new int[5];
01373                         ypoints = new int[5];
01374                         xpoints[0] = xposi;
01375                         ypoints[0] = yposi - _radius;
01376                         xpoints[1] = xposi + _radius;
01377                         ypoints[1] = yposi;
01378                         xpoints[2] = xposi;
01379                         ypoints[2] = yposi + _radius;
01380                         xpoints[3] = xposi - _radius;
01381                         ypoints[3] = yposi;
01382                         xpoints[4] = xposi;
01383                         ypoints[4] = yposi - _radius;
01384                         graphics.drawPolygon(xpoints, ypoints, 5);
01385                         break;
01386 
01387                     case 5:
01388 
01389                         // circle
01390                         graphics.drawOval(xposi - _radius, yposi - _radius,
01391                                 _diameter, _diameter);
01392                         break;
01393 
01394                     case 6:
01395 
01396                         // plus sign
01397                         graphics.drawLine(xposi, yposi - _radius, xposi, yposi
01398                                 + _radius);
01399                         graphics.drawLine(xposi - _radius, yposi, xposi
01400                                 + _radius, yposi);
01401                         break;
01402 
01403                     case 7:
01404 
01405                         // filled square
01406                         graphics.fillRect(xposi - _radius, yposi - _radius,
01407                                 _diameter, _diameter);
01408                         break;
01409 
01410                     case 8:
01411 
01412                         // triangle
01413                         xpoints = new int[4];
01414                         ypoints = new int[4];
01415                         xpoints[0] = xposi;
01416                         ypoints[0] = yposi - _radius;
01417                         xpoints[1] = xposi + _radius;
01418                         ypoints[1] = yposi + _radius;
01419                         xpoints[2] = xposi - _radius;
01420                         ypoints[2] = yposi + _radius;
01421                         xpoints[3] = xposi;
01422                         ypoints[3] = yposi - _radius;
01423                         graphics.drawPolygon(xpoints, ypoints, 4);
01424                         break;
01425 
01426                     case 9:
01427 
01428                         // filled diamond
01429                         xpoints = new int[5];
01430                         ypoints = new int[5];
01431                         xpoints[0] = xposi;
01432                         ypoints[0] = yposi - _radius;
01433                         xpoints[1] = xposi + _radius;
01434                         ypoints[1] = yposi;
01435                         xpoints[2] = xposi;
01436                         ypoints[2] = yposi + _radius;
01437                         xpoints[3] = xposi - _radius;
01438                         ypoints[3] = yposi;
01439                         xpoints[4] = xposi;
01440                         ypoints[4] = yposi - _radius;
01441                         graphics.fillPolygon(xpoints, ypoints, 5);
01442                         break;
01443                     }
01444 
01445                     break;
01446 
01447                 case 4:
01448 
01449                     // If the mark style is pixels, draw a filled rectangle.
01450                     graphics.fillRect(xposi, yposi, 1, 1);
01451                     break;
01452 
01453                 default:
01454                     // none
01455                 }
01456             }
01457         }
01458     }
01459 
01466     protected boolean _parseLine(String line) {
01467         boolean connected = false;
01468 
01469         if (_isConnected(_currentdataset)) {
01470             connected = true;
01471         }
01472 
01473         // parse only if the super class does not recognize the line.
01474         if (super._parseLine(line)) {
01475             return true;
01476         } else {
01477             // We convert the line to lower case so that the command
01478             // names are case insensitive
01479             String lcLine = line.toLowerCase();
01480 
01481             if (lcLine.startsWith("marks:")) {
01482                 // If we have seen a dataset directive, then apply the
01483                 // request to the current dataset only.
01484                 String style = (line.substring(6)).trim();
01485 
01486                 if (_sawFirstDataSet) {
01487                     setMarksStyle(style, _currentdataset);
01488                 } else {
01489                     setMarksStyle(style);
01490                 }
01491 
01492                 return true;
01493             } else if (lcLine.startsWith("numsets:")) {
01494                 // Ignore.  No longer relevant.
01495                 return true;
01496             } else if (lcLine.startsWith("reusedatasets:")) {
01497                 if (lcLine.indexOf("off", 16) >= 0) {
01498                     setReuseDatasets(false);
01499                 } else {
01500                     setReuseDatasets(true);
01501                 }
01502 
01503                 return true;
01504             } else if (lcLine.startsWith("dataset:")) {
01505                 if (_reuseDatasets && (lcLine.length() > 0)) {
01506                     String tlegend = (line.substring(8)).trim();
01507                     _currentdataset = -1;
01508 
01509                     int i;
01510 
01511                     for (i = 0; i <= _maxDataset; i++) {
01512                         if (getLegend(i).compareTo(tlegend) == 0) {
01513                             _currentdataset = i;
01514                         }
01515                     }
01516 
01517                     if (_currentdataset != -1) {
01518                         return true;
01519                     } else {
01520                         _currentdataset = _maxDataset;
01521                     }
01522                 }
01523 
01524                 // new data set
01525                 _firstInSet = true;
01526                 _sawFirstDataSet = true;
01527                 _currentdataset++;
01528 
01529                 if (lcLine.length() > 0) {
01530                     String legend = (line.substring(8)).trim();
01531 
01532                     if ((legend != null) && (legend.length() > 0)) {
01533                         addLegend(_currentdataset, legend);
01534                     }
01535                 }
01536 
01537                 _maxDataset = _currentdataset;
01538                 return true;
01539             } else if (lcLine.startsWith("lines:")) {
01540                 if (_sawFirstDataSet) {
01541                     // Backward compatbility with xgraph here.
01542                     // If we see some data sets, then they are drawn
01543                     // with lines, if we then see a Lines: off
01544                     // the current dataset and succeeding datasets
01545                     // will be drawn without lines.
01546                     // For each of the existing datasets, if
01547                     // it fmt.connectedUseDefault is true, then
01548                     // set fmt.connectedUseDefault to false and set
01549                     // the value of fmt.connected
01550                     Enumeration formats = _formats.elements();
01551 
01552                     while (formats.hasMoreElements()) {
01553                         Format format = (Format) formats.nextElement();
01554 
01555                         if (format.connectedUseDefault) {
01556                             format.connectedUseDefault = false;
01557                             format.connected = _connected;
01558                         }
01559                     }
01560                 }
01561 
01562                 if (lcLine.indexOf("off", 6) >= 0) {
01563                     setConnected(false);
01564                 } else {
01565                     setConnected(true);
01566                 }
01567 
01568                 return true;
01569             } else if (lcLine.startsWith("impulses:")) {
01570                 // If we have not yet seen a dataset, then this is interpreted
01571                 // as the global default.  Otherwise, it is assumed to apply
01572                 // only to the current dataset.
01573                 if (_sawFirstDataSet) {
01574                     if (lcLine.indexOf("off", 9) >= 0) {
01575                         setImpulses(false, _currentdataset);
01576                     } else {
01577                         setImpulses(true, _currentdataset);
01578                     }
01579                 } else {
01580                     if (lcLine.indexOf("off", 9) >= 0) {
01581                         setImpulses(false);
01582                     } else {
01583                         setImpulses(true);
01584                     }
01585                 }
01586 
01587                 return true;
01588             } else if (lcLine.startsWith("bars:")) {
01589                 if (lcLine.indexOf("off", 5) >= 0) {
01590                     setBars(false);
01591                 } else {
01592                     setBars(true);
01593 
01594                     int comma = line.indexOf(",", 5);
01595                     String barwidth;
01596                     String baroffset = null;
01597 
01598                     if (comma > 0) {
01599                         barwidth = (line.substring(5, comma)).trim();
01600                         baroffset = (line.substring(comma + 1)).trim();
01601                     } else {
01602                         barwidth = (line.substring(5)).trim();
01603                     }
01604 
01605                     try {
01606                         // Use Double.parseDouble() and avoid creating a Double
01607                         double bwidth = Double.parseDouble(barwidth);
01608                         double boffset = _barOffset;
01609 
01610                         if (baroffset != null) {
01611                             boffset = Double.parseDouble(baroffset);
01612                         }
01613 
01614                         setBars(bwidth, boffset);
01615                     } catch (NumberFormatException e) {
01616                         // ignore if format is bogus.
01617                     }
01618                 }
01619 
01620                 return true;
01621             } else if (line.startsWith("move:")) {
01622                 // a disconnected point
01623                 connected = false;
01624 
01625                 // deal with 'move: 1 2' and 'move:2 2'
01626                 line = line.substring(5, line.length()).trim();
01627             } else if (line.startsWith("move")) {
01628                 // a disconnected point
01629                 connected = false;
01630 
01631                 // deal with 'move 1 2' and 'move2 2'
01632                 line = line.substring(4, line.length()).trim();
01633             } else if (line.startsWith("draw:")) {
01634                 // a connected point, if connect is enabled.
01635                 line = line.substring(5, line.length()).trim();
01636             } else if (line.startsWith("draw")) {
01637                 // a connected point, if connect is enabled.
01638                 line = line.substring(4, line.length()).trim();
01639             }
01640 
01641             line = line.trim();
01642 
01643             // We can't use StreamTokenizer here because it can't
01644             // process numbers like 1E-01.
01645             // This code is somewhat optimized for speed, since
01646             // most data consists of two data points, we want
01647             // to handle that case as efficiently as possible.
01648             int fieldsplit = line.indexOf(",");
01649 
01650             if (fieldsplit == -1) {
01651                 fieldsplit = line.indexOf(" ");
01652             }
01653 
01654             if (fieldsplit == -1) {
01655                 fieldsplit = line.indexOf("\t"); // a tab
01656             }
01657 
01658             if (fieldsplit > 0) {
01659                 String x = (line.substring(0, fieldsplit)).trim();
01660                 String y = (line.substring(fieldsplit + 1)).trim();
01661 
01662                 // Any more separators?
01663                 int fieldsplit2 = y.indexOf(",");
01664 
01665                 if (fieldsplit2 == -1) {
01666                     fieldsplit2 = y.indexOf(" ");
01667                 }
01668 
01669                 if (fieldsplit2 == -1) {
01670                     fieldsplit2 = y.indexOf("\t"); // a tab
01671                 }
01672 
01673                 if (fieldsplit2 > 0) {
01674                     line = (y.substring(fieldsplit2 + 1)).trim();
01675                     y = (y.substring(0, fieldsplit2)).trim();
01676                 }
01677 
01678                 try {
01679                     // Use Double.parseDouble() and avoid creating a Double.
01680                     double xpt = Double.parseDouble(x);
01681                     double ypt = Double.parseDouble(y);
01682 
01683                     if (fieldsplit2 > 0) {
01684                         // There was one separator after the y value, now
01685                         // look for another separator.
01686                         int fieldsplit3 = line.indexOf(",");
01687 
01688                         if (fieldsplit3 == -1) {
01689                             fieldsplit3 = line.indexOf(" ");
01690                         }
01691 
01692                         //if (fieldsplit3 == -1) {
01693                         //    fieldsplit2 = line.indexOf("\t"); // a tab
01694                         //}
01695                         if (fieldsplit3 > 0) {
01696                             // We have more numbers, assume that this is
01697                             // an error bar
01698                             String yl = (line.substring(0, fieldsplit3)).trim();
01699                             String yh = (line.substring(fieldsplit3 + 1))
01700                                     .trim();
01701                             double yLowEB = Double.parseDouble(yl);
01702                             double yHighEB = Double.parseDouble(yh);
01703                             connected = _addLegendIfNecessary(connected);
01704                             addPointWithErrorBars(_currentdataset, xpt, ypt,
01705                                     yLowEB, yHighEB, connected);
01706                             return true;
01707                         } else {
01708                             // It is unlikely that we have a fieldsplit2 >0
01709                             // but not fieldsplit3 >0, but just in case:
01710                             connected = _addLegendIfNecessary(connected);
01711                             addPoint(_currentdataset, xpt, ypt, connected);
01712                             return true;
01713                         }
01714                     } else {
01715                         // There were no more fields, so this is
01716                         // a regular pt.
01717                         connected = _addLegendIfNecessary(connected);
01718                         addPoint(_currentdataset, xpt, ypt, connected);
01719                         return true;
01720                     }
01721                 } catch (NumberFormatException e) {
01722                     // ignore if format is bogus.
01723                 }
01724             }
01725         }
01726 
01727         return false;
01728     }
01729 
01735     protected void _setWidth(Graphics graphics, float width) {
01736         // For historical reasons, the API here only assumes Graphics
01737         // objects, not Graphics2D.
01738         if (graphics instanceof Graphics2D) {
01739             // We cache the two most common cases.
01740             if (width == 1f) {
01741                 ((Graphics2D) graphics).setStroke(_lineStroke1);
01742             } else if (width == 2f) {
01743                 ((Graphics2D) graphics).setStroke(_lineStroke2);
01744             } else {
01745                 ((Graphics2D) graphics).setStroke(new BasicStroke(width,
01746                         BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
01747             }
01748         }
01749     }
01750 
01760     protected void _writeOldSyntax(PrintWriter output) {
01761         super._writeOldSyntax(output);
01762 
01763         // NOTE: NumSets is obsolete, so we don't write it.
01764         if (_reuseDatasets) {
01765             output.println("ReuseDatasets: on");
01766         }
01767 
01768         if (!_connected) {
01769             output.println("Lines: off");
01770         }
01771 
01772         if (_bars) {
01773             output.println("Bars: " + barWidth + ", " + _barOffset);
01774         }
01775 
01776         // Write the defaults for formats that can be controlled by dataset
01777         if (_impulses) {
01778             output.println("Impulses: on");
01779         }
01780 
01781         switch (_marks) {
01782         case 1:
01783             output.println("Marks: points");
01784             break;
01785 
01786         case 2:
01787             output.println("Marks: dots");
01788             break;
01789 
01790         case 3:
01791             output.println("Marks: various");
01792             break;
01793 
01794         case 4:
01795             output.println("Marks: pixels");
01796             break;
01797         }
01798 
01799         for (int dataset = 0; dataset < _points.size(); dataset++) {
01800             // Write the dataset directive
01801             String legend = getLegend(dataset);
01802 
01803             if (legend != null) {
01804                 output.println("DataSet: " + getLegend(dataset));
01805             } else {
01806                 output.println("DataSet:");
01807             }
01808 
01809             // Write dataset-specific format information
01810             Format fmt = (Format) _formats.elementAt(dataset);
01811 
01812             if (!fmt.impulsesUseDefault) {
01813                 if (fmt.impulses) {
01814                     output.println("Impulses: on");
01815                 } else {
01816                     output.println("Impulses: off");
01817                 }
01818             }
01819 
01820             if (!fmt.marksUseDefault) {
01821                 switch (fmt.marks) {
01822                 case 0:
01823                     output.println("Marks: none");
01824                     break;
01825 
01826                 case 1:
01827                     output.println("Marks: points");
01828                     break;
01829 
01830                 case 2:
01831                     output.println("Marks: dots");
01832                     break;
01833 
01834                 case 3:
01835                     output.println("Marks: various");
01836                     break;
01837 
01838                 case 4:
01839                     output.println("Marks: pixels");
01840                     break;
01841                 }
01842             }
01843 
01844             // Write the data
01845             Vector pts = (Vector) _points.elementAt(dataset);
01846 
01847             for (int pointnum = 0; pointnum < pts.size(); pointnum++) {
01848                 PlotPoint pt = (PlotPoint) pts.elementAt(pointnum);
01849 
01850                 if (!pt.connected) {
01851                     output.print("move: ");
01852                 }
01853 
01854                 if (pt.errorBar) {
01855                     output.println(pt.x + ", " + pt.y + ", " + pt.yLowEB + ", "
01856                             + pt.yHighEB);
01857                 } else {
01858                     output.println(pt.x + ", " + pt.y);
01859                 }
01860             }
01861         }
01862     }
01863 
01866 
01868     protected int _currentdataset = -1;
01869 
01871     protected Vector _points = new Vector();
01872 
01876     protected int _marks;
01877 
01880 
01881     /* Add a legend if necessary, return the value of the connected flag.
01882      */
01883     private boolean _addLegendIfNecessary(boolean connected) {
01884         if ((!_sawFirstDataSet || (_currentdataset < 0)) && !_reuseDatasets) {
01885             // We did not set a DataSet line, but
01886             // we did get called with -<digit> args and
01887             // we did not see reusedatasets: yes
01888             _sawFirstDataSet = true;
01889             _currentdataset++;
01890         }
01891 
01892         if (!_sawFirstDataSet && (getLegend(_currentdataset) == null)) {
01893             // We did not see a "DataSet" string yet,
01894             // nor did we call addLegend().
01895             _firstInSet = true;
01896             _sawFirstDataSet = true;
01897             addLegend(_currentdataset, "Set " + _currentdataset);
01898         }
01899 
01900         if (_firstInSet && !_reuseDatasets) {
01901             connected = false;
01902             _firstInSet = false;
01903         }
01904 
01905         return connected;
01906     }
01907 
01908     /* In the specified data set, add the specified x, y point to the
01909      * plot.  Data set indices begin with zero.  If the dataset
01910      * argument is less than zero, throw an IllegalArgumentException
01911      * (a runtime exception).  If it refers to a data set that does
01912      * not exist, create the data set.  The fourth argument indicates
01913      * whether the point should be connected by a line to the previous
01914      * point.  However, this argument is ignored if setConnected() has
01915      * been called with a false argument.  In that case, a point is never
01916      * connected to the previous point.  That argument is also ignored
01917      * if the point is the first in the specified dataset.
01918      * The point is drawn on the screen only if is visible.
01919      * Otherwise, it is drawn the next time paintComponent() is called.
01920      *
01921      * This is not synchronized, so the caller should be.  Moreover, this
01922      * should only be called in the event dispatch thread. It should only
01923      * be called via deferIfNecessary().
01924      */
01925     private void _addPoint(int dataset, double x, double y, double yLowEB,
01926             double yHighEB, boolean connected, boolean errorBar) {
01927         // Ensure replot of offscreen buffer.
01928         _plotImage = null;
01929 
01930         _checkDatasetIndex(dataset);
01931 
01932         if (_xlog) {
01933             if (x <= 0.0) {
01934                 System.err.println("Can't plot non-positive X values "
01935                         + "when the logarithmic X axis value is specified: "
01936                         + x);
01937                 return;
01938             }
01939 
01940             x = Math.log(x) * _LOG10SCALE;
01941         }
01942 
01943         if (_ylog) {
01944             if (y <= 0.0) {
01945                 System.err.println("Can't plot non-positive Y values "
01946                         + "when the logarithmic Y axis value is specified: "
01947                         + y);
01948                 return;
01949             }
01950 
01951             y = Math.log(y) * _LOG10SCALE;
01952 
01953             if (errorBar) {
01954                 if ((yLowEB <= 0.0) || (yHighEB <= 0.0)) {
01955                     System.err
01956                             .println("Can't plot non-positive Y values "
01957                                     + "when the logarithmic Y axis value is specified: "
01958                                     + y);
01959                     return;
01960                 }
01961 
01962                 yLowEB = Math.log(yLowEB) * _LOG10SCALE;
01963                 yHighEB = Math.log(yHighEB) * _LOG10SCALE;
01964             }
01965         }
01966 
01967         Vector pts = (Vector) _points.elementAt(dataset);
01968 
01969         // If X persistence has been set, then delete any old points.
01970         if (_xPersistence > 0.0) {
01971             int numToDelete = 0;
01972 
01973             while (numToDelete < pts.size()) {
01974                 PlotPoint old = (PlotPoint) (pts.elementAt(numToDelete));
01975 
01976                 if ((x - old.originalx) <= _xPersistence) {
01977                     break;
01978                 }
01979 
01980                 numToDelete++;
01981             }
01982 
01983             for (int i = 0; i < numToDelete; i++) {
01984                 erasePoint(dataset, 0);
01985             }
01986         }
01987 
01988         // Get the new size after deletions.
01989         int size = pts.size();
01990 
01991         PlotPoint pt = new PlotPoint();
01992 
01993         // Original value of x before wrapping.
01994         pt.originalx = x;
01995 
01996         // Modify x if wrapping.
01997         if (_wrap) {
01998             double width = _wrapHigh - _wrapLow;
01999 
02000             if (x < _wrapLow) {
02001                 x += (width * Math.floor(1.0 + ((_wrapLow - x) / width)));
02002             } else if (x > _wrapHigh) {
02003                 x -= (width * Math.floor(1.0 + ((x - _wrapHigh) / width)));
02004 
02005                 // NOTE: Could quantization errors be a problem here?
02006                 if (Math.abs(x - _wrapLow) < 0.00001) {
02007                     x = _wrapHigh;
02008                 }
02009             }
02010         }
02011 
02012         // For auto-ranging, keep track of min and max.
02013         if (x < _xBottom) {
02014             _xBottom = x;
02015         }
02016 
02017         if (x > _xTop) {
02018             _xTop = x;
02019         }
02020 
02021         if (y < _yBottom) {
02022             _yBottom = y;
02023         }
02024 
02025         if (y > _yTop) {
02026             _yTop = y;
02027         }
02028 
02029         pt.x = x;
02030         pt.y = y;
02031         pt.connected = connected && _isConnected(dataset);
02032 
02033         if (errorBar) {
02034             if (yLowEB < _yBottom) {
02035                 _yBottom = yLowEB;
02036             }
02037 
02038             if (yLowEB > _yTop) {
02039                 _yTop = yLowEB;
02040             }
02041 
02042             if (yHighEB < _yBottom) {
02043                 _yBottom = yHighEB;
02044             }
02045 
02046             if (yHighEB > _yTop) {
02047                 _yTop = yHighEB;
02048             }
02049 
02050             pt.yLowEB = yLowEB;
02051             pt.yHighEB = yHighEB;
02052             pt.errorBar = true;
02053         }
02054 
02055         // If this is the first point in the dataset, clear the connected bit.
02056         if (size == 0) {
02057             pt.connected = false;
02058         } else if (_wrap) {
02059             // Do not connect points if wrapping...
02060             PlotPoint old = (PlotPoint) (pts.elementAt(size - 1));
02061 
02062             if (old.x > x) {
02063                 pt.connected = false;
02064             }
02065         }
02066 
02067         pts.addElement(pt);
02068 
02069         // If points persistence has been set, then delete one old point.
02070         if (_pointsPersistence > 0) {
02071             if (size > _pointsPersistence) {
02072                 erasePoint(dataset, 0);
02073             }
02074         }
02075 
02076         // Draw the point on the screen only if the plot is showing.
02077         Graphics graphics = getGraphics();
02078 
02079         // Need to check that graphics is not null because plot may have
02080         // been dismissed.
02081         if (_showing && (graphics != null)) {
02082             if (((_pointsPersistence > 0) || (_xPersistence > 0.0))
02083                     && isDoubleBuffered()) {
02084                 // NOTE: Double buffering has a bug in Java (in at least
02085                 // version 1.3) where there is a one pixel alignment problem
02086                 // that prevents XOR drawing from working correctly.
02087                 // XOR drawing is used for live plots, and if double buffering
02088                 // is turned on, then cruft is left on the screen whenever the
02089                 // fill or zoom functions are used.
02090                 // Here, if it hasn't been done already, we turn off double
02091                 // buffering on this panel and all its parents for which this
02092                 // is possible.  Note that we could do this globally using
02093                 //
02094                 // RepaintManager repaintManager
02095                 //        = RepaintManager.currentManager(this);
02096                 // repaintManager.setDoubleBufferingEnabled(false);
02097                 //
02098                 // However, that turns off double buffering in all windows
02099                 // of the application, which means that other windows that only
02100                 // work properly with double buffering (such as vergil windows)
02101                 // will not work.
02102                 //
02103                 // NOTE: This fix creates another problem...
02104                 // If there are other widgets besides the plotter in the
02105                 // same top-level window, and they implement double
02106                 // buffering (which they will by default), then they
02107                 // need to be opaque or drawing artifacts will appear
02108                 // upon exposure events.  The workaround is simple:
02109                 // Make these other objects opaque, and set their
02110                 // background color appropriately.
02111                 //
02112                 // See:
02113                 // <pre>
02114                 // http://developer.java.sun.com/developer/bugParade/bugs/
02115                 //     4188795.html
02116                 //     4204551.html
02117                 //     4295712.htm
02118                 // </pre>
02119                 //
02120                 // Since we are assured of being in the event dispatch thread,
02121                 // we can simply execute this.
02122                 setDoubleBuffered(false);
02123 
02124                 Component parent = getParent();
02125 
02126                 while (parent != null) {
02127                     if (parent instanceof JComponent) {
02128                         ((JComponent) parent).setDoubleBuffered(false);
02129                     }
02130 
02131                     parent = parent.getParent();
02132                 }
02133             }
02134 
02135             // Again, we are in the event thread, so this is safe...
02136             _drawPlotPoint(graphics, dataset, pts.size() - 1);
02137         }
02138 
02139         if (_wrap && (Math.abs(x - _wrapHigh)) < 0.00001) {
02140             // Plot a second point at the low end of the range.
02141             _addPoint(dataset, _wrapLow, y, yLowEB, yHighEB, false, errorBar);
02142         }
02143     }
02144 
02145     /* Clear the plot of all data points.  If the argument is true, then
02146      * reset all parameters to their initial conditions, including
02147      * the persistence, plotting format, and axes formats.
02148      * For the change to take effect, you must call repaint().
02149      *
02150      * This is not synchronized, so the caller should be.  Moreover, this
02151      * should only be called in the event dispatch thread. It should only
02152      * be called via deferIfNecessary().
02153      */
02154     private void _clear(boolean format) {
02155         // Ensure replot of offscreen buffer.
02156         _plotImage = null;
02157         super.clear(format);
02158         _currentdataset = -1;
02159         _points = new Vector();
02160         _prevx = new Vector();
02161         _prevy = new Vector();
02162         _maxDataset = -1;
02163         _firstInSet = true;
02164         _sawFirstDataSet = false;
02165         _xyInvalid = true;
02166 
02167         if (format) {
02168             _showing = false;
02169 
02170             // Reset format controls
02171             _formats = new Vector();
02172             _marks = 0;
02173             _pointsPersistence = 0;
02174             _xPersistence = 0;
02175             _bars = false;
02176             barWidth = 0.5;
02177             _barOffset = 0.05;
02178             _connected = true;
02179             _impulses = false;
02180             _reuseDatasets = false;
02181         }
02182     }
02183 
02191     private void _clear(int dataset) {
02192         // Ensure replot of offscreen buffer.
02193         _plotImage = null;
02194         _checkDatasetIndex(dataset);
02195         _xyInvalid = true;
02196 
02197         Vector points = (Vector) _points.elementAt(dataset);
02198 
02199         // Vector.clear() is new in JDK1.2, so we use just
02200         // create a new Vector here so that we can compile
02201         // this with JDK1.1 for use in JDK1.1 browsers
02202         points.clear();
02203 
02204         //_points.setElementAt(new Vector(), dataset);
02205         _points.setElementAt(points, dataset);
02206         repaint();
02207     }
02208 
02209     /* Draw the specified point and associated lines, if any.
02210      * Note that paintComponent() should be called before
02211      * calling this method so that it calls _drawPlot(), which sets
02212      * _xscale and _yscale. Note that this does not check the dataset
02213      * index.  It is up to the caller to do that.
02214      *
02215      * Note that this method is not synchronized, so the caller should be.
02216      * Moreover this method should always be called from the event thread
02217      * when being used to write to the screen.
02218      */
02219     private void _drawPlotPoint(Graphics graphics, int dataset, int index) {
02220         if ((_pointsPersistence > 0) || (_xPersistence > 0.0)) {
02221             // To allow erasing to work by just redrawing the points.
02222             if (_background == null) {
02223                 // java.awt.Component.setBackground(color) says that
02224                 // if the color "parameter is null then this component
02225                 // will inherit the  background color of its parent."
02226                 graphics.setXORMode(getBackground());
02227             } else {
02228                 graphics.setXORMode(_background);
02229             }
02230         }
02231 
02232         // Set the color
02233         if (_usecolor) {
02234             int color = dataset % _colors.length;
02235             graphics.setColor(_colors[color]);
02236         } else {
02237             graphics.setColor(_foreground);
02238         }
02239 
02240         Vector pts = (Vector) _points.elementAt(dataset);
02241         PlotPoint pt = (PlotPoint) pts.elementAt(index);
02242 
02243         // Use long here because these numbers can be quite large
02244         // (when we are zoomed out a lot).
02245         long ypos = _lry - (long) ((pt.y - _yMin) * _yscale);
02246         long xpos = _ulx + (long) ((pt.x - _xMin) * _xscale);
02247 
02248         // Draw the line to the previous point.
02249         long prevx = ((Long) _prevx.elementAt(dataset)).longValue();
02250         long prevy = ((Long) _prevy.elementAt(dataset)).longValue();
02251 
02252         // Avoid drawing points and lines that are invisible.
02253         // Note that if the size of the dataset is 1, then we have only
02254         // one point, so we should be sure to draw it.
02255         if ((xpos != prevx) || (ypos != prevy) || (pts.size() == 1)) {
02256             // MIN_VALUE is a flag that there has been no previous x or y.
02257             if (pt.connected) {
02258                 _drawLine(graphics, dataset, xpos, ypos, prevx, prevy, true, 2f);
02259             }
02260 
02261             // Save the current point as the "previous" point for future
02262             // line drawing.
02263             _prevx.setElementAt(Long.valueOf(xpos), dataset);
02264             _prevy.setElementAt(Long.valueOf(ypos), dataset);
02265 
02266             // Draw decorations that may be specified on a per-dataset basis
02267             Format fmt = (Format) _formats.elementAt(dataset);
02268 
02269             if (fmt.impulsesUseDefault) {
02270                 if (_impulses) {
02271                     _drawImpulse(graphics, xpos, ypos, true);
02272                 }
02273             } else {
02274                 if (fmt.impulses) {
02275                     _drawImpulse(graphics, xpos, ypos, true);
02276                 }
02277             }
02278 
02279             // Check to see whether the dataset has a marks directive
02280             int marks = _marks;
02281 
02282             if (!fmt.marksUseDefault) {
02283                 marks = fmt.marks;
02284             }
02285 
02286             if (marks != 0) {
02287                 _drawPoint(graphics, dataset, xpos, ypos, true);
02288             }
02289 
02290             if (_bars) {
02291                 _drawBar(graphics, dataset, xpos, ypos, true);
02292             }
02293         }
02294 
02295         if (pt.errorBar) {
02296             _drawErrorBar(graphics, dataset, xpos, _lry
02297                     - (long) ((pt.yLowEB - _yMin) * _yscale), _lry
02298                     - (long) ((pt.yHighEB - _yMin) * _yscale), true);
02299         }
02300 
02301         // Restore the color, in case the box gets redrawn.
02302         graphics.setColor(_foreground);
02303 
02304         if ((_pointsPersistence > 0) || (_xPersistence > 0.0)) {
02305             // Restore paint mode in case axes get redrawn.
02306             graphics.setPaintMode();
02307         }
02308     }
02309 
02310     /* Erase the point at the given index in the given dataset.  If
02311      * lines are being drawn, also erase the line to the next points
02312      * (note: not to the previous point).
02313      *
02314      * This is not synchronized, so the caller should be.  Moreover, this
02315      * should only be called in the event dispatch thread. It should only
02316      * be called via deferIfNecessary().
02317      */
02318     private void _erasePoint(int dataset, int index) {
02319         // Ensure replot of offscreen buffer.
02320         _plotImage = null;
02321 
02322         _checkDatasetIndex(dataset);
02323 
02324         // Plot has probably been dismissed.  Return.
02325         Graphics graphics = getGraphics();
02326 
02327         // Need to check that graphics is not null because plot may have
02328         // been dismissed.
02329         if (_showing && (graphics != null)) {
02330             // Set the color
02331             if ((_pointsPersistence > 0) || (_xPersistence > 0.0)) {
02332                 // To allow erasing to work by just redrawing the points.
02333                 if (_background == null) {
02334                     graphics.setXORMode(getBackground());
02335                 } else {
02336                     graphics.setXORMode(_background);
02337                 }
02338             }
02339 
02340             if (_usecolor) {
02341                 int color = dataset % _colors.length;
02342                 graphics.setColor(_colors[color]);
02343             } else {
02344                 graphics.setColor(_foreground);
02345             }
02346 
02347             Vector pts = (Vector) _points.elementAt(dataset);
02348             PlotPoint pt = (PlotPoint) pts.elementAt(index);
02349             long ypos = _lry - (long) ((pt.y - _yMin) * _yscale);
02350             long xpos = _ulx + (long) ((pt.x - _xMin) * _xscale);
02351 
02352             // Erase line to the next point, if appropriate.
02353             if (index < (pts.size() - 1)) {
02354                 PlotPoint nextp = (PlotPoint) pts.elementAt(index + 1);
02355                 int nextx = _ulx + (int) ((nextp.x - _xMin) * _xscale);
02356                 int nexty = _lry - (int) ((nextp.y - _yMin) * _yscale);
02357 
02358                 // NOTE: I have no idea why I have to give this point backwards.
02359                 if (nextp.connected) {
02360                     _drawLine(graphics, dataset, nextx, nexty, xpos, ypos,
02361                             true, 2f);
02362                 }
02363 
02364                 nextp.connected = false;
02365             }
02366 
02367             // Draw decorations that may be specified on a per-dataset basis
02368             Format fmt = (Format) _formats.elementAt(dataset);
02369 
02370             if (fmt.impulsesUseDefault) {
02371                 if (_impulses) {
02372                     _drawImpulse(graphics, xpos, ypos, true);
02373                 }
02374             } else {
02375                 if (fmt.impulses) {
02376                     _drawImpulse(graphics, xpos, ypos, true);
02377                 }
02378             }
02379 
02380             // Check to see whether the dataset has a marks directive
02381             int marks = _marks;
02382 
02383             if (!fmt.marksUseDefault) {
02384                 marks = fmt.marks;
02385             }
02386 
02387             if (marks != 0) {
02388                 _drawPoint(graphics, dataset, xpos, ypos, true);
02389             }
02390 
02391             if (_bars) {
02392                 _drawBar(graphics, dataset, xpos, ypos, true);
02393             }
02394 
02395             if (pt.errorBar) {
02396                 _drawErrorBar(graphics, dataset, xpos, _lry
02397                         - (long) ((pt.yLowEB - _yMin) * _yscale), _lry
02398                         - (long) ((pt.yHighEB - _yMin) * _yscale), true);
02399             }
02400 
02401             // Restore the color, in case the box gets redrawn.
02402             graphics.setColor(_foreground);
02403 
02404             if ((_pointsPersistence > 0) || (_xPersistence > 0.0)) {
02405                 // Restore paint mode in case axes get redrawn.
02406                 graphics.setPaintMode();
02407             }
02408         }
02409 
02410         // The following is executed whether the plot is showing or not.
02411         // Remove the point from the model.
02412         Vector points = (Vector) _points.elementAt(dataset);
02413 
02414         if (points != null) {
02415             // If this point is at the maximum or minimum x or y boundary,
02416             // then flag that boundary needs to be recalculated next time
02417             // fillPlot() is called.
02418             PlotPoint pt = (PlotPoint) points.elementAt(index);
02419 
02420             if (pt != null) {
02421                 if ((pt.x == _xBottom) || (pt.x == _xTop) || (pt.y == _yBottom)
02422                         || (pt.y == _yTop)) {
02423                     _xyInvalid = true;
02424                 }
02425 
02426                 points.removeElementAt(index);
02427             }
02428         }
02429     }
02430 
02431     /* Rescale so that the data that is currently plotted just fits.
02432      * This overrides the base class method to ensure that the protected
02433      * variables _xBottom, _xTop, _yBottom, and _yTop are valid.
02434      * This method calls repaint(), which causes the display
02435      * to be updated.
02436      *
02437      * This is not synchronized, so the caller should be.  Moreover, this
02438      * should only be called in the event dispatch thread. It should only
02439      * be called via deferIfNecessary().
02440      */
02441     private void _fillPlot() {
02442         if (_xyInvalid) {
02443             // Recalculate the boundaries based on currently visible data
02444             _xBottom = Double.MAX_VALUE;
02445             _xTop = -Double.MAX_VALUE;
02446             _yBottom = Double.MAX_VALUE;
02447             _yTop = -Double.MAX_VALUE;
02448 
02449             for (int dataset = 0; dataset < _points.size(); dataset++) {
02450                 Vector points = (Vector) _points.elementAt(dataset);
02451 
02452                 for (int index = 0; index < points.size(); index++) {
02453                     PlotPoint pt = (PlotPoint) points.elementAt(index);
02454 
02455                     if (pt.x < _xBottom) {
02456                         _xBottom = pt.x;
02457                     }
02458 
02459                     if (pt.x > _xTop) {
02460                         _xTop = pt.x;
02461                     }
02462 
02463                     if (pt.y < _yBottom) {
02464                         _yBottom = pt.y;
02465                     }
02466 
02467                     if (pt.y > _yTop) {
02468                         _yTop = pt.y;
02469                     }
02470                 }
02471             }
02472         }
02473 
02474         _xyInvalid = false;
02475 
02476         // If this is a bar graph, then make sure the Y range includes 0
02477         if (_bars) {
02478             if (_yBottom > 0.0) {
02479                 _yBottom = 0.0;
02480             }
02481 
02482             if (_yTop < 0.0) {
02483                 _yTop = 0.0;
02484             }
02485         }
02486 
02487         super.fillPlot();
02488     }
02489 
02490     // Return true if the specified dataset is connected by default.
02491     private boolean _isConnected(int dataset) {
02492         if (dataset < 0) {
02493             return _connected;
02494         }
02495 
02496         _checkDatasetIndex(dataset);
02497 
02498         Format fmt = (Format) _formats.elementAt(dataset);
02499 
02500         if (fmt.connectedUseDefault) {
02501             return _connected;
02502         } else {
02503             return fmt.connected;
02504         }
02505     }
02506 
02509 
02511     private int _pointsPersistence = 0;
02512 
02514     private double _xPersistence = 0.0;
02515 
02517     private boolean _bars = false;
02518 
02520     private double barWidth = 0.5;
02521 
02523     private double _barOffset = 0.05;
02524 
02526     private boolean _connected = true;
02527 
02529     private boolean _impulses = false;
02530 
02532     private int _maxDataset = -1;
02533 
02535     private boolean _reuseDatasets = false;
02536 
02538     private boolean _firstInSet = true;
02539 
02541     private boolean _sawFirstDataSet = false;
02542 
02544     private int _radius = 3;
02545 
02547     private int _diameter = 6;
02548 
02550     private Vector _prevx = new Vector();
02551 
02553     private Vector _prevy = new Vector();
02554 
02555     // Half of the length of the error bar horizontal leg length;
02556     private static final int _ERRORBAR_LEG_LENGTH = 5;
02557 
02558     // Maximum number of different marks
02559     // NOTE: There are 11 colors in the base class.  Combined with 10
02560     // marks, that makes 110 unique signal identities.
02561     private static final int _MAX_MARKS = 10;
02562 
02563     // A stroke of width 1.
02564     private static final BasicStroke _lineStroke1 = new BasicStroke(1f,
02565             BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
02566 
02567     // A stroke of width 2.
02568     private static final BasicStroke _lineStroke2 = new BasicStroke(2f,
02569             BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
02570 
02571     // The stroke to use for thin lines in the plot.
02572     //private static final BasicStroke _thinStroke = new BasicStroke(
02573     //        1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
02574 
02578     private boolean _xyInvalid = true;
02579 
02581     private boolean _showing = false;
02582 
02584     private Vector _formats = new Vector();
02585 
02589     private static final Long _initialPreviousValue =  Long.valueOf(0x0L);
02590 
02594     private Graphics _graphics = null;
02595 
02598     private static class Format implements Serializable {
02599         // FindBugs suggests making this class static so as to decrease
02600         // the size of instances and avoid dangling references.
02601 
02602         // Indicate whether the current dataset is connected.
02603         public boolean connected;
02604 
02605         // Indicate whether the above variable should be ignored.
02606         public boolean connectedUseDefault = true;
02607 
02608         // Indicate whether a stem plot should be drawn for this data set.
02609         // This is ignored unless the following variable is set to false.
02610         public boolean impulses;
02611 
02612         // Indicate whether the above variable should be ignored.
02613         public boolean impulsesUseDefault = true;
02614 
02615         // Indicate what type of mark to use.
02616         // This is ignored unless the following variable is set to false.
02617         public int marks;
02618 
02619         // Indicate whether the above variable should be ignored.
02620         public boolean marksUseDefault = true;
02621     }
02622 }