/* A histogram plotter. @Copyright (c) 1997-2014 The Regents of the University of California. All rights reserved. Permission is hereby granted, without written agreement and without license or royalty fees, to use, copy, modify, and distribute this software and its documentation for any purpose, provided that the above copyright notice and the following two paragraphs appear in all copies of this software. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY */ package ptolemy.plot; import java.awt.Graphics; import java.awt.Rectangle; import java.io.BufferedWriter; import java.io.PrintWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashSet; import java.util.Hashtable; import java.util.Locale; import java.util.Random; import java.util.Vector; import ptolemy.util.RunnableExceptionCatcher; /////////////////////////////////////////////////////////////////// //// Histogram /** A histogram plotter. The plot can be configured and data can be provided either through a file with commands or through direct invocation of the public methods of the class. To read a file or a URL, use the read() method.
When calling the public methods, in most cases the changes will not be visible until paint() has been called. To request that this be done, call repaint(). One exception is addPoint(), which makes the affect of the new point visible immediately (or nearly immediately) if the plot is visible on the screen.
The ASCII format for the file file contains any number commands, one per line. Unrecognized commands and commands with syntax errors are ignored. Comments are denoted by a line starting with a pound sign "#". The recognized commands include those supported by the base class, plus a few more. The commands are case insensitive, but are usually capitalized. The number of data sets to be plotted does not need to be specified. Data sets are added as needed. Each dataset is identified with a color (see the base class).
The appearance of the histogram can be altered by the following commands:
Bars: width Bars: width, offsetThe width is a real number specifying the width of the bars as a fraction of the bin width. It usually has a value less than or equal to one, and defaults to 0.5. The offset is a real number specifying how much the bar of the i th data set is offset from the previous one. This allows bars to "peek out" from behind the ones in front. It defaults to 0.15. Note that the frontmost data set will be the first one.
The width of each bin of the histogram can be specified using:
BinWidth: widthThis is given in whatever units the data has. By default, each bin is centered at x = nw, where w is the width of the bin and n is an integer. That bin represents values in the range (x - w/2, x + w/2). The alignment of the bins can be changed with the following command:
BinOffset: offsetIf this method is used with argument o, then each bin is centered at x = nw + o, and represents values in the range (x - w/2 + o, x + w/2 + o). So for example, if o = w/2, then each bin represents values from nw to (n + 1)w for some integer n. The default offset is 0.5, half the default bin width.
To specify data to be plotted, start a data set with the following command:
DataSet: stringHere, string is a label that will appear in the legend. It is not necessary to enclose the string in quotation marks. To start a new dataset without giving it a name, use:
DataSet:In this case, no item will appear in the legend. New datasets are plotted behind the previous ones. The data itself is given by a sequence of numbers, one per line. The numbers are specified as strings that can be parsed by the Double parser in Java. It is also possible to specify the numbers using all the formats accepted by the Plot class, so that the same data may be plotted by both classes. The x data is ignored, and only the y data is used to calculate the histogram. @author Edward A. Lee, Contributor: Bert Rodiers @version $Id: Histogram.java 69607 2014-07-30 17:07:26Z cxh $ @since Ptolemy II 0.3 @Pt.ProposedRating Yellow (cxh) @Pt.AcceptedRating Yellow (cxh) */ @SuppressWarnings("serial") public class Histogram extends PlotBox { /////////////////////////////////////////////////////////////////// //// public methods //// /** Add a legend (displayed at the upper right) for the specified * data set with the specified string. Short strings generally * fit better than long strings. * @param dataset The dataset index. * @param legend The label for the dataset. */ @Override public void addLegend(int dataset, String legend) { _checkDatasetIndex(dataset); super.addLegend(dataset, legend); } /** In the specified data set, add the specified value to the * histogram. Data set indices begin with zero. If the data set * does not exist, create it. * The new point will visibly alter the histogram if the plot is visible * on the screen. Otherwise, it will be drawn the next time the histogram * is drawn on the screen. *
* In order to work well with swing and be thread safe, this method * actually defers execution to the event dispatch thread, where * all user interface actions are performed. Thus, the point will * not be added immediately (unless you call this method from within * the event dispatch thread). All the methods that do this deferring * coordinate so that they are executed in the order that you * called them. * * @param dataset The data set index. * @param value The new value. */ public synchronized void addPoint(final int dataset, final double value) { Runnable doAddPoint = new Runnable() { @Override public void run() { _addPoint(dataset, value); } }; deferIfNecessary(doAddPoint); } /** In the specified data set, add the specified y value to the * histogram. The x value and the connected arguments are * ignored. Data set indices begin with zero. If the data set * does not exist, create it. * @param dataset The data set index. * @param x Ignored. * @param y The Y position of the new point. * @param connected Ignored */ public synchronized void addPoint(int dataset, double x, double y, boolean connected) { addPoint(dataset, y); } /** Clear the plot of all data points. If the argument is true, then * reset all parameters to their initial conditions, including * the persistence, plotting format, and axes formats. * For the change to take effect, you must call repaint(). *
* In order to work well with swing and be thread safe, this method
* actually defers execution to the event dispatch thread, where
* all user interface actions are performed. Thus, the clear will
* not be executed immediately (unless you call this method from within
* the event dispatch thread). All the methods that do this deferring
* coordinate so that they are executed in the order that you
* called them.
*
* @param format If true, clear the format controls as well.
*/
@Override
public synchronized void clear(final boolean format) {
Runnable doClear = new Runnable() {
@Override
public void run() {
_clear(format);
}
};
deferIfNecessary(doClear);
}
/** Write plot data information to the specified output stream in PlotML,
* but in such a way that the Plot class can read it and reproduce the
* histogram. The ordinary mechanism for saving the histogram
* records the raw data and the configuration to be used by this class.
* @param output A buffered print writer.
* @param dtd The DTD, or null to reference the default.
*/
public synchronized void exportToPlot(PrintWriter output, String dtd) {
if (dtd == null) {
output.println("");
output.println("");
} else {
output.println("");
output.println("");
}
output.println("
* Note that this is synchronized so that points are not added
* by other threads while the drawing is occurring. This method
* should be called only from the event dispatch thread, consistent
* with swing policy.
* @param graphics The graphics context.
* @param clearfirst If true, clear the plot before proceeding.
* @param drawRect A specification of the size.
*/
@Override
protected synchronized void _drawPlot(Graphics graphics,
boolean clearfirst, Rectangle drawRect) {
// This method called when images are exported.
// We must call PlotBox._drawPlot() before calling _drawPlotPoint
// so that _xscale and _yscale are set.
super._drawPlot(graphics, clearfirst, drawRect);
_showing = true;
// Plot the histograms in reverse order so that the first colors
// appear on top.
for (int dataset = _points.size() - 1; dataset >= 0; dataset--) {
Hashtable data = (Hashtable) _histogram.elementAt(dataset);
Enumeration keys = data.keys();
while (keys.hasMoreElements()) {
Integer bin = (Integer) keys.nextElement();
Integer count = (Integer) data.get(bin);
_drawPlotPoint(graphics, dataset, bin.intValue(),
count.intValue());
}
}
}
/** Parse a line that gives plotting information. Return true if
* the line is recognized. Lines with syntax errors are ignored.
* @param line A command line.
* It is not synchronized, so its caller should be.
* @return True if the line is recognized.
*/
@Override
protected boolean _parseLine(String line) {
// parse only if the super class does not recognize the line.
if (super._parseLine(line)) {
return true;
} else {
// We convert the line to lower case so that the command
// names are case insensitive
String lcLine = line.toLowerCase(Locale.getDefault());
if (lcLine.startsWith("dataset:")) {
// new data set
_currentdataset++;
if (lcLine.length() > 0) {
String legend = line.substring(8).trim();
if (legend != null && legend.length() > 0) {
addLegend(_currentdataset, legend);
}
}
return true;
} else if (lcLine.startsWith("bars:")
|| lcLine.startsWith("bargraph:")) {
// The PlotML code uses barGraph, but the older style
// uses bars
int comma = line.indexOf(",", 5);
String barwidth;
String baroffset = null;
if (comma > 0) {
barwidth = line.substring(5, comma).trim();
baroffset = line.substring(comma + 1).trim();
} else {
barwidth = line.substring(5).trim();
}
try {
Double bwidth = Double.valueOf(barwidth);
double boffset = _baroffset;
if (baroffset != null) {
boffset = Double.valueOf(baroffset).doubleValue();
}
setBars(bwidth.doubleValue(), boffset);
} catch (NumberFormatException e) {
// ignore if format is bogus.
}
return true;
} else if (lcLine.startsWith("binwidth:")) {
String binwidth = line.substring(9).trim();
try {
Double bwidth = Double.valueOf(binwidth);
setBinWidth(bwidth.doubleValue());
} catch (NumberFormatException e) {
// ignore if format is bogus.
}
return true;
} else if (lcLine.startsWith("binoffset:")) {
String binoffset = line.substring(10).trim();
try {
Double boffset = Double.valueOf(binoffset);
setBinOffset(boffset.doubleValue());
} catch (NumberFormatException e) {
// ignore if format is bogus.
}
return true;
} else if (lcLine.startsWith("numsets:")) {
// Obsolete field... ignore.
return true;
} else if (line.startsWith("move:")) {
// deal with 'move: 1 2' and 'move:2 2'
line = line.substring(5, line.length()).trim();
} else if (line.startsWith("move")) {
// deal with 'move 1 2' and 'move2 2'
line = line.substring(4, line.length()).trim();
} else if (line.startsWith("draw:")) {
// a connected point, if connect is enabled.
line = line.substring(5, line.length()).trim();
} else if (line.startsWith("draw")) {
// a connected point, if connect is enabled.
line = line.substring(4, line.length()).trim();
}
line = line.trim();
// Handle Plot formats
int fieldsplit = line.indexOf(",");
if (fieldsplit == -1) {
fieldsplit = line.indexOf(" ");
}
if (fieldsplit == -1) {
fieldsplit = line.indexOf("\t"); // a tab
}
if (fieldsplit == -1) {
// Have just one number per line
try {
Double xpt = Double.valueOf(line);
addPoint(_currentdataset, xpt.doubleValue());
return true;
} catch (NumberFormatException e) {
// ignore if format is bogus.
}
} else {
String y = line.substring(fieldsplit + 1).trim();
try {
Double ypt = Double.valueOf(y);
addPoint(_currentdataset, ypt.doubleValue());
return true;
} catch (NumberFormatException e) {
// ignore if format is bogus.
}
}
}
return false;
}
/** Reset a scheduled redraw tasks.
*/
@Override
protected void _resetScheduledTasks() {
Runnable redraw = new RunnableExceptionCatcher(new Runnable() {
@Override
public void run() {
_scheduledBinsToAdd.clear();
}
});
synchronized (this) {
deferIfNecessary(redraw);
}
}
/** Perform a scheduled redraw.
*/
@Override
protected void _scheduledRedraw() {
if (_needPlotRefill || _needBinRedraw) {
Runnable redraw = new RunnableExceptionCatcher(new Runnable() {
@Override
public void run() {
ArrayList