001/* A histogram 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
030import java.awt.Graphics;
031import java.awt.Rectangle;
032import java.io.BufferedWriter;
033import java.io.PrintWriter;
034import java.io.Writer;
035import java.util.ArrayList;
036import java.util.Enumeration;
037import java.util.HashSet;
038import java.util.Hashtable;
039import java.util.Locale;
040import java.util.Random;
041import java.util.Vector;
042
043import ptolemy.util.RunnableExceptionCatcher;
044
045///////////////////////////////////////////////////////////////////
046//// Histogram
047
048/**
049 A histogram plotter.  The plot can be configured and data can
050 be provided either through a file with commands or through direct
051 invocation of the public methods of the class.  To read a file or a
052 URL, use the read() method.
053 <p>
054 When calling the public methods, in most cases the changes will not
055 be visible until paint() has been called.  To request that this
056 be done, call repaint().  One exception is addPoint(), which
057 makes the affect of the new point visible immediately (or nearly
058 immediately) if the plot is visible on the screen.
059 <p>
060 The ASCII format for the file file contains any number commands,
061 one per line.  Unrecognized commands and commands with syntax
062 errors are ignored.  Comments are denoted by a line starting with a
063 pound sign "#".  The recognized commands include those supported by
064 the base class, plus a few more.  The commands are case
065 insensitive, but are usually capitalized.  The number of data sets
066 to be plotted does not need to be specified.  Data sets are added as needed.
067 Each dataset is identified with a color (see the base class).
068 <P>
069 The appearance of the histogram can be altered by the following commands:
070 <pre>
071 Bars: <i>width</i>
072 Bars: <i>width, offset</i>
073 </pre>
074 The <i>width</i> is a real number specifying the width of the bars
075 as a fraction of the bin width.  It usually has a value less than
076 or equal to one,
077 and defaults to 0.5.  The <i>offset</i> is a real number
078 specifying how much the bar of the <i>i </i><sup>th</sup> data set
079 is offset from the previous one.  This allows bars to "peek out"
080 from behind the ones in front.  It defaults to 0.15.
081 Note that the frontmost data set will be the first one.
082 <p>
083 The width of each bin of the histogram can be specified using:
084 <pre>
085 BinWidth: <i>width</i>
086 </pre>
087 This is given in whatever units the data has.
088 By default, each bin is centered at <i>x</i> = <i>nw</i>,
089 where <i>w</i> is the width of the bin and <i>n</i> is an integer.
090 That bin represents values in the range (<i>x - w/2, x + w/2</i>).
091 The alignment of the bins can be changed with the following command:
092 <pre>
093 BinOffset: <i>offset</i>
094 </pre>
095 If this method is used with argument <i>o</i>, then each bin is
096 centered at <i>x = nw + o</i>, and represents values in the range
097 (<i>x - w/2 + o, x + w/2 + o</i>).  So for example, if <i>o = w/2</i>,
098 then each bin represents values from <i>nw</i> to
099 (<i>n</i> + 1)<i>w</i> for some integer <i>n</i>.
100 The default offset is 0.5, half the default bin width.
101 <p>
102 To specify data to be plotted, start a data set with the following command:
103 <pre>
104 DataSet: <i>string</i>
105 </pre>
106 Here, <i>string</i> is a label that will appear in the legend.
107 It is not necessary to enclose the string in quotation marks.
108 To start a new dataset without giving it a name, use:
109 <pre>
110 DataSet:
111 </pre>
112 In this case, no item will appear in the legend.
113 New datasets are plotted <i>behind</i> the previous ones.
114 The data itself is given by a sequence of numbers, one per line.
115 The numbers are specified as
116 strings that can be parsed by the Double parser in Java.
117 It is also possible to specify the numbers using all the formats
118 accepted by the Plot class, so that the same data may be plotted by
119 both classes.  The <i>x</i> data is ignored, and only the <i>y</i>
120 data is used to calculate the histogram.
121
122 @author Edward A. Lee, Contributor: Bert Rodiers
123 @version $Id$
124 @since Ptolemy II 0.3
125 @Pt.ProposedRating Yellow (cxh)
126 @Pt.AcceptedRating Yellow (cxh)
127 */
128@SuppressWarnings("serial")
129public class Histogram extends PlotBox {
130
131    ///////////////////////////////////////////////////////////////////
132    ////                         public methods                    ////
133
134    /** Add a legend (displayed at the upper right) for the specified
135     *  data set with the specified string.  Short strings generally
136     *  fit better than long strings.
137     *  @param dataset The dataset index.
138     *  @param legend The label for the dataset.
139     */
140    @Override
141    public void addLegend(int dataset, String legend) {
142        _checkDatasetIndex(dataset);
143        super.addLegend(dataset, legend);
144    }
145
146    /** In the specified data set, add the specified value to the
147     *  histogram.  Data set indices begin with zero.  If the data set
148     *  does not exist, create it.
149     *  The new point will visibly alter the histogram if the plot is visible
150     *  on the screen.  Otherwise, it will be drawn the next time the histogram
151     *  is drawn on the screen.
152     *  <p>
153     *  In order to work well with swing and be thread safe, this method
154     *  actually defers execution to the event dispatch thread, where
155     *  all user interface actions are performed.  Thus, the point will
156     *  not be added immediately (unless you call this method from within
157     *  the event dispatch thread). All the methods that do this deferring
158     *  coordinate so that they are executed in the order that you
159     *  called them.
160     *
161     *  @param dataset The data set index.
162     *  @param value The new value.
163     */
164    public synchronized void addPoint(final int dataset, final double value) {
165        Runnable doAddPoint = new Runnable() {
166            @Override
167            public void run() {
168                _addPoint(dataset, value);
169            }
170        };
171
172        deferIfNecessary(doAddPoint);
173    }
174
175    /** In the specified data set, add the specified y value to the
176     *  histogram.  The x value and the <i>connected</i> arguments are
177     *  ignored.  Data set indices begin with zero.  If the data set
178     *  does not exist, create it.
179     *  @param dataset The data set index.
180     *  @param x Ignored.
181     *  @param y The Y position of the new point.
182     *  @param connected Ignored
183     */
184    public synchronized void addPoint(int dataset, double x, double y,
185            boolean connected) {
186        addPoint(dataset, y);
187    }
188
189    /** Clear the plot of all data points.  If the argument is true, then
190     *  reset all parameters to their initial conditions, including
191     *  the persistence, plotting format, and axes formats.
192     *  For the change to take effect, you must call repaint().
193     *  <p>
194     *  In order to work well with swing and be thread safe, this method
195     *  actually defers execution to the event dispatch thread, where
196     *  all user interface actions are performed.  Thus, the clear will
197     *  not be executed immediately (unless you call this method from within
198     *  the event dispatch thread).  All the methods that do this deferring
199     *  coordinate so that they are executed in the order that you
200     *  called them.
201     *
202     *  @param format If true, clear the format controls as well.
203     */
204    @Override
205    public synchronized void clear(final boolean format) {
206        Runnable doClear = new Runnable() {
207            @Override
208            public void run() {
209                _clear(format);
210            }
211        };
212
213        deferIfNecessary(doClear);
214    }
215
216    /** Write plot data information to the specified output stream in PlotML,
217     *  but in such a way that the Plot class can read it and reproduce the
218     *  histogram.  The ordinary mechanism for saving the histogram
219     *  records the raw data and the configuration to be used by this class.
220     *  @param output A buffered print writer.
221     *  @param dtd The DTD, or null to reference the default.
222     */
223    public synchronized void exportToPlot(PrintWriter output, String dtd) {
224        if (dtd == null) {
225            output.println("<?xml version=\"1.0\" standalone=\"yes\"?>");
226            output.println(
227                    "<!DOCTYPE plot PUBLIC \"-//UC Berkeley//DTD PlotML 1//EN\"");
228            output.println(
229                    "    \"http://ptolemy.eecs.berkeley.edu/xml/dtd/PlotML_1.dtd\">");
230        } else {
231            output.println("<?xml version=\"1.0\" standalone=\"no\"?>");
232            output.println("<!DOCTYPE plot SYSTEM \"" + dtd + "\">");
233        }
234
235        output.println("<plot>");
236        output.println("<!-- Ptolemy plot, version " + PTPLOT_RELEASE
237                + " , PlotML format. Exported from Histogram. -->");
238
239        super.writeFormat(output);
240        output.println("<barGraph width=\"" + _barwidth * _binWidth
241                + "\" offset=\"" + _baroffset * _binWidth + "\"/>");
242
243        for (int dataset = 0; dataset < _points.size(); dataset++) {
244            // Write the dataset directive
245            String legend = getLegend(dataset);
246
247            if (legend != null) {
248                output.println(
249                        "<dataset name=\"" + legend + "\" connected=\"no\">");
250            } else {
251                output.println("<dataset connected=\"no\">");
252            }
253
254            Hashtable data = (Hashtable) _histogram.elementAt(dataset);
255            Enumeration keys = data.keys();
256
257            while (keys.hasMoreElements()) {
258                Integer bin = (Integer) keys.nextElement();
259                Integer count = (Integer) data.get(bin);
260
261                // The X axis value is a bit complex to get.
262                int xValue = (int) (bin.intValue() * _binWidth + _binOffset);
263                output.println("<p x=\"" + xValue + "\" y=\"" + count.intValue()
264                        + "\"/>");
265            }
266
267            output.println("</dataset>");
268        }
269
270        output.println("</plot>");
271        output.flush();
272    }
273
274    /** Rescale so that the data that is currently plotted just fits.
275     *  This overrides the base class method to ensure that the
276     *  fill is actually performed in the event dispatch thread.
277     *  In order to work well with swing and be thread safe, this method
278     *  actually defers execution to the event dispatch thread, where
279     *  all user interface actions are performed.  Thus, the fill will
280     *  not occur immediately (unless you call this method from within
281     *  the event dispatch thread).  All the methods that do this deferring
282     *  coordinate so that they are executed in the order that you
283     *  called them.
284     */
285    @Override
286    public synchronized void fillPlot() {
287        Runnable doFill = new Runnable() {
288            @Override
289            public void run() {
290                _fillPlot();
291            }
292        };
293
294        deferIfNecessary(doFill);
295    }
296
297    /** Create a sample plot.
298     */
299    @Override
300    public synchronized void samplePlot() {
301        // Create a sample plot.
302        clear(true);
303
304        setTitle("Sample histogram");
305        setXLabel("values");
306        setYLabel("count");
307
308        for (int i = 0; i <= 1000; i++) {
309            this.addPoint(0, 5.0 * Math.cos(Math.PI * i / 500.0));
310            this.addPoint(1, 10.0 * _random.nextDouble() - 5.0);
311            this.addPoint(2, 2.0 * _random.nextGaussian());
312        }
313
314        this.repaint();
315    }
316
317    /** Set the width and offset of the bars.  Both are specified
318     *  as a fraction of a bin width.  The offset is the amount by which the
319     *  i <sup>th</sup> data set is shifted to the right, so that it
320     *  peeks out from behind the earlier data sets.
321     *  @param width The width of the bars.
322     *  @param offset The offset per data set.
323     */
324    public synchronized void setBars(double width, double offset) {
325        // Ensure replot of offscreen buffer.
326        _plotImage = null;
327        _barwidth = width;
328        _baroffset = offset;
329    }
330
331    /** Set the offset of the bins, in whatever units the data are given.
332     *  Without calling this, each bin is centered at <i>x</i> = <i>nw</i>,
333     *  where <i>w</i> is the width of the bin and <i>n</i> is an integer.
334     *  That bin represents values in the range (<i>x - w/2, x + w/2</i>).
335     *  If this method is called with argument <i>o</i>, then each bin is
336     *  centered at <i>x = nw + o</i>, and represents values in the range
337     *  (<i>x - w/2 + o, x + w/2 + o</i>).  So for example, if <i>o = w/2</i>,
338     *  then each bin represents values from <i>nw</i> to
339     *  (<i>n</i> + 1)<i>w</i>) for some integer <i>n</i>.
340     *  @param offset The bin offset.
341     */
342    public synchronized void setBinOffset(double offset) {
343        // Ensure replot of offscreen buffer.
344        _plotImage = null;
345        _binOffset = offset;
346    }
347
348    /** Set the width of the bins, in whatever units the data are given.
349     *  @param width The width of the bins.
350     */
351    public void setBinWidth(double width) {
352        // Ensure replot of offscreen buffer.
353        _plotImage = null;
354        _binWidth = width;
355    }
356
357    /** Set the seed of the random number generator used to create
358     *  the sample plot.  This method is primarily used for testing.
359     *  @param seed The seed.
360     *  @see #samplePlot()
361     */
362    public void setSeed(Long seed) {
363        _random.setSeed(seed);
364    }
365
366    /** Write the current data and plot configuration to the
367     *  specified stream in PlotML syntax.  This writes the histogram,
368     *  not the raw data, in such a way that the Plot class (not the
369     *  Histogram class) will correctly render it.
370     *  The URL (relative or absolute) for the DTD is
371     *  given as the second argument.  If that argument is null,
372     *  then the PlotML PUBLIC DTD is referenced, resulting in a file
373     *  that can be read by a PlotML parser without any external file
374     *  references, as long as that parser has local access to the DTD.
375     *  The output is buffered, and is flushed before exiting.
376     *  @param out An output writer.
377     *  @param dtd The reference (URL) for the DTD, or null to use the
378     *   PUBLIC DTD.
379     */
380    @Override
381    public synchronized void write(Writer out, String dtd) {
382        // Auto-flush is disabled.
383        PrintWriter output = new PrintWriter(new BufferedWriter(out), false);
384        exportToPlot(output, dtd);
385    }
386
387    /** Write plot data information to the specified output stream in PlotML.
388     *  @param output A buffered print writer.
389     */
390    @Override
391    public synchronized void writeData(PrintWriter output) {
392        super.writeData(output);
393
394        for (int dataset = 0; dataset < _points.size(); dataset++) {
395            // Write the dataset directive
396            String legend = getLegend(dataset);
397
398            if (legend != null) {
399                output.println("<dataset name=\"" + legend + "\">");
400            } else {
401                output.println("<dataset>");
402            }
403
404            // Write the data
405            Vector pts = (Vector) _points.elementAt(dataset);
406
407            for (int pointnum = 0; pointnum < pts.size(); pointnum++) {
408                Double pt = (Double) pts.elementAt(pointnum);
409                output.println("<p y=\"" + pt.doubleValue() + "\"/>");
410            }
411
412            output.println("</dataset>");
413        }
414    }
415
416    /** Write plot format information to the specified output stream.
417     *  @param output A buffered print writer.
418     */
419    @Override
420    public synchronized void writeFormat(PrintWriter output) {
421        super.writeFormat(output);
422
423        // NOTE: Regrettably, the meaning of the barGraph data is
424        // different for a histogram than for a normal plot.
425        // In a histogram, it is proportional to the bin width.
426        // Thus, this is not the same as the corresponding line
427        // in exportToPlot().
428        output.println("<barGraph width=\"" + _barwidth + "\" offset=\""
429                + _baroffset + "\"/>");
430
431        output.println("<bin width=\"" + _binWidth + "\" offset=\"" + _binOffset
432                + "\"/>");
433    }
434
435    ///////////////////////////////////////////////////////////////////
436    ////                         protected methods                 ////
437
438    /** Check the argument to ensure that it is a valid data set index.
439     *  If it is less than zero, throw an IllegalArgumentException (which
440     *  is a runtime exception).  If it does not refer to an existing
441     *  data set, then fill out the _points and _histogram
442     *  Vectors so that they refer
443     *  to all existing data sets.
444     *  @param dataset The data set index.
445     */
446    protected void _checkDatasetIndex(int dataset) {
447        if (dataset < 0) {
448            throw new IllegalArgumentException("Plot._checkDatasetIndex: Cannot"
449                    + " give a negative number for the data set index.");
450        }
451
452        while (dataset >= _points.size()) {
453            _points.addElement(new Vector());
454            _histogram.addElement(new Hashtable());
455        }
456    }
457
458    /** Draw bar from the specified point to the y axis.
459     *  If the specified point is below the y axis or outside the
460     *  x range, do nothing.  If the <i>clip</i> argument is true,
461     *  then do not draw above the y range.
462     *  Note that paint() should be called before
463     *  calling this method so that _xscale and _yscale are properly set.
464     *  @param graphics The graphics context.
465     *  @param dataset The index of the dataset.
466     *  @param xpos The x position.
467     *  @param ypos The y position.
468     *  @param clip If true, then do not draw outside the range.
469     */
470    protected void _drawBar(Graphics graphics, int dataset, long xpos,
471            long ypos, boolean clip) {
472        if (clip) {
473            if (ypos < _uly) {
474                ypos = _uly;
475            }
476
477            if (ypos > _lry) {
478                ypos = _lry;
479            }
480        }
481
482        if (ypos <= _lry && xpos <= _lrx && xpos >= _ulx) {
483            // left x position of bar.
484            int barlx = (int) (xpos - _barwidth * _binWidth * _xscale / 2
485                    + dataset * _baroffset * _binWidth * _xscale);
486
487            // right x position of bar
488            int barrx = (int) (barlx + _barwidth * _binWidth * _xscale);
489
490            if (barlx < _ulx) {
491                barlx = _ulx;
492            }
493
494            if (barrx > _lrx) {
495                barrx = _lrx;
496            }
497
498            // Make sure that a bar is always at least one pixel wide.
499            if (barlx >= barrx) {
500                barrx = barlx + 1;
501            }
502
503            // The y position of the zero line.
504            long zeroypos = _lry - (long) ((0 - _yMin) * _yscale);
505
506            if (_lry < zeroypos) {
507                zeroypos = _lry;
508            }
509
510            if (_uly > zeroypos) {
511                zeroypos = _uly;
512            }
513
514            if (_yMin >= 0 || ypos <= zeroypos) {
515                graphics.fillRect(barlx, (int) ypos, barrx - barlx,
516                        (int) (zeroypos - ypos));
517            } else {
518                graphics.fillRect(barlx, (int) zeroypos, barrx - barlx,
519                        (int) (ypos - zeroypos));
520            }
521        }
522    }
523
524    /** Draw the axes and then plot the histogram. If the second
525     *  argument is true, clear the display first.
526     *  This method is called by paint().  To cause it to be called you
527     *  would normally call repaint(), which eventually causes paint() to
528     *  be called.
529     *  <p>
530     *  Note that this is synchronized so that points are not added
531     *  by other threads while the drawing is occurring.  This method
532     *  should be called only from the event dispatch thread, consistent
533     *  with swing policy.
534     *  @param graphics The graphics context.
535     *  @param clearfirst If true, clear the plot before proceeding.
536     *  @param drawRect A specification of the size.
537     */
538    @Override
539    protected synchronized void _drawPlot(Graphics graphics, boolean clearfirst,
540            Rectangle drawRect) {
541        // This method called when images are exported.
542
543        // We must call PlotBox._drawPlot() before calling _drawPlotPoint
544        // so that _xscale and _yscale are set.
545        super._drawPlot(graphics, clearfirst, drawRect);
546
547        _showing = true;
548
549        // Plot the histograms in reverse order so that the first colors
550        // appear on top.
551        for (int dataset = _points.size() - 1; dataset >= 0; dataset--) {
552            Hashtable data = (Hashtable) _histogram.elementAt(dataset);
553            Enumeration keys = data.keys();
554
555            while (keys.hasMoreElements()) {
556                Integer bin = (Integer) keys.nextElement();
557                Integer count = (Integer) data.get(bin);
558                _drawPlotPoint(graphics, dataset, bin.intValue(),
559                        count.intValue());
560            }
561        }
562    }
563
564    /** Parse a line that gives plotting information. Return true if
565     *  the line is recognized.  Lines with syntax errors are ignored.
566     *  @param line A command line.
567     *  It is not synchronized, so its caller should be.
568     *  @return True if the line is recognized.
569     */
570    @Override
571    protected boolean _parseLine(String line) {
572        // parse only if the super class does not recognize the line.
573        if (super._parseLine(line)) {
574            return true;
575        } else {
576            // We convert the line to lower case so that the command
577            // names are case insensitive
578            String lcLine = line.toLowerCase(Locale.getDefault());
579
580            if (lcLine.startsWith("dataset:")) {
581                // new data set
582                _currentdataset++;
583
584                if (lcLine.length() > 0) {
585                    String legend = line.substring(8).trim();
586
587                    if (legend != null && legend.length() > 0) {
588                        addLegend(_currentdataset, legend);
589                    }
590                }
591
592                return true;
593            } else if (lcLine.startsWith("bars:")
594                    || lcLine.startsWith("bargraph:")) {
595                // The PlotML code uses barGraph, but the older style
596                // uses bars
597                int comma = line.indexOf(",", 5);
598                String barwidth;
599                String baroffset = null;
600
601                if (comma > 0) {
602                    barwidth = line.substring(5, comma).trim();
603                    baroffset = line.substring(comma + 1).trim();
604                } else {
605                    barwidth = line.substring(5).trim();
606                }
607
608                try {
609                    Double bwidth = Double.valueOf(barwidth);
610                    double boffset = _baroffset;
611
612                    if (baroffset != null) {
613                        boffset = Double.valueOf(baroffset).doubleValue();
614                    }
615
616                    setBars(bwidth.doubleValue(), boffset);
617                } catch (NumberFormatException e) {
618                    // ignore if format is bogus.
619                }
620
621                return true;
622            } else if (lcLine.startsWith("binwidth:")) {
623                String binwidth = line.substring(9).trim();
624
625                try {
626                    Double bwidth = Double.valueOf(binwidth);
627                    setBinWidth(bwidth.doubleValue());
628                } catch (NumberFormatException e) {
629                    // ignore if format is bogus.
630                }
631
632                return true;
633            } else if (lcLine.startsWith("binoffset:")) {
634                String binoffset = line.substring(10).trim();
635
636                try {
637                    Double boffset = Double.valueOf(binoffset);
638                    setBinOffset(boffset.doubleValue());
639                } catch (NumberFormatException e) {
640                    // ignore if format is bogus.
641                }
642
643                return true;
644            } else if (lcLine.startsWith("numsets:")) {
645                // Obsolete field... ignore.
646                return true;
647            } else if (line.startsWith("move:")) {
648                // deal with 'move: 1 2' and 'move:2 2'
649                line = line.substring(5, line.length()).trim();
650            } else if (line.startsWith("move")) {
651                // deal with 'move 1 2' and 'move2 2'
652                line = line.substring(4, line.length()).trim();
653            } else if (line.startsWith("draw:")) {
654                // a connected point, if connect is enabled.
655                line = line.substring(5, line.length()).trim();
656            } else if (line.startsWith("draw")) {
657                // a connected point, if connect is enabled.
658                line = line.substring(4, line.length()).trim();
659            }
660
661            line = line.trim();
662
663            // Handle Plot formats
664            int fieldsplit = line.indexOf(",");
665
666            if (fieldsplit == -1) {
667                fieldsplit = line.indexOf(" ");
668            }
669
670            if (fieldsplit == -1) {
671                fieldsplit = line.indexOf("\t"); // a tab
672            }
673
674            if (fieldsplit == -1) {
675                // Have just one number per line
676                try {
677                    Double xpt = Double.valueOf(line);
678                    addPoint(_currentdataset, xpt.doubleValue());
679                    return true;
680                } catch (NumberFormatException e) {
681                    // ignore if format is bogus.
682                }
683            } else {
684                String y = line.substring(fieldsplit + 1).trim();
685
686                try {
687                    Double ypt = Double.valueOf(y);
688                    addPoint(_currentdataset, ypt.doubleValue());
689                    return true;
690                } catch (NumberFormatException e) {
691                    // ignore if format is bogus.
692                }
693            }
694        }
695
696        return false;
697    }
698
699    /** Reset a scheduled redraw tasks.
700     */
701    @Override
702    protected void _resetScheduledTasks() {
703        Runnable redraw = new RunnableExceptionCatcher(new Runnable() {
704            @Override
705            public void run() {
706                _scheduledBinsToAdd.clear();
707            }
708        });
709        synchronized (this) {
710            deferIfNecessary(redraw);
711        }
712    }
713
714    /** Perform a scheduled redraw.
715     */
716    @Override
717    protected void _scheduledRedraw() {
718        if (_needPlotRefill || _needBinRedraw) {
719            Runnable redraw = new RunnableExceptionCatcher(new Runnable() {
720                @Override
721                public void run() {
722                    ArrayList<HashSet<Integer>> scheduledBinsToAdd = new ArrayList<HashSet<Integer>>();
723                    for (int i = 0; i < _scheduledBinsToAdd.size(); ++i) {
724                        HashSet<Integer> element = _scheduledBinsToAdd.get(i);
725                        scheduledBinsToAdd
726                                .add((HashSet<Integer>) element.clone());
727                        element.clear();
728                    }
729                    _needBinRedraw = false;
730                    if (_needPlotRefill) {
731                        fillPlot();
732                        _needPlotRefill = false;
733                    } else {
734                        Graphics graphics = getGraphics();
735                        if (graphics != null) {
736                            {
737                                int nbrOfDataSets = scheduledBinsToAdd.size();
738                                for (int i = 0; i < nbrOfDataSets; ++i) {
739                                    Hashtable bins = (Hashtable) _histogram
740                                            .elementAt(i);
741                                    for (Integer bin : scheduledBinsToAdd
742                                            .get(i)) {
743                                        _drawPlotPoint(graphics, i, bin,
744                                                (Integer) bins.get(bin));
745                                    }
746                                }
747                            }
748                        }
749                    }
750                }
751            });
752            synchronized (this) {
753                deferIfNecessary(redraw);
754            }
755        }
756    }
757
758    ///////////////////////////////////////////////////////////////////
759    ////                         protected variables               ////
760
761    /**  The current dataset.
762     *         @serial
763     */
764    protected int _currentdataset = -1;
765
766    /**  A vector of datasets.
767     *         @serial
768     */
769    protected Vector _points = new Vector();
770
771    /**  A vector of histogram data.
772     *         @serial
773     */
774    protected Vector _histogram = new Vector();
775
776    ///////////////////////////////////////////////////////////////////
777    ////                         private methods                   ////
778
779    /*  In the specified data set, add the specified value to the
780     *  histogram.  Data set indices begin with zero.  If the data set
781     *  does not exist, create it.
782     *  The new point will visibly alter the histogram if the plot is visible
783     *  on the screen.  Otherwise, it will be drawn the next time the histogram
784     *  is drawn on the screen.
785     *
786     *  This is not synchronized, so the caller should be.  Moreover, this
787     *  should only be called in the event dispatch thread. It should only
788     *  be called by _executeDeferredActions().
789     *
790     *  @param dataset The data set index.
791     *  @param value The new value.
792     */
793    private void _addPoint(int dataset, double value) {
794        // Ensure replot of offscreen buffer.
795        _plotImage = null;
796
797        _checkDatasetIndex(dataset);
798
799        // Calculate the bin number.
800        int bin = (int) Math.round((value - _binOffset) / _binWidth);
801        Integer binobj = Integer.valueOf(bin);
802
803        // Add to the appropriate bin
804        Hashtable bins = (Hashtable) _histogram.elementAt(dataset);
805        int count;
806
807        if (bins.containsKey(binobj)) {
808            // increase the count
809            count = 1 + ((Integer) bins.get(binobj)).intValue();
810            bins.put(binobj, Integer.valueOf(count));
811        } else {
812            // start a new entry.
813            count = 1;
814            bins.put(binobj, Integer.valueOf(count));
815        }
816
817        // For auto-ranging, keep track of min and max.
818        double x = bin * _binWidth + _binOffset;
819
820        if (x < _xBottom) {
821            if (_automaticRescale() && _xTop != -Double.MAX_VALUE
822                    && _xBottom != Double.MAX_VALUE) {
823                _needPlotRefill = true;
824                _xBottom = x - (_xTop - _xBottom);
825            } else {
826                _xBottom = x;
827            }
828        }
829
830        double xtop = x + _binWidth / 2.0;
831
832        if (xtop > _xTop) {
833            if (_automaticRescale() && _xTop != -Double.MAX_VALUE
834                    && _xBottom != Double.MAX_VALUE) {
835                _needPlotRefill = true;
836                _xTop = xtop + _xTop - _xBottom;
837            } else {
838                _xTop = xtop;
839            }
840        }
841
842        _yBottom = 0.0;
843
844        if (count > _yTop) {
845            if (_automaticRescale() && _yTop != -Double.MAX_VALUE
846                    && _yBottom != Double.MAX_VALUE) {
847                _needPlotRefill = true;
848                _yTop = count + _yTop - _yBottom;
849            } else {
850                _yTop = count;
851            }
852        }
853
854        Vector pts = (Vector) _points.elementAt(dataset);
855        pts.addElement(Double.valueOf(value));
856
857        // Draw the point on the screen only if the plot is showing.
858        // Need to check that graphics is not null because plot may have
859        // been dismissed.
860        Graphics graphics = getGraphics();
861
862        if (_showing && graphics != null) {
863            // In swing, updates to showing graphics must be done in the
864            // event thread, not here.  Thus, we have to queue the request.
865            final int pendingDataset = dataset;
866            final int pendingBin = bin;
867            final int pendingCount = count;
868
869            if (!_timedRepaint()) {
870                // We are in the event thread, so this is safe...
871                _drawPlotPoint(graphics, pendingDataset, pendingBin,
872                        pendingCount);
873            } else {
874                if (!_needPlotRefill) {
875                    _scheduleBinRedraw(dataset, bin);
876                }
877            }
878        }
879    }
880
881    /*  Clear the plot of all data points.  If the argument is true, then
882     *  reset all parameters to their initial conditions, including
883     *  the persistence, plotting format, and axes formats.
884     *  For the change to take effect, you must call repaint().
885     *
886     *  @param format If true, clear the format controls as well.
887     */
888    private void _clear(boolean format) {
889        // Ensure replot of offscreen buffer.
890        _plotImage = null;
891
892        super.clear(format);
893        _currentdataset = -1;
894        _points = new Vector();
895        _histogram = new Vector();
896        _showing = false;
897        _resetScheduledTasks();
898
899        if (format) {
900            // Reset format controls
901            _barwidth = 0.5;
902            _baroffset = 0.15;
903            _binWidth = 1.0;
904            _binOffset = 0.5;
905        }
906    }
907
908    /* Draw the specified histogram bar.
909     * Note that paint() should be called before
910     * calling this method so that it calls _drawPlot(), which sets
911     * _xscale and _yscale. Note that this does not check the dataset
912     * index.  It is up to the caller to do that.
913     *
914     * Note that this method is not synchronized, so the caller should be.
915     * Moreover this method should always be called from the event thread
916     * when being used to write to the screen.
917     */
918    private void _drawPlotPoint(Graphics graphics, int dataset, int bin,
919            int count) {
920        // Set the color
921        if (_usecolor) {
922            int color = dataset % _colors.length;
923            graphics.setColor(_colors[color]);
924        } else {
925            graphics.setColor(_foreground);
926        }
927
928        double y = count;
929        double x = _binWidth * bin + _binOffset;
930
931        if (_xlog) {
932            if (x <= 0.0) {
933                System.err.println("Can't plot non-positive X values "
934                        + "when the logarithmic X axis value is specified: "
935                        + x);
936                return;
937            }
938
939            x = Math.log(x) * _LOG10SCALE;
940        }
941
942        if (_ylog) {
943            if (y <= 0.0) {
944                System.err.println("Can't plot non-positive Y values "
945                        + "when the logarithmic Y axis value is specified: "
946                        + y);
947                return;
948            }
949
950            y = Math.log(y) * _LOG10SCALE;
951        }
952
953        // Use long here because these numbers can be quite large
954        // (when we are zoomed out a lot).
955        long ypos = _lry - (long) ((y - _yMin) * _yscale);
956        long xpos = _ulx + (long) ((x - _xMin) * _xscale);
957
958        _drawBar(graphics, dataset, xpos, ypos, true);
959
960        // Restore the color, in case the box gets redrawn.
961        graphics.setColor(_foreground);
962    }
963
964    /* Rescale so that the data that is currently plotted just fits.
965     * This simply calls the base class.
966     *
967     * This is not synchronized, so the caller should be.  Moreover, this
968     * should only be called in the event dispatch thread. It should only
969     * be called by _executeDeferredActions().
970     */
971    private void _fillPlot() {
972        super.fillPlot();
973    }
974
975    /** Schedule a bin to be (re)drawn due to the addition of point.
976     */
977    private void _scheduleBinRedraw(int dataset, int bin) {
978        while (_scheduledBinsToAdd.size() <= dataset) {
979            _scheduledBinsToAdd.add(new HashSet<Integer>());
980        }
981        _scheduledBinsToAdd.get(dataset).add(bin);
982        _needBinRedraw = true;
983    }
984
985    ///////////////////////////////////////////////////////////////////
986    ////                         private variables                 ////
987
988    /** @serial The width of a bar. */
989    private volatile double _barwidth = 0.5;
990
991    /** @serial The offset between bars. */
992    private volatile double _baroffset = 0.15;
993
994    /** @serial The width of a bin. */
995    private double _binWidth = 1.0;
996
997    /** @serial The offset between bins. */
998    private volatile double _binOffset = 0.5;
999
1000    // True when a bin has been changed and it need to be repainted
1001    // by the next scheduled repaint.
1002    private boolean _needBinRedraw = false;
1003
1004    // True when a the plot need to be refilled
1005    // by the next scheduled repaint.
1006    private boolean _needPlotRefill = false;
1007
1008    // _scheduledBinsToAdd a a list a bins that should be added by the scheduled
1009    // repaint.
1010    private ArrayList<HashSet<Integer>> _scheduledBinsToAdd = new ArrayList<HashSet<Integer>>();
1011
1012    /** Random number generator used to create the sample plot. */
1013    private Random _random = new Random();
1014
1015    /** @serial  Set by _drawPlot(), and reset by clear(). */
1016    private boolean _showing = false;
1017}