Back to index

wims  3.65+svn20090927
Histogram.java
Go to the documentation of this file.
00001 /* A histogram plotter.
00002 
00003  @Copyright (c) 1997-2007 The Regents of the University of California.
00004  All rights reserved.
00005 
00006  Permission is hereby granted, without written agreement and without
00007  license or royalty fees, to use, copy, modify, and distribute this
00008  software and its documentation for any purpose, provided that the
00009  above copyright notice and the following two paragraphs appear in all
00010  copies of this software.
00011 
00012  IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
00013  FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
00014  ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
00015  THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
00016  SUCH DAMAGE.
00017 
00018  THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
00019  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
00020  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
00021  PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
00022  CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
00023  ENHANCEMENTS, OR MODIFICATIONS.
00024 
00025  PT_COPYRIGHT_VERSION_2
00026  COPYRIGHTENDKEY
00027  */
00028 package ptolemy.plot;
00029 
00030 import java.awt.Graphics;
00031 import java.io.BufferedWriter;
00032 import java.io.PrintWriter;
00033 import java.io.Writer;
00034 import java.util.Enumeration;
00035 import java.util.Hashtable;
00036 import java.util.Random;
00037 import java.util.Vector;
00038 
00041 
00122 public class Histogram extends PlotBox {
00125 
00132     public void addLegend(int dataset, String legend) {
00133         _checkDatasetIndex(dataset);
00134         super.addLegend(dataset, legend);
00135     }
00136 
00155     public synchronized void addPoint(final int dataset, final double value) {
00156         Runnable doAddPoint = new Runnable() {
00157             public void run() {
00158                 _addPoint(dataset, value);
00159             }
00160         };
00161 
00162         deferIfNecessary(doAddPoint);
00163     }
00164 
00174     public synchronized void addPoint(int dataset, double x, double y,
00175             boolean connected) {
00176         addPoint(dataset, y);
00177     }
00178 
00194     public synchronized void clear(final boolean format) {
00195         Runnable doClear = new Runnable() {
00196             public void run() {
00197                 _clear(format);
00198             }
00199         };
00200 
00201         deferIfNecessary(doClear);
00202     }
00203 
00211     public synchronized void exportToPlot(PrintWriter output, String dtd) {
00212         if (dtd == null) {
00213             output.println("<?xml version=\"1.0\" standalone=\"yes\"?>");
00214             output
00215                     .println("<!DOCTYPE plot PUBLIC \"-//UC Berkeley//DTD PlotML 1//EN\"");
00216             output
00217                     .println("    \"http://ptolemy.eecs.berkeley.edu/xml/dtd/PlotML_1.dtd\">");
00218         } else {
00219             output.println("<?xml version=\"1.0\" standalone=\"no\"?>");
00220             output.println("<!DOCTYPE plot SYSTEM \"" + dtd + "\">");
00221         }
00222 
00223         output.println("<plot>");
00224         output.println("<!-- Ptolemy plot, version " + PTPLOT_RELEASE
00225                 + " , PlotML format. Exported from Histogram. -->");
00226 
00227         super.writeFormat(output);
00228         output.println("<barGraph width=\"" + (_barwidth * _binWidth)
00229                 + "\" offset=\"" + (_baroffset * _binWidth) + "\"/>");
00230 
00231         for (int dataset = 0; dataset < _points.size(); dataset++) {
00232             // Write the dataset directive
00233             String legend = getLegend(dataset);
00234 
00235             if (legend != null) {
00236                 output.println("<dataset name=\"" + legend
00237                         + "\" connected=\"no\">");
00238             } else {
00239                 output.println("<dataset connected=\"no\">");
00240             }
00241 
00242             Hashtable data = (Hashtable) _histogram.elementAt(dataset);
00243             Enumeration keys = data.keys();
00244 
00245             while (keys.hasMoreElements()) {
00246                 Integer bin = (Integer) keys.nextElement();
00247                 Integer count = (Integer) data.get(bin);
00248 
00249                 // The X axis value is a bit complex to get.
00250                 int xValue = (int) ((bin.intValue() * _binWidth) + _binOffset);
00251                 output.println("<p x=\"" + xValue + "\" y=\""
00252                         + count.intValue() + "\"/>");
00253             }
00254 
00255             output.println("</dataset>");
00256         }
00257 
00258         output.println("</plot>");
00259         output.flush();
00260     }
00261 
00273     public synchronized void fillPlot() {
00274         Runnable doFill = new Runnable() {
00275             public void run() {
00276                 _fillPlot();
00277             }
00278         };
00279 
00280         deferIfNecessary(doFill);
00281     }
00282 
00285     public synchronized void samplePlot() {
00286         // Create a sample plot.
00287         clear(true);
00288 
00289         setTitle("Sample histogram");
00290         setXLabel("values");
00291         setYLabel("count");
00292 
00293         Random random = new Random();
00294 
00295         for (int i = 0; i <= 1000; i++) {
00296             this.addPoint(0, 5.0 * Math.cos((Math.PI * i) / 500.0));
00297             this.addPoint(1, (10.0 * random.nextDouble()) - 5.0);
00298             this.addPoint(2, 2.0 * random.nextGaussian());
00299         }
00300 
00301         this.repaint();
00302     }
00303 
00311     public synchronized void setBars(double width, double offset) {
00312         // Ensure replot of offscreen buffer.
00313         _plotImage = null;
00314         _barwidth = width;
00315         _baroffset = offset;
00316     }
00317 
00329     public synchronized void setBinOffset(double offset) {
00330         // Ensure replot of offscreen buffer.
00331         _plotImage = null;
00332         _binOffset = offset;
00333     }
00334 
00338     public void setBinWidth(double width) {
00339         // Ensure replot of offscreen buffer.
00340         _plotImage = null;
00341         _binWidth = width;
00342     }
00343 
00358     public synchronized void write(Writer out, String dtd) {
00359         // Auto-flush is disabled.
00360         PrintWriter output = new PrintWriter(new BufferedWriter(out), false);
00361         exportToPlot(output, dtd);
00362     }
00363 
00367     public synchronized void writeData(PrintWriter output) {
00368         super.writeData(output);
00369 
00370         for (int dataset = 0; dataset < _points.size(); dataset++) {
00371             // Write the dataset directive
00372             String legend = getLegend(dataset);
00373 
00374             if (legend != null) {
00375                 output.println("<dataset name=\"" + legend + "\">");
00376             } else {
00377                 output.println("<dataset>");
00378             }
00379 
00380             // Write the data
00381             Vector pts = (Vector) _points.elementAt(dataset);
00382 
00383             for (int pointnum = 0; pointnum < pts.size(); pointnum++) {
00384                 Double pt = (Double) pts.elementAt(pointnum);
00385                 output.println("<p y=\"" + pt.doubleValue() + "\"/>");
00386             }
00387 
00388             output.println("</dataset>");
00389         }
00390     }
00391 
00395     public synchronized void writeFormat(PrintWriter output) {
00396         super.writeFormat(output);
00397 
00398         // NOTE: Regrettably, the meaning of the barGraph data is
00399         // different for a histogram than for a normal plot.
00400         // In a histogram, it is proportional to the bin width.
00401         // Thus, this is not the same as the corresponding line
00402         // in exportToPlot().
00403         output.println("<barGraph width=\"" + _barwidth + "\" offset=\""
00404                 + _baroffset + "\"/>");
00405 
00406         output.println("<bin width=\"" + _binWidth + "\" offset=\""
00407                 + _binOffset + "\"/>");
00408     }
00409 
00412 
00421     protected void _checkDatasetIndex(int dataset) {
00422         if (dataset < 0) {
00423             throw new IllegalArgumentException(
00424                     "Plot._checkDatasetIndex: Cannot"
00425                             + " give a negative number for the data set index.");
00426         }
00427 
00428         while (dataset >= _points.size()) {
00429             _points.addElement(new Vector());
00430             _histogram.addElement(new Hashtable());
00431         }
00432     }
00433 
00446     protected void _drawBar(Graphics graphics, int dataset, long xpos,
00447             long ypos, boolean clip) {
00448         if (clip) {
00449             if (ypos < _uly) {
00450                 ypos = _uly;
00451             }
00452 
00453             if (ypos > _lry) {
00454                 ypos = _lry;
00455             }
00456         }
00457 
00458         if ((ypos <= _lry) && (xpos <= _lrx) && (xpos >= _ulx)) {
00459             // left x position of bar.
00460             int barlx = (int) (xpos - ((_barwidth * _binWidth * _xscale) / 2) + (dataset
00461                     * _baroffset * _binWidth * _xscale));
00462 
00463             // right x position of bar
00464             int barrx = (int) (barlx + (_barwidth * _binWidth * _xscale));
00465 
00466             if (barlx < _ulx) {
00467                 barlx = _ulx;
00468             }
00469 
00470             if (barrx > _lrx) {
00471                 barrx = _lrx;
00472             }
00473 
00474             // Make sure that a bar is always at least one pixel wide.
00475             if (barlx >= barrx) {
00476                 barrx = barlx + 1;
00477             }
00478 
00479             // The y position of the zero line.
00480             long zeroypos = _lry - (long) ((0 - _yMin) * _yscale);
00481 
00482             if (_lry < zeroypos) {
00483                 zeroypos = _lry;
00484             }
00485 
00486             if (_uly > zeroypos) {
00487                 zeroypos = _uly;
00488             }
00489 
00490             if ((_yMin >= 0) || (ypos <= zeroypos)) {
00491                 graphics.fillRect(barlx, (int) ypos, barrx - barlx,
00492                         (int) (zeroypos - ypos));
00493             } else {
00494                 graphics.fillRect(barlx, (int) zeroypos, barrx - barlx,
00495                         (int) (ypos - zeroypos));
00496             }
00497         }
00498     }
00499 
00513     protected synchronized void _drawPlot(Graphics graphics, boolean clearfirst) {
00514         // We must call PlotBox._drawPlot() before calling _drawPlotPoint
00515         // so that _xscale and _yscale are set.
00516         super._drawPlot(graphics, clearfirst);
00517 
00518         _showing = true;
00519 
00520         // Plot the histograms in reverse order so that the first colors
00521         // appear on top.
00522         for (int dataset = _points.size() - 1; dataset >= 0; dataset--) {
00523             Hashtable data = (Hashtable) _histogram.elementAt(dataset);
00524             Enumeration keys = data.keys();
00525 
00526             while (keys.hasMoreElements()) {
00527                 Integer bin = (Integer) keys.nextElement();
00528                 Integer count = (Integer) data.get(bin);
00529                 _drawPlotPoint(graphics, dataset, bin.intValue(), count
00530                         .intValue());
00531             }
00532         }
00533     }
00534 
00541     protected boolean _parseLine(String line) {
00542         // parse only if the super class does not recognize the line.
00543         if (super._parseLine(line)) {
00544             return true;
00545         } else {
00546             // We convert the line to lower case so that the command
00547             // names are case insensitive
00548             String lcLine = line.toLowerCase();
00549 
00550             if (lcLine.startsWith("dataset:")) {
00551                 // new data set
00552                 _currentdataset++;
00553 
00554                 if (lcLine.length() > 0) {
00555                     String legend = (line.substring(8)).trim();
00556 
00557                     if ((legend != null) && (legend.length() > 0)) {
00558                         addLegend(_currentdataset, legend);
00559                     }
00560                 }
00561 
00562                 return true;
00563             } else if (lcLine.startsWith("bars:")
00564                     || lcLine.startsWith("bargraph:")) {
00565                 // The PlotML code uses barGraph, but the older style
00566                 // uses bars
00567                 int comma = line.indexOf(",", 5);
00568                 String barwidth;
00569                 String baroffset = null;
00570 
00571                 if (comma > 0) {
00572                     barwidth = (line.substring(5, comma)).trim();
00573                     baroffset = (line.substring(comma + 1)).trim();
00574                 } else {
00575                     barwidth = (line.substring(5)).trim();
00576                 }
00577 
00578                 try {
00579                     Double bwidth = Double.valueOf(barwidth);
00580                     double boffset = _baroffset;
00581 
00582                     if (baroffset != null) {
00583                         boffset = (Double.valueOf(baroffset)).doubleValue();
00584                     }
00585 
00586                     setBars(bwidth.doubleValue(), boffset);
00587                 } catch (NumberFormatException e) {
00588                     // ignore if format is bogus.
00589                 }
00590 
00591                 return true;
00592             } else if (lcLine.startsWith("binwidth:")) {
00593                 String binwidth = (line.substring(9)).trim();
00594 
00595                 try {
00596                     Double bwidth = Double.valueOf(binwidth);
00597                     setBinWidth(bwidth.doubleValue());
00598                 } catch (NumberFormatException e) {
00599                     // ignore if format is bogus.
00600                 }
00601 
00602                 return true;
00603             } else if (lcLine.startsWith("binoffset:")) {
00604                 String binoffset = (line.substring(10)).trim();
00605 
00606                 try {
00607                     Double boffset = Double.valueOf(binoffset);
00608                     setBinOffset(boffset.doubleValue());
00609                 } catch (NumberFormatException e) {
00610                     // ignore if format is bogus.
00611                 }
00612 
00613                 return true;
00614             } else if (lcLine.startsWith("numsets:")) {
00615                 // Obsolete field... ignore.
00616                 return true;
00617             } else if (line.startsWith("move:")) {
00618                 // deal with 'move: 1 2' and 'move:2 2'
00619                 line = line.substring(5, line.length()).trim();
00620             } else if (line.startsWith("move")) {
00621                 // deal with 'move 1 2' and 'move2 2'
00622                 line = line.substring(4, line.length()).trim();
00623             } else if (line.startsWith("draw:")) {
00624                 // a connected point, if connect is enabled.
00625                 line = line.substring(5, line.length()).trim();
00626             } else if (line.startsWith("draw")) {
00627                 // a connected point, if connect is enabled.
00628                 line = line.substring(4, line.length()).trim();
00629             }
00630 
00631             line = line.trim();
00632 
00633             // Handle Plot formats
00634             int fieldsplit = line.indexOf(",");
00635 
00636             if (fieldsplit == -1) {
00637                 fieldsplit = line.indexOf(" ");
00638             }
00639 
00640             if (fieldsplit == -1) {
00641                 fieldsplit = line.indexOf("\t"); // a tab
00642             }
00643 
00644             if (fieldsplit == -1) {
00645                 // Have just one number per line
00646                 try {
00647                     Double xpt = Double.valueOf(line);
00648                     addPoint(_currentdataset, xpt.doubleValue());
00649                     return true;
00650                 } catch (NumberFormatException e) {
00651                     // ignore if format is bogus.
00652                 }
00653             } else {
00654                 String y = (line.substring(fieldsplit + 1)).trim();
00655 
00656                 try {
00657                     Double ypt = Double.valueOf(y);
00658                     addPoint(_currentdataset, ypt.doubleValue());
00659                     return true;
00660                 } catch (NumberFormatException e) {
00661                     // ignore if format is bogus.
00662                 }
00663             }
00664         }
00665 
00666         return false;
00667     }
00668 
00671 
00673     protected int _currentdataset = -1;
00674 
00676     protected Vector _points = new Vector();
00677 
00679     protected Vector _histogram = new Vector();
00680 
00683 
00684     /*  In the specified data set, add the specified value to the
00685      *  histogram.  Data set indices begin with zero.  If the data set
00686      *  does not exist, create it.
00687      *  The new point will visibly alter the histogram if the plot is visible
00688      *  on the screen.  Otherwise, it will be drawn the next time the histogram
00689      *  is drawn on the screen.
00690      *
00691      *  This is not synchronized, so the caller should be.  Moreover, this
00692      *  should only be called in the event dispatch thread. It should only
00693      *  be called by _executeDeferredActions().
00694      *
00695      *  @param dataset The data set index.
00696      *  @param value The new value.
00697      */
00698     private void _addPoint(int dataset, double value) {
00699         // Ensure replot of offscreen buffer.
00700         _plotImage = null;
00701 
00702         _checkDatasetIndex(dataset);
00703 
00704         // Calculate the bin number.
00705         int bin = (int) (Math.round((value - _binOffset) / _binWidth));
00706         Integer binobj = Integer.valueOf(bin);
00707 
00708         // Add to the appropriate bin
00709         Hashtable bins = (Hashtable) _histogram.elementAt(dataset);
00710         int count;
00711 
00712         if (bins.containsKey(binobj)) {
00713             // increase the count
00714             count = 1 + ((Integer) bins.get(binobj)).intValue();
00715             bins.put(binobj, Integer.valueOf(count));
00716         } else {
00717             // start a new entry.
00718             count = 1;
00719             bins.put(binobj, Integer.valueOf(count));
00720         }
00721 
00722         // For auto-ranging, keep track of min and max.
00723         double x = (bin * _binWidth) + _binOffset;
00724 
00725         if (x < _xBottom) {
00726             _xBottom = x;
00727         }
00728 
00729         double xtop = x + (_binWidth / 2.0);
00730 
00731         if (xtop > _xTop) {
00732             _xTop = xtop;
00733         }
00734 
00735         if (count > _yTop) {
00736             _yTop = count;
00737         }
00738 
00739         _yBottom = 0.0;
00740 
00741         Vector pts = (Vector) _points.elementAt(dataset);
00742         pts.addElement(Double.valueOf(value));
00743 
00744         // Draw the point on the screen only if the plot is showing.
00745         // Need to check that graphics is not null because plot may have
00746         // been dismissed.
00747         Graphics graphics = getGraphics();
00748 
00749         if (_showing && (graphics != null)) {
00750             // In swing, updates to showing graphics must be done in the
00751             // event thread, not here.  Thus, we have to queue the request.
00752             final int pendingDataset = dataset;
00753             final int pendingBin = bin;
00754             final int pendingCount = count;
00755 
00756             // We are in the event thread, so this is safe...
00757             _drawPlotPoint(graphics, pendingDataset, pendingBin, pendingCount);
00758         }
00759     }
00760 
00761     /*  Clear the plot of all data points.  If the argument is true, then
00762      *  reset all parameters to their initial conditions, including
00763      *  the persistence, plotting format, and axes formats.
00764      *  For the change to take effect, you must call repaint().
00765      *
00766      *  @param format If true, clear the format controls as well.
00767      */
00768     private void _clear(boolean format) {
00769         // Ensure replot of offscreen buffer.
00770         _plotImage = null;
00771 
00772         super.clear(format);
00773         _currentdataset = -1;
00774         _points = new Vector();
00775         _histogram = new Vector();
00776         _showing = false;
00777 
00778         if (format) {
00779             // Reset format controls
00780             _barwidth = 0.5;
00781             _baroffset = 0.15;
00782             _binWidth = 1.0;
00783             _binOffset = 0.5;
00784         }
00785     }
00786 
00787     /* Draw the specified histogram bar.
00788      * Note that paint() should be called before
00789      * calling this method so that it calls _drawPlot(), which sets
00790      * _xscale and _yscale. Note that this does not check the dataset
00791      * index.  It is up to the caller to do that.
00792      *
00793      * Note that this method is not synchronized, so the caller should be.
00794      * Moreover this method should always be called from the event thread
00795      * when being used to write to the screen.
00796      */
00797     private void _drawPlotPoint(Graphics graphics, int dataset, int bin,
00798             int count) {
00799         // Set the color
00800         if (_usecolor) {
00801             int color = dataset % _colors.length;
00802             graphics.setColor(_colors[color]);
00803         } else {
00804             graphics.setColor(_foreground);
00805         }
00806 
00807         double y = count;
00808         double x = (_binWidth * bin) + _binOffset;
00809 
00810         if (_xlog) {
00811             if (x <= 0.0) {
00812                 System.err.println("Can't plot non-positive X values "
00813                         + "when the logarithmic X axis value is specified: "
00814                         + x);
00815                 return;
00816             }
00817 
00818             x = Math.log(x) * _LOG10SCALE;
00819         }
00820 
00821         if (_ylog) {
00822             if (y <= 0.0) {
00823                 System.err.println("Can't plot non-positive Y values "
00824                         + "when the logarithmic Y axis value is specified: "
00825                         + y);
00826                 return;
00827             }
00828 
00829             y = Math.log(y) * _LOG10SCALE;
00830         }
00831 
00832         // Use long here because these numbers can be quite large
00833         // (when we are zoomed out a lot).
00834         long ypos = _lry - (long) ((y - _yMin) * _yscale);
00835         long xpos = _ulx + (long) ((x - _xMin) * _xscale);
00836 
00837         _drawBar(graphics, dataset, xpos, ypos, true);
00838 
00839         // Restore the color, in case the box gets redrawn.
00840         graphics.setColor(_foreground);
00841     }
00842 
00843     /* Rescale so that the data that is currently plotted just fits.
00844      * This simply calls the base class.
00845      *
00846      * This is not synchronized, so the caller should be.  Moreover, this
00847      * should only be called in the event dispatch thread. It should only
00848      * be called by _executeDeferredActions().
00849      */
00850     private void _fillPlot() {
00851         super.fillPlot();
00852     }
00853 
00856 
00858     private double _barwidth = 0.5;
00859 
00861     private double _baroffset = 0.15;
00862 
00864     private double _binWidth = 1.0;
00865 
00867     private double _binOffset = 0.5;
00868 
00870     private boolean _showing = false;
00871 }