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