/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.display;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;
import javax.swing.table.AbstractTableModel;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.controls.XMLLoader;
/**
* Dataset stores and plots (x,y) points.
* Dataset is Drawable and can be rendered on a DrawingPanel. Dataset
* extends AbstractTableModel and can be rendered in a JTable.
*
* @author Joshua Gould
* @author Wolfgang Christian
* @created February 13, 2002
* @version 1.0
*/
public class Dataset extends AbstractTableModel implements Measurable, LogMeasurable, Data {
/** Field datasetID: an integer ID that identifies this object */
protected int datasetID = hashCode();
/** Field columnID: an integer used by DataTool */
protected int columnID = 0;
/** Field NO_MARKER */
public final static int NO_MARKER = 0; // no marker
/** Field CIRCLE */
public final static int CIRCLE = 1; // marker type
/** Field SQUARE */
public final static int SQUARE = 2; // marker type
/** Field AREA */
public final static int AREA = 5; // marker type
/** Field PIXEL */
public final static int PIXEL = 6; // marker type
/** Field BAR */
public final static int BAR = 7; // marker type
/** Field POST */
public final static int POST = 8; // marker type
/** Field POST */
public final static int CUSTOM = -1; // marker type
/** maxPointsMultiplier: a multiplier for maxPoints */
public static double maxPointsMultiplier = 1.0;
/** defaultMaxPoints: the default maxPoints */
private static int defaultMaxPoints = 16*1024;
protected double[] xpoints;
// array of x points
protected double[] ypoints;
// array of y points\
protected GeneralPath generalPath;
// path used to draw line plots
protected double xmax;
// the maximum x value in the dataset
protected double ymax;
// the maximum y value in the dataset
protected double xmin;
// the minimum x value in the dataset
protected double ymin;
// the minimum y value in the dataset
protected double xmaxLogscale;
// the maximum x value in the dataset when using a log scale
protected double ymaxLogscale;
// the maximum y value in the dataset when using a log scale
protected double xminLogscale;
// the minimum x value in the dataset when using a log scale
protected double yminLogscale;
// the minimum y value in the dataset when using a log scale
protected int index;
// the current index of the array
protected boolean sorted = false;
// sort the data by increasing x
private int initialSize;
// the initial size of the points array
private int markerSize = 2;
// the size in pixels of the marker
private int markerShape = SQUARE;
// the type of marker
private Color lineColor;
// the color of the line
private Color fillColor;
// the fill color of the marker
private Color edgeColor;
// the edge color of the marker
private Color errorBarColor;
// the error bar color of the marker
private boolean connected;
// whether the points are connected
private String name = null;
// an optional name that can used to identify this dataset
private String xColumnName;
// the name of the x data
private String yColumnName;
// the name of the y data
protected String xColumnDescription;
// a description of the y data
protected String yColumnDescription;
// a description of the y data
private boolean[] colVisible = new boolean[2];
// column visibilities for table view
protected boolean visible = true;
// visible in drawing panel; only visible dataset affect autoscale
private int stride = 1;
// stride for table view
protected int maxPoints = defaultMaxPoints;
// the maximum number of points that will be saved in a dataset
protected ArrayList<ErrorBar> errorBars = new ArrayList<ErrorBar>();
protected Shape customMarker = new Rectangle2D.Double(-markerSize/2, -markerSize/2, markerSize, markerSize);
/**
* Dataset constructor.
*/
public Dataset() {
this(Color.black, Color.black, false);
}
/**
* Dataset constructor specifying the marker color.
*
* @param _markerColor
*/
public Dataset(Color _markerColor) {
this(_markerColor, Color.black, false);
}
/**
* Dataset constructor specifying the marker color, line color, and whether
* points are connected.
*
* @param markerColor
* @param _lineColor
* @param _connected
*/
public Dataset(Color markerColor, Color _lineColor, boolean _connected) {
fillColor = markerColor;
edgeColor = markerColor;
errorBarColor = markerColor;
lineColor = _lineColor;
connected = _connected;
markerSize = 2;
initialSize = 10;
xColumnName = "x"; //$NON-NLS-1$
yColumnName = "y"; //$NON-NLS-1$
generalPath = new GeneralPath();
index = 0;
java.util.Arrays.fill(colVisible, true);
clear();
}
/**
* Sets the ID number of this Data.
*
* @param id the ID number
*/
public void setID(int id) {
datasetID = id;
}
/**
* Returns a unique identifier for this Data.
*
* @return the ID number
*/
public int getID() {
return datasetID;
}
/**
* Sets the column ID.
*
* @param id the column ID
*/
public void setColumnID(int id) {
columnID = id;
}
/**
* Returns the column ID.
*
* @return the column ID
*/
public int getColumnID() {
return columnID;
}
/**
* Sets the sorted flag. Data is sorted by increasing x.
*
* @param _sorted <code>true<\code> to sort
*/
public void setSorted(boolean _sorted) {
sorted = _sorted;
if(sorted) {
insertionSort();
}
}
/**
* Sets the data connected flag. Points are connected by straight lines.
*
* @param _connected <code>true<\code> if points are connected
*
*/
public void setConnected(boolean _connected) {
connected = _connected;
if(connected) {
recalculatePath();
}
}
/**
* Sets the data point fill, edge, and error bar colors to the same color.
*
* @param markerColor
*/
public void setMarkerColor(Color markerColor) {
fillColor = markerColor;
edgeColor = markerColor;
errorBarColor = markerColor;
}
/**
* Sets the data point marker colors.
*
* The error bar color is set equal to the edge color.
*
* @param _fillColor
* @param _edgeColor
*/
public void setMarkerColor(Color _fillColor, Color _edgeColor) {
fillColor = _fillColor;
edgeColor = _edgeColor;
errorBarColor = _edgeColor;
}
/**
* Sets the data point marker colors.
*
* @param _fillColor
* @param _edgeColor
* @param _errorBarColor
*/
public void setMarkerColor(Color _fillColor, Color _edgeColor, Color _errorBarColor) {
fillColor = _fillColor;
edgeColor = _edgeColor;
errorBarColor = _errorBarColor;
}
/**
* Gets the data point fill color.
*
* @return the fill color
*/
public Color getFillColor() {
return fillColor;
}
/**
* Fill colors for Data interface.
* @return color array
*/
public java.awt.Color[] getFillColors() {
return new Color[] {Color.BLACK, fillColor};
}
/**
* Gets the data point edge color.
*
* @return the edge color
*/
public Color getEdgeColor() {
return edgeColor;
}
/**
* Gets the line color.
*
* @return the line color
*/
public Color getLineColor() {
return lineColor;
}
/**
* Line colors for Data interface.
* @return color array
*/
public java.awt.Color[] getLineColors() {
return new Color[] {Color.BLACK, lineColor};
}
/**
* Sets a custom marker shape.
*
* @param marker Shape
*/
public void setCustomMarker(Shape marker) {
customMarker = marker;
if(customMarker==null) {
markerShape = SQUARE;
customMarker = new Rectangle2D.Double(-markerSize/2, -markerSize/2, markerSize, markerSize);
} else {
markerShape = CUSTOM;
}
}
/**
* Sets the data point marker shape. Shapes are: NO_MARKER, CIRCLE, SQUARE,
* AREA, PIXEL, BAR, POST
*
* @param _markerShape
*/
public void setMarkerShape(int _markerShape) {
markerShape = _markerShape;
}
/**
* Gets the data point marker shape.
*
* @return the marker shape
*/
public int getMarkerShape() {
return markerShape;
}
/**
* Sets the half-width of the data point marker.
*
* @param _markerSize in pixels
*/
public void setMarkerSize(int _markerSize) {
markerSize = _markerSize;
}
/**
* Sets the maximum number of allowed datapoints.
*
* @param maxPoints int
*/
public void setMaximumPoints(int maxPoints) {
this.maxPoints = maxPoints;
}
/**
* Gets the half-width of the data point marker.
*
* @return the marker size in pixels
*/
public int getMarkerSize() {
return markerSize;
}
/**
* Sets the color of the lines connecting data points.
*
* @param _lineColor
*/
public void setLineColor(Color _lineColor) {
lineColor = _lineColor;
}
/**
* Sets the column names when rendering this dataset in a JTable.
*
* @param _xColumnName
* @param _yColumnName
*/
public void setXYColumnNames(String _xColumnName, String _yColumnName) {
xColumnName = TeXParser.parseTeX(_xColumnName);
yColumnName = TeXParser.parseTeX(_yColumnName);
}
/**
* Sets the column names and the dataset name.
*
* @param xColumnName
* @param yColumnName
* @param name
*/
public void setXYColumnNames(String xColumnName, String yColumnName, String name) {
setXYColumnNames(xColumnName, yColumnName);
this.name = TeXParser.parseTeX(name);
}
/**
* Gets the x column name.
*
* @return String
*/
public String getXColumnName() {
return xColumnName;
}
/**
* Gets the y column name.
*
* @return String
*/
public String getYColumnName() {
return yColumnName;
}
/**
* Gets the x column description.
*
* @return the description (may be null)
*/
public String getXColumnDescription() {
return xColumnDescription;
}
/**
* Sets the x column description.
*
* @param desc the description (may be null)
*/
public void setXColumnDescription(String desc) {
xColumnDescription = desc;
}
/**
* Gets the y column description.
*
* @return the description (may be null)
*/
public String getYColumnDescription() {
return yColumnDescription;
}
/**
* Sets the y column description.
*
* @param desc the description (may be null)
*/
public void setYColumnDescription(String desc) {
yColumnDescription = desc;
}
/**
* Sets a name that can be used to identify the dataset.
*
* @param name String
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets the dataset name.
*
* @return String
*/
public String getName() {
return name;
}
/**
* Some Data objects (e.g., a Group) do not contain data, but a list of Data objects which do.
* This method is used by Data displaying tools to create as many pages as needed.
* @return a list of Data objects, or null if this object contains data
*/
public java.util.List<Data> getDataList() {
return null;
}
/**
* The column names to be used in the data tool
* @return array of names
*/
public String[] getColumnNames() {
return new String[] {xColumnName, yColumnName};
}
/**
* Gets the valid measure flag. The measure is valid if the min and max values
* have been set.
*
* @return <code>true<\code> if measure is valid
*
*/
public boolean isMeasured() {
if(visible) {
return ymin<Double.MAX_VALUE; // changed by D.Brown
// return index >= 1;
}
return false;
}
/**
* Gets the x world coordinate for the left hand side of the panel.
*
* @return xmin
*/
public double getXMin() {
return xmin;
}
/**
* Gets the x world coordinate for the right hand side of the panel.
*
* @return xmax
*/
public double getXMax() {
return xmax;
}
/**
* Gets y world coordinate for the bottom of the panel.
*
* @return ymin
*/
public double getYMin() {
return ymin;
}
/**
* Gets y world coordinate for the top of the panel.
*
* @return ymax
*/
public double getYMax() {
return ymax;
}
/**
* Gets the minimum x needed to draw this object on a log scale.
* @return minimum
*/
public double getXMinLogscale() {
return xminLogscale;
}
/**
* Gets the maximum x needed to draw this object on a log scale.
* @return maximum
*/
public double getXMaxLogscale() {
return xmaxLogscale;
}
/**
* Gets the minimum y needed to draw this object on a log scale.
* @return minimum
*/
public double getYMinLogscale() {
return yminLogscale;
}
/**
* Gets the maximum y needed to draw this object on a log scale on a log scale.
* @return maximum
*/
public double getYMaxLogscale() {
return ymaxLogscale;
}
/**
* Gets a data array containing both x and y values.
* The data is arranged as an array of (x,y) pairs.
*
* @return a double[index][2] array of data
*/
public double[][] getPoints() {
double[][] temp = new double[index][2];
double[] xValues = getXPoints();
double[] yValues = getYPoints();
for(int i = 0; i<index; i++) {
temp[i] = new double[] {xValues[i], yValues[i]};
}
return temp;
}
/**
* Gets a data array containing both x and y values. Implements Data.
* The data is arranged as a pair of separate x and y arrays.
*
* @return a double[2][index] array of data
*/
public double[][] getData2D() {
double[][] data = new double[2][index];
data[0] = getXPoints();
data[1] = getYPoints();
return data;
}
/**
* Returns a null 3D array of data. Implements Data.
*
* @return null
*/
public double[][][] getData3D() {
return null;
}
/**
* Returns a list containing this Dataset. Implements Data.
*
* @return ArrayList
*/
public ArrayList<Dataset> getDatasets() {
ArrayList<Dataset> list = new ArrayList<Dataset>();
list.add(this);
return list;
}
/**
* Gets a copy of the xpoints array.
*
* @return xpoints[]
*/
public double[] getXPoints() {
double[] temp = new double[index];
System.arraycopy(xpoints, 0, temp, 0, index);
return temp;
}
/**
* Gets a copy of the ypoints array.
*
* @return ypoints[]
*/
public double[] getYPoints() {
double[] temp = new double[index];
System.arraycopy(ypoints, 0, temp, 0, index);
return temp;
}
/**
* Gets an array of valid xpoints.
* A point is valid if the ypoint for that index is not Double.NaN.
*
* @return valid xpoints[]
*/
public double[] getValidXPoints() {
return getValidPoints(getXPoints());
}
/**
* Gets an array of valid ypoints.
* A point is valid if the ypoint for that index is not Double.NaN.
*
* @return valid ypoints[]
*/
public double[] getValidYPoints() {
return getValidPoints(getYPoints());
}
/**
* Gets the sorted flag.
*
* @return <code>true<\code> if the data is sorted
*
*/
public boolean isSorted() {
return sorted;
}
/**
* Gets the data connected flag.
*
* @return <code>true<\code> if points are connected
*
*/
public boolean isConnected() {
return connected;
}
/**
* Gets the number of columns for rendering in a JTable.
*
* @return the count
*/
public int getColumnCount() {
return Dataset.countColumnsVisible(colVisible);
}
/**
* Gets the current index of the array.
*
* The index is equal to the number of data points that are currently stored.
* When data is appended, it will fill the xpoints and ypoints arrays starting
* at the current index.
*
* @return the count
*/
public int getIndex() {
return index;
}
/**
* Gets the number of rows for rendering in a JTable.
*
* @return the count
*/
public int getRowCount() {
return(index+stride-1)/stride;
}
/**
* Gets the name of the colummn for rendering in a JTable
*
* @param columnIndex
* @return the name
*/
public String getColumnName(int columnIndex) {
columnIndex = Dataset.convertTableColumnIndex(colVisible, columnIndex);
if(columnIndex==0) {
return xColumnName;
}
return yColumnName;
}
/**
* Gets an x or y value for rendering in a JTable.
*
* @param rowIndex
* @param columnIndex
* @return the datum
*/
public Object getValueAt(int rowIndex, int columnIndex) {
columnIndex = Dataset.convertTableColumnIndex(colVisible, columnIndex);
rowIndex = rowIndex*stride;
double[] xValues = getXPoints();
double[] yValues = getYPoints();
// conversionFactor added by D Brown Dec 2010
if(columnIndex==0) {
return new Double(xValues[rowIndex]);
}
// changed by D.Brown
if(Double.isNaN(yValues[rowIndex])) {
return null;
}
return new Double(yValues[rowIndex]);
}
/**
* Gets the type of object for JTable entry.
*
* @param columnIndex
* @return the class
*/
public Class<?> getColumnClass(int columnIndex) {
return Double.class;
}
/**
* Appends a data point and its uncertainty to the Dataset.
*
* @param x
* @param y
* @param delx
* @param dely
*/
public void append(double x, double y, double delx, double dely) {
errorBars.add(new ErrorBar(x, y, delx, dely));
append(x, y);
}
/**
* Appends an (x,y) datum to the Dataset. A y value of Double.NaN
* is treated as null in plots and tables.
*
* @param x
* @param y
*/
public void append(double x, double y) {
if(Double.isNaN(x)||Double.isInfinite(x)||Double.isInfinite(y)) {
return;
}
if(index>=xpoints.length) {
increaseCapacity(xpoints.length*2);
}
xpoints[index] = x;
ypoints[index] = y;
// generalPath.append(new Rectangle2D.Double(x, y, 0, 0), true);
if(!Double.isNaN(y)) {
Point2D curPt = generalPath.getCurrentPoint();
if(curPt==null) {
generalPath.moveTo((float) x, (float) y);
} else {
generalPath.lineTo((float) x, (float) y);
}
ymax = Math.max(y, ymax);
ymin = Math.min(y, ymin);
if(y>0) {
ymaxLogscale = Math.max(y, ymaxLogscale);
yminLogscale = Math.min(y, yminLogscale);
}
}
xmax = Math.max(x, xmax);
xmin = Math.min(x, xmin);
if(x>0) {
xmaxLogscale = Math.max(x, xmaxLogscale);
xminLogscale = Math.min(x, xminLogscale);
}
index++;
// move the new datum if x is less than the last value.
if(sorted&&(index>1)&&(x<xpoints[index-2])) {
moveDatum(index-1);
// the new datum is out of place so move it
recalculatePath();
}
}
/**
* Appends arrays of data points and uncertainties to the Dataset.
*
* @param xpoints
* @param ypoints
* @param delx
* @param dely
*/
public void append(double[] xpoints, double[] ypoints, double[] delx, double[] dely) {
for(int i = 0, n = xpoints.length; i<n; i++) {
errorBars.add(new ErrorBar(xpoints[i], ypoints[i], delx[i], dely[i]));
}
append(xpoints, ypoints);
}
/**
* Appends (x,y) arrays to the Dataset. Any y value of Double.NaN
* is treated as null in plots and tables.
*
* @param _xpoints
* @param _ypoints
*/
public void append(double[] _xpoints, double[] _ypoints) {
boolean badData = false;
for(int i = 0; i<_xpoints.length; i++) {
double xp = _xpoints[i];
double yp = _ypoints[i];
if(Double.isNaN(xp)||Double.isInfinite(xp)||Double.isInfinite(yp)) {
badData = true;
continue;
}
xmax = Math.max(xp, xmax);
xmin = Math.min(xp, xmin);
if(xp>0) {
xmaxLogscale = Math.max(xp, xmaxLogscale);
xminLogscale = Math.min(xp, xminLogscale);
}
if(!Double.isNaN(yp)) {
ymax = Math.max(yp, ymax);
ymin = Math.min(yp, ymin);
if(yp>0) {
ymaxLogscale = Math.max(yp, ymaxLogscale);
yminLogscale = Math.min(yp, yminLogscale);
}
Point2D curPt = generalPath.getCurrentPoint();
if(curPt==null) {
generalPath.moveTo((float) xp, (float) yp);
} else {
generalPath.lineTo((float) xp, (float) yp);
}
}
}
int pointsAdded = _xpoints.length;
int availableSpots = xpoints.length-index;
boolean increasedCapacity = false;
if(pointsAdded>availableSpots) {
increaseCapacity(xpoints.length+pointsAdded);
increasedCapacity = true;
}
int maxPts = maxPoints==defaultMaxPoints? (int)(maxPoints*maxPointsMultiplier): maxPoints;
pointsAdded = Math.min(pointsAdded, maxPts); //cannot add more than the maximum capacity
System.arraycopy(_xpoints, Math.max(0, _xpoints.length-pointsAdded), xpoints, index, pointsAdded);
System.arraycopy(_ypoints, Math.max(0, _xpoints.length-pointsAdded), ypoints, index, pointsAdded);
index += pointsAdded;
if(badData) {
removeBadData();
}
if(sorted) {
insertionSort();
}
if(increasedCapacity) {
resetXYMinMax();
}
}
/**
* Reads a file and appends the data contained in the file to this
* Dataset. The format of the file is x and y coordinates separated by tabs.
* Lines beginning with # are ignored.
* @param inputFile
*/
public void read(String inputFile) {
try {
BufferedReader reader = new BufferedReader(new FileReader(inputFile));
String s;
while((s = reader.readLine())!=null) {
s = s.trim();
if((s.length()==0)||(s.charAt(0)=='#')) { // ignore lines beginning with #
continue;
}
StringTokenizer st = new StringTokenizer(s, "\t"); //$NON-NLS-1$
switch(st.countTokens()) {
case 0 :
continue;
case 2 :
append(Double.parseDouble(st.nextToken()), Double.parseDouble(st.nextToken()));
break;
default :
throw new IOException();
}
}
reader.close();
} catch(java.io.FileNotFoundException fnfe) {
System.err.println("File "+inputFile+" not found."); //$NON-NLS-1$ //$NON-NLS-2$
} catch(java.io.IOException ioe) {
System.err.println("Error reading file "+inputFile); //$NON-NLS-1$
} catch(NumberFormatException nfe) {
System.err.println("Error reading file "+inputFile); //$NON-NLS-1$
}
}
/**
* Writes data from this Dataset to a file. The format of the file is x and y coordinates
* separated by tabs.
* @param outputFile
*/
public void write(String outputFile) {
try {
PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(outputFile)));
for(int i = 0; i<index; i++) {
writer.println(xpoints[i]+"\t"+ypoints[i]); //$NON-NLS-1$
}
writer.close();
} catch(java.io.FileNotFoundException fnfe) {
System.err.println("File "+outputFile+" not found."); //$NON-NLS-1$ //$NON-NLS-2$
} catch(java.io.IOException ioe) {
System.err.println("Error writing file "+outputFile); //$NON-NLS-1$
}
}
/**
* Draw this Dataset in the drawing panel.
*
* @param drawingPanel
* @param g
*/
public void draw(DrawingPanel drawingPanel, Graphics g) {
if(!visible) {
return;
}
try {
Graphics2D g2 = (Graphics2D) g;
if(markerShape!=NO_MARKER) {
drawScatterPlot(drawingPanel, g2);
}
if(connected) {
drawLinePlot(drawingPanel, g2);
}
} catch(Exception ex) {} // abort drawing if we have bad data
}
/**
* Clear all data from this Dataset.
*/
public void clear() {
index = 0;
xpoints = new double[initialSize];
ypoints = new double[initialSize];
generalPath.reset();
errorBars.clear();
resetXYMinMax();
}
/**
* Creates a string representation of the data.
*
* @return the data
*/
public String toString() {
if(index==0) {
return "No data in dataset."; //$NON-NLS-1$
}
String s = xpoints[0]+" "+ypoints[0]+"\n"; //$NON-NLS-1$ //$NON-NLS-2$
StringBuffer b = new StringBuffer(index*s.length());
for(int i = 0; i<index; i++) {
b.append(xpoints[i]);
String eol = "\n"; // end of line //$NON-NLS-1$
try { // system properties may not be readable!
eol = System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$
} catch(SecurityException ex) {}
b.append(" "); //$NON-NLS-1$
// changed by D.Brown
if(Double.isNaN(ypoints[i])) {
b.append("null"); //$NON-NLS-1$
} else {
b.append(ypoints[i]);
}
b.append(eol);
// s += xpoints[i] + "\t" + ypoints[i] + "\n";
}
return b.toString();
}
/**
* Counts the number of columns visible
*
* @param visible array of column visibilities
* @return number of visible columns
*/
public static int countColumnsVisible(boolean visible[]) {
int count = 0;
for(int i = 0; i<visible.length; i++) {
if(visible[i]) {
count++;
}
}
return count;
}
/**
* Converts a table column in a table model to the appropriate table column.
* For example, if the x points are hidden in a Dataset, and the column index
* is 0, then this method will return 1.
*
* @param visible array of column visibilities
* @param columnIndex table column index to convert
* @return converted table column index
*/
public static int convertTableColumnIndex(boolean visible[], int columnIndex) {
if((columnIndex==0)&&!visible[0]) {
columnIndex++;
} else if((columnIndex==1)&&!visible[1]) {
columnIndex--;
}
return columnIndex;
}
/**
* Sets the visibility of the x column of this Dataset in a table view.
* @param b new visibility
*/
public void setXColumnVisible(boolean b) {
colVisible[0] = b;
}
/**
* Sets the visibility of the y column of this Dataset in a table view.
* @param b new visibility
*/
public void setYColumnVisible(boolean b) {
colVisible[1] = b;
}
/**
* Sets the visibility of this Dataset in a DrawingPanel.
* Only visible panels affect autoscaled panels.
*
* @param b new visibility
*/
public void setVisible(boolean b) {
visible = b;
}
/**
* Gets the visibility of this dataset in the DrawingPanel.
* @return boolean
*/
public boolean getVisible() {
return visible;
}
/**
* Sets the stride of this Dataset in a table view.
* @param _stride the stride
*/
public void setStride(int _stride) {
stride = _stride;
}
/**
* Gets the visibility of the x column of this Dataset in a table view.
* @return the x column visibility
*/
public boolean isXColumnVisible() {
return colVisible[0];
}
/**
* Gets the visibility of the y column of this Dataset in a table view.
* @return the x column visibility
*/
public boolean isYColumnVisible() {
return colVisible[1];
}
/**
* Perform an insertion sort of the data set. Since data will be partially
* sorted this should be fast. Added by W. Christian.
*/
protected void insertionSort() {
boolean dataChanged = false;
if(index<2) {
return;
// need at least two points to sort.
}
for(int i = 1; i<index; i++) {
if(xpoints[i]<xpoints[i-1]) {
// is the i-th datum smaller?
dataChanged = true;
moveDatum(i);
}
}
if(dataChanged) {
recalculatePath();
}
}
/**
* Recalculate the general path.
*/
protected void recalculatePath() {
generalPath.reset();
if(index<1) {
return;
}
int i = 0;
double[] xValues = getXPoints();
double[] yValues = getYPoints();
for(; i<index; i++) {
if(!Double.isNaN(yValues[i])) {
generalPath.moveTo((float) xValues[i], (float) yValues[i]);
break;
}
}
for(int j = i+1; j<index; j++) {
if(!Double.isNaN(yValues[j])) {
generalPath.lineTo((float) xValues[j], (float) yValues[j]);
}
}
}
/**
* Move an out-of-place datum into its correct position.
*
* @param loc the datum
*/
protected void moveDatum(int loc) {
if(loc<1) {
return;
// zero-th point cannot be out-of-place
}
double x = xpoints[loc];
// save the old values
double y = ypoints[loc];
for(int i = 0; i<index; i++) {
if(xpoints[i]>x) {
// find the insertion point
System.arraycopy(xpoints, i, xpoints, i+1, loc-i);
xpoints[i] = x;
System.arraycopy(ypoints, i, ypoints, i+1, loc-i);
ypoints[i] = y;
return;
}
}
}
/**
* Draw the lines connecting the data points.
*
* @param drawingPanel
* @param g2
*/
protected void drawLinePlot(DrawingPanel drawingPanel, Graphics2D g2) {
// check that at least one ypoints element is a number
boolean noNumbers = true;
for(int i = 0; i<index; i++) {
noNumbers = Double.isNaN(ypoints[i]);
if(!noNumbers) {
break;
}
}
if(noNumbers) {
return;
}
AffineTransform at = drawingPanel.getPixelTransform();
Shape s = generalPath.createTransformedShape(at);
g2.setColor(lineColor);
g2.draw(s);
}
/**
* Fills the line connecting the data points.
*
* @param drawingPanel
* @param g2
*/
protected void drawFilledPlot(DrawingPanel drawingPanel, Graphics2D g2) {
// check that at least one ypoints element is a number
boolean noNumbers = true;
for(int i = 0; i<index; i++) {
noNumbers = Double.isNaN(ypoints[i]);
if(!noNumbers) {
break;
}
}
if(noNumbers) {
return;
}
AffineTransform at = drawingPanel.getPixelTransform();
Shape s = generalPath.createTransformedShape(at);
g2.setColor(fillColor);
g2.fill(s);
g2.setColor(edgeColor);
g2.draw(s);
}
/**
* Draw the markers at the data points.
*
* @param drawingPanel
* @param g2
*/
protected void drawScatterPlot(DrawingPanel drawingPanel, Graphics2D g2) {
if(markerShape==AREA) {
this.drawFilledPlot(drawingPanel, g2);
return;
}
double xp = 0;
double yp = 0;
Shape shape = null;
int size = markerSize*2+1;
Shape clipShape = g2.getClip();
// increase the clip so as to include the entire marker
g2.setClip(drawingPanel.leftGutter-markerSize-1, drawingPanel.topGutter-markerSize-1, drawingPanel.getWidth()-drawingPanel.leftGutter-drawingPanel.rightGutter+2+2*markerSize, drawingPanel.getHeight()-drawingPanel.bottomGutter-drawingPanel.topGutter+2+2*markerSize);
Rectangle viewRect = drawingPanel.getViewRect();
if(viewRect!=null) { // decrease the clip if we are in a scroll pane
g2.clipRect(viewRect.x, viewRect.y, viewRect.x+viewRect.width, viewRect.y+viewRect.height);
}
double[] tempX = getXPoints();
double[] tempY = getYPoints();
for(int i = 0; i<index; i++) {
if(Double.isNaN(tempY[i])) {
continue;
}
if(drawingPanel.isLogScaleX()&&(tempX[i]<=0)) {
continue;
}
if(drawingPanel.isLogScaleY()&&(tempY[i]<=0)) {
continue;
}
xp = drawingPanel.xToPix(tempX[i]);
yp = drawingPanel.yToPix(tempY[i]);
switch(markerShape) {
case BAR : // draw a bar graph.
double bottom = Math.min(drawingPanel.yToPix(0), drawingPanel.yToPix(drawingPanel.getYMin()));
double barHeight = bottom-yp;
if(barHeight>0) {
shape = new Rectangle2D.Double(xp-markerSize, yp, size, barHeight);
} else {
shape = new Rectangle2D.Double(xp-markerSize, bottom, size, -barHeight);
}
g2.setColor(fillColor);
g2.fill(shape);
if(edgeColor!=fillColor) {
g2.setColor(edgeColor);
g2.draw(shape);
}
break;
case POST :
bottom = Math.min(drawingPanel.yToPix(0), drawingPanel.yToPix(drawingPanel.getYMin()));
shape = new Rectangle2D.Double(xp-markerSize, yp-markerSize, size, size);
g2.setColor(edgeColor);
g2.drawLine((int) xp, (int) yp, (int) xp, (int) bottom);
g2.setColor(fillColor);
g2.fill(shape);
if(edgeColor!=fillColor) {
g2.setColor(edgeColor);
g2.draw(shape);
}
break;
case SQUARE :
shape = new Rectangle2D.Double(xp-markerSize, yp-markerSize, size, size);
g2.setColor(fillColor);
g2.fill(shape);
if(edgeColor!=fillColor) {
g2.setColor(edgeColor);
g2.draw(shape);
}
break;
case CIRCLE :
shape = new Ellipse2D.Double(xp-markerSize, yp-markerSize, size, size);
g2.setColor(fillColor);
g2.fill(shape);
if(edgeColor!=fillColor) {
g2.setColor(edgeColor);
g2.draw(shape);
}
break;
case PIXEL :
shape = new Rectangle2D.Double(xp, yp, 1, 1); // this produces a one pixel shape
g2.setColor(edgeColor);
g2.draw(shape);
// draw and center the point
break;
case CUSTOM :
Shape temp = AffineTransform.getTranslateInstance(xp, yp).createTransformedShape(customMarker);
g2.setColor(fillColor);
g2.fill(temp);
if(edgeColor!=fillColor) {
g2.setColor(edgeColor);
g2.draw(temp);
}
break;
default :
shape = new Rectangle2D.Double(xp-markerSize, yp-markerSize, size, size);
g2.setColor(fillColor);
g2.fill(shape);
if(edgeColor!=fillColor) {
g2.setColor(edgeColor);
g2.draw(shape);
}
break;
}
}
Iterator<ErrorBar> it = errorBars.iterator();
while(it.hasNext()) { // copy only the obejcts of the correct type
(it.next()).draw(drawingPanel, g2);
}
g2.setClip(clipShape); // restore the original clipping
}
/**
* Removes infinities and NaN (x only) from the dataset.
*/
private void removeBadData() {
for(int i = 0; i<index; i++) {
if(Double.isNaN(xpoints[i])||Double.isInfinite(xpoints[i])||Double.isInfinite(ypoints[i])) {
if((index==1)||(i==index-1)) {
// we only have one point and it is a bad point!
index--;
break;
// exit the loop
}
System.arraycopy(xpoints, i+1, xpoints, i, index-i-1);
System.arraycopy(ypoints, i+1, ypoints, i, index-i-1);
index--;
i--;
}
}
}
/**
* Increase the array size up to a maximum size.
*
* @param newCapacity
*/
private synchronized void increaseCapacity(int newCapacity) {
int pointsAdded = newCapacity-xpoints.length;
int maxPts = maxPoints==defaultMaxPoints? (int)(maxPoints*maxPointsMultiplier): maxPoints;
newCapacity = Math.min(newCapacity, maxPts); // do not let the number of data points exceed maxPoints
int newIndex = Math.min(index, (3*newCapacity)/4); // drop 1/4 of the old data if the capacity is no longer increasing
newIndex = Math.min(newIndex, newCapacity-pointsAdded); // drop 1/4 of the old data if the capacity is no longer increasing
if(newIndex<0) {
newIndex = 0;
}
double[] tempx = xpoints;
xpoints = new double[newCapacity];
System.arraycopy(tempx, index-newIndex, xpoints, 0, newIndex);
double[] tempy = ypoints;
ypoints = new double[newCapacity];
System.arraycopy(tempy, index-newIndex, ypoints, 0, newIndex);
if(index!=newIndex) { // data was dropped
index = newIndex;
resetXYMinMax();
recalculatePath();
}
index = newIndex;
}
/**
* Reset the minimum and maximum values.
*/
private void resetXYMinMax() {
xmax = xmaxLogscale = -Double.MAX_VALUE;
ymax = ymaxLogscale = -Double.MAX_VALUE;
xmin = xminLogscale = Double.MAX_VALUE;
ymin = yminLogscale = Double.MAX_VALUE;
double[] xValues = getXPoints();
double[] yValues = getYPoints();
for(int i = 0; i<index; i++) {
if(Double.isNaN(xValues[i])||Double.isInfinite(xValues[i])||Double.isInfinite(yValues[i])) {
continue;
}
double xp = xValues[i];
xmax = Math.max(xp, xmax);
xmin = Math.min(xp, xmin);
if(xp>0) {
xmaxLogscale = Math.max(xp, xmaxLogscale);
xminLogscale = Math.min(xp, xminLogscale);
}
double yp = yValues[i];
if(!Double.isNaN(yp)) {
ymax = Math.max(yp, ymax);
ymin = Math.min(yp, ymin);
if(yp>0) {
ymaxLogscale = Math.max(yp, ymaxLogscale);
yminLogscale = Math.min(yp, yminLogscale);
}
}
}
}
/**
* Returns an array of valid points.
* A point is valid if the ypoint for that index is not Double.NaN.
*
* @return valid points
*/
private double[] getValidPoints(double[] pts) {
// eliminate NaN values, if any
int nans = 0;
for(int i = 0; i<pts.length; i++) {
if(nans>0) {
pts[i-nans] = pts[i];
}
if(Double.isNaN(ypoints[i])) {
nans++;
}
}
if(nans==0) {
return pts;
}
double[] temp = new double[index-nans];
System.arraycopy(pts, 0, temp, 0, index-nans);
return temp;
}
/**
* ErrorBar for datapoints.
*/
class ErrorBar implements Drawable {
double x, y, delx, dely; // the position and uncertainty of the data point
int tick = 3;
ErrorBar(double _x, double _y, double _delx, double _dely) {
x = _x;
y = _y;
delx = _delx;
dely = _dely;
}
/**
* Draws the error bars for a data point.
*
* @param panel
* @param g
*/
public void draw(DrawingPanel panel, Graphics g) {
// changed by D.Brown
if(Double.isNaN(y)) {
return;
}
int xpix = panel.xToPix(x);
int xpix1 = panel.xToPix(x-delx);
int xpix2 = panel.xToPix(x+delx);
int ypix = panel.yToPix(y);
int ypix1 = panel.yToPix(y-dely);
int ypix2 = panel.yToPix(y+dely);
g.setColor(errorBarColor);
g.drawLine(xpix1, ypix, xpix2, ypix);
g.drawLine(xpix, ypix1, xpix, ypix2);
g.drawLine(xpix1, ypix-tick, xpix1, ypix+tick);
g.drawLine(xpix2, ypix-tick, xpix2, ypix+tick);
g.drawLine(xpix-tick, ypix1, xpix+tick, ypix1);
g.drawLine(xpix-tick, ypix2, xpix+tick, ypix2);
}
}
/**
* Returns the XML.ObjectLoader for this class.
*
* @return the object loader
*/
public static XML.ObjectLoader getLoader() {
return new Loader();
}
/**
* A class to save and load Dataset data in an XMLControl.
*/
protected static class Loader extends XMLLoader {
public void saveObject(XMLControl control, Object obj) {
Dataset data = (Dataset) obj;
control.setValue("points", data.getPoints()); //$NON-NLS-1$
control.setValue("index", data.index); //$NON-NLS-1$
// control.setValue("x_points", data.getXPoints());
// control.setValue("y_points", data.getYPoints());
control.setValue("marker_shape", data.getMarkerShape()); //$NON-NLS-1$
control.setValue("marker_size", data.getMarkerSize()); //$NON-NLS-1$
control.setValue("sorted", data.isSorted()); //$NON-NLS-1$
control.setValue("connected", data.isConnected()); //$NON-NLS-1$
control.setValue("name", data.name); //$NON-NLS-1$
control.setValue("x_name", data.xColumnName); //$NON-NLS-1$
control.setValue("y_name", data.yColumnName); //$NON-NLS-1$
control.setValue("x_description", data.xColumnDescription); //$NON-NLS-1$
control.setValue("y_description", data.yColumnDescription); //$NON-NLS-1$
control.setValue("line_color", data.lineColor); //$NON-NLS-1$
control.setValue("fill_color", data.fillColor); //$NON-NLS-1$
control.setValue("edge_color", data.edgeColor); //$NON-NLS-1$
control.setValue("errorbar_color", data.errorBarColor); //$NON-NLS-1$
control.setValue("datasetID", data.datasetID); //$NON-NLS-1$
control.setValue("visible", data.colVisible); //$NON-NLS-1$
}
public Object createObject(XMLControl control) {
Class<?> type = control.getObjectClass();
// handle subclasses separately
if(Dataset.class.isAssignableFrom(type)&&!Dataset.class.equals(type)) {
try {
return type.newInstance();
} catch(InstantiationException ex) {}
catch(IllegalAccessException ex) {}
}
return new Dataset();
}
public Object loadObject(XMLControl control, Object obj) {
Dataset data = (Dataset) obj;
double[][] points = (double[][]) control.getObject("points"); //$NON-NLS-1$
if((points!=null)&&(points.length>0)&&(points[0]!=null)) {
data.clear();
for(int i = 0; i<points.length; i++) {
data.append(points[i][0], points[i][1]);
}
}
// for backward compatibility
double[] xPoints = (double[]) control.getObject("x_points"); //$NON-NLS-1$
double[] yPoints = (double[]) control.getObject("y_points"); //$NON-NLS-1$
if((xPoints!=null)&&(yPoints!=null)) {
data.clear();
data.append(xPoints, yPoints);
}
data.index = control.getInt("index"); //$NON-NLS-1$
if(control.getPropertyNames().contains("marker_shape")) { //$NON-NLS-1$
data.setMarkerShape(control.getInt("marker_shape")); //$NON-NLS-1$
}
if(control.getPropertyNames().contains("marker_size")) { //$NON-NLS-1$
data.setMarkerSize(control.getInt("marker_size")); //$NON-NLS-1$
}
data.setSorted(control.getBoolean("sorted")); //$NON-NLS-1$
data.setConnected(control.getBoolean("connected")); //$NON-NLS-1$
data.name = control.getString("name"); //$NON-NLS-1$
data.xColumnName = control.getString("x_name"); //$NON-NLS-1$
data.yColumnName = control.getString("y_name"); //$NON-NLS-1$
data.xColumnDescription = control.getString("x_description"); //$NON-NLS-1$
data.yColumnDescription = control.getString("y_description"); //$NON-NLS-1$
Color color = (Color) control.getObject("line_color"); //$NON-NLS-1$
if(color!=null) {
data.lineColor = color;
}
color = (Color) control.getObject("fill_color"); //$NON-NLS-1$
if(color!=null) {
data.fillColor = color;
}
color = (Color) control.getObject("edge_color"); //$NON-NLS-1$
if(color!=null) {
data.edgeColor = color;
}
color = (Color) control.getObject("errorbar_color"); //$NON-NLS-1$
if(color!=null) {
data.errorBarColor = color;
}
data.setID(control.getInt("datasetID")); //$NON-NLS-1$
boolean[] colVisible = (boolean[]) control.getObject("visible"); //$NON-NLS-1$
if(colVisible!=null) {
data.colVisible = colVisible;
}
return obj;
}
}
}
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/