/*
* 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.display2d;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.controls.XMLLoader;
/**
* ArrayData stores numeric data for 2d visualizations using a 2d array for each component.
*
* Data components can represent almost anything. For example, we can store vectors as follows:
* <br>
* <pre>
* <code>data=new double [3][n][m]<\code>
*
* <code>data[0] = length[][] <\code>
* <code>data[1] = a[][] <\code>
* <code>data[2] = b[][] <\code>
* <\pre>
*
* @author Wolfgang Christian
* @created Jan 2, 2004
* @version 1.0
*/
public class ArrayData implements GridData {
protected double[][][] data;
protected double left, right, bottom, top;
protected double dx = 0, dy = 0;
protected boolean cellData = false;
protected String[] names;
/**
* Constructor ArrayData
*
* @param ix
* @param iy
* @param nsamples
*/
public ArrayData(int ix, int iy, int nsamples) {
if((iy<1)||(ix<1)) {
throw new IllegalArgumentException("Number of dataset rows and columns must be positive. Your row="+iy+" col="+ix); //$NON-NLS-1$ //$NON-NLS-2$
}
if((nsamples<1)) {
throw new IllegalArgumentException("Number of 2d data components must be positive. Your ncomponents="+nsamples); //$NON-NLS-1$
}
data = new double[nsamples][ix][iy]; // x, y, and components
setScale(0, ix, 0, iy);
names = new String[nsamples];
for(int i = 0; i<nsamples; i++) {
names[i] = "Component_"+i; //$NON-NLS-1$
}
}
/**
* Sets the name of the component.
*
* @param i int the component index
* @param name String
*/
public void setComponentName(int i, String name) {
names[i] = name;
}
/**
* Gets the name of the component,
* @param i int the component index
* @return String the name
*/
public String getComponentName(int i) {
return names[i];
}
/**
* Gets the number of data components.
*
* @return int
*/
public int getComponentCount() {
return data.length;
}
/**
*
* Sets the left, right, bottom, and top of the grid data using a lattice model.
*
* @param _left
* @param _right
* @param _bottom
* @param _top
*/
public void setScale(double _left, double _right, double _bottom, double _top) {
cellData = false;
left = _left;
right = _right;
bottom = _bottom;
top = _top;
int ix = data[0].length;
int iy = data[0][0].length;
dx = 0; // special case if #col==1
if(ix>1) {
dx = (right-left)/(ix-1);
}
dy = 0; // special ase if #row==1
if(iy>1) {
dy = (bottom-top)/(iy-1); // note that dy is usualy negative
}
if(dx==0) {
left -= 0.5;
right += 0.5;
}
if(dy==0) {
bottom -= 0.5;
top += 0.5;
}
}
/**
* Gets the cellData flag.
*
* @return true if cell data.
*/
public boolean isCellData() {
return cellData;
}
/**
* Gets the value of the given component at the given location.
*
* @param ix x index
* @param iy y index
* @param component
* @return the value.
*/
public double getValue(int ix, int iy, int component) {
return data[component][ix][iy];
}
/**
* Sets the value of the given component at the given location.
*
* @param ix x index
* @param iy y index
* @param component
* @param value
*/
public void setValue(int ix, int iy, int component, double value) {
data[component][ix][iy] = value;
}
/**
* Gets the number of x entries.
* @return nx
*/
public int getNx() {
return data[0].length;
}
/**
* Gets the number of y entries.
* @return ny
*/
public int getNy() {
return data[0][0].length;
}
/**
* Sets the left, right, bottom, and top of the grid data using a cell model.
*
* Coordinates are centered on each cell and will NOT include the edges.
*
* @param _left
* @param _right
* @param _bottom
* @param _top
*/
public void setCellScale(double _left, double _right, double _bottom, double _top) {
cellData = true;
int nx = data[0].length;
int ny = data[0][0].length;
dx = 0; // special case if #col==1
if(nx>1) {
dx = (_right-_left)/nx;
}
dy = 0; // special ase if #row==1
if(ny>1) {
dy = (_bottom-_top)/ny; // note that dy is usualy negative
}
left = _left+dx/2;
right = _right-dx/2;
bottom = _bottom-dy/2;
top = _top+dy/2;
}
/**
* Sets the grid such that the centers of the corner cells match the given coordinates.
*
* Coordinates are centered on each cell and the bounds are ouside the max and min values.
*
* @param xmin
* @param xmax
* @param ymin
* @param ymax
*/
public void setCenteredCellScale(double xmin, double xmax, double ymin, double ymax) {
int nx = data[0].length;
int ny = data[0][0].length;
double delta = (nx>1) ? (xmax-xmin)/(nx-1)/2 : 0;
xmin -= delta;
xmax += delta;
delta = (ny>1) ? (ymax-ymin)/(ny-1)/2 : 0;
ymin -= delta;
ymax += delta;
setCellScale(xmin, xmax, ymin, ymax);
}
/**
* Estimates the value of a component at an untabulated point, (x,y).
*
* Interpolate uses bilinear interpolation on the grid. Although the interpolating
* function is continous across the grid boundaries, the gradient changes discontinuously
* at the grid-square boundaries.
*
* @param x the untabulated x
* @param y the untabulated y
* @param index
* @return the interpolated sample
*/
public double interpolate(double x, double y, int index) {
int ix = (int) ((x-left)/dx);
ix = Math.max(0, ix);
ix = Math.min(data[0].length-2, ix);
int iy = -(int) ((top-y)/dy);
iy = Math.max(0, iy);
iy = Math.min(data[0][0].length-2, iy);
double t = (x-left)/dx-ix;
double u = -(top-y)/dy-iy;
if(ix<0) {
return(1-u)*data[index][0][iy]+u*data[index][0][iy+1];
} else if(iy<0) {
return(1-t)*data[index][ix][0]+t*data[index][ix+1][0];
} else {
return(1-t)*(1-u)*data[index][ix][iy]+t*(1-u)*data[index][ix+1][iy]+t*u*data[index][ix+1][iy+1]+(1-t)*u*data[index][ix][iy+1];
}
}
/**
* Estimates multiple sample components at an untabulated point, (x,y).
*
* Interpolate uses bilinear interpolation on the grid. Although the interpolating
* function is continous across the grid boundaries, the gradient changes discontinuously
* at the grid square boundaries.
*
* @param x untabulated x
* @param y untabulated y
* @param indexes to be interpolated
* @param values array will contain the interpolated values
* @return the interpolated array
*/
public double[] interpolate(double x, double y, int[] indexes, double[] values) {
int ix = (int) ((x-left)/dx);
ix = Math.max(0, ix);
ix = Math.min(data[0].length-2, ix);
int iy = -(int) ((top-y)/dy);
iy = Math.max(0, iy);
iy = Math.min(data[0][0].length-2, iy);
// special case if there is only one row or one column
if((ix<0)&&(iy<0)) {
for(int i = 0, n = indexes.length; i<n; i++) {
values[i] = data[indexes[i]][0][0];
}
return values;
} else if(ix<0) {
double u = -(top-y)/dy-iy;
for(int i = 0, n = indexes.length; i<n; i++) {
values[i] = (1-u)*data[indexes[i]][0][iy]+u*data[indexes[i]][0][iy+1];
}
return values;
} else if(iy<0) {
double t = (x-left)/dx-ix;
for(int i = 0, n = indexes.length; i<n; i++) {
values[i] = (1-t)*data[indexes[i]][ix][0]+t*data[indexes[i]][ix+1][0];
}
return values;
}
double t = (x-left)/dx-ix;
double u = -(top-y)/dy-iy;
for(int i = 0, n = indexes.length; i<n; i++) {
int index = indexes[i];
values[i] = (1-t)*(1-u)*data[index][ix][iy]+t*(1-u)*data[index][ix+1][iy]+t*u*data[index][ix+1][iy+1]+(1-t)*u*data[index][ix][iy+1];
}
return values;
}
/**
* Gets the array containing the data.
*
* @return the data
*/
public double[][][] getData() {
return data;
}
/**
* Gets the minimum and maximum values of the n-th component.
*
* @param n the component
* @return {zmin,zmax}
*/
public double[] getZRange(int n) {
double zmin = data[n][0][0];
double zmax = zmin;
for(int i = 0, mx = data[0].length; i<mx; i++) {
for(int j = 0, my = data[0][0].length; j<my; j++) {
double v = data[n][i][j];
if(v>zmax) {
zmax = v;
}
if(v<zmin) {
zmin = v;
}
}
}
return new double[] {zmin, zmax};
}
/**
* Gets the x value for the first column in the grid.
* @return the leftmost x value
*/
public final double getLeft() {
return left;
}
/**
* Gets the x value for the right column in the grid.
* @return the rightmost x value
*/
public final double getRight() {
return right;
}
/**
* Gets the y value for the first row of the grid.
* @return the topmost y value
*/
public final double getTop() {
return top;
}
/**
* Gets the y value for the last row of the grid.
* @return the bottommost y value
*/
public final double getBottom() {
return bottom;
}
/**
* Gets the change in x between grid columns.
* @return the bottommost y value
*/
public final double getDx() {
return dx;
}
/**
* Gets the change in y between grid rows.
* @return the bottommost y value
*/
public final double getDy() {
return dy;
}
/**
* Gets the x coordinate for the given index.
*
* @param i int
* @return double the x coordinate
*/
public double indexToX(int i) {
return(data==null) ? Double.NaN : left+dx*i;
}
/**
* Gets the y coordinate for the given index.
*
* @param i int
* @return double the y coordinate
*/
public double indexToY(int i) {
return(data==null) ? Double.NaN : top+dy*i;
}
/**
* Gets closest index from the given x world coordinate.
*
* @param x double the coordinate
* @return int the index
*/
public int xToIndex(double x) {
if(data==null) {
return 0;
}
int nx = getNx();
double dx = (right-left)/nx;
int i = (int) ((x-left)/dx);
if(i<0) {
return 0;
}
if(i>=nx) {
return nx-1;
}
return i;
}
/**
* Gets closest index from the given y world coordinate.
*
* @param y double the coordinate
* @return int the index
*/
public int yToIndex(double y) {
if(data==null) {
return 0;
}
int ny = getNy();
double dy = (top-bottom)/ny;
int i = (int) ((top-y)/dy);
if(i<0) {
return 0;
}
if(i>=ny) {
return ny-1;
}
return i;
}
/**
* 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.
*/
private static class Loader extends XMLLoader {
public void saveObject(XMLControl control, Object obj) {
ArrayData gpd = (ArrayData) obj;
control.setValue("left", gpd.left); //$NON-NLS-1$
control.setValue("right", gpd.right); //$NON-NLS-1$
control.setValue("bottom", gpd.bottom); //$NON-NLS-1$
control.setValue("top", gpd.top); //$NON-NLS-1$
control.setValue("dx", gpd.dx); //$NON-NLS-1$
control.setValue("dy", gpd.dy); //$NON-NLS-1$
control.setValue("is cell data", gpd.cellData); //$NON-NLS-1$
control.setValue("data", gpd.data); //$NON-NLS-1$
}
public Object createObject(XMLControl control) {
return new ArrayData(1, 1, 1);
}
public Object loadObject(XMLControl control, Object obj) {
ArrayData gpd = (ArrayData) obj;
double[][][] data = (double[][][]) control.getObject("data"); //$NON-NLS-1$
gpd.data = data;
gpd.left = control.getDouble("left"); //$NON-NLS-1$
gpd.right = control.getDouble("right"); //$NON-NLS-1$
gpd.bottom = control.getDouble("bottom"); //$NON-NLS-1$
gpd.top = control.getDouble("top"); //$NON-NLS-1$
gpd.dx = control.getDouble("dx"); //$NON-NLS-1$
gpd.dy = control.getDouble("dy"); //$NON-NLS-1$
gpd.cellData = control.getBoolean("is cell data"); //$NON-NLS-1$
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
*/