/* * 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; /** * GridPointData stores numeric data on a scaled rectangular grid using an array of points. * * Every grid point contains the x and y coordinates and one or more components. * The first component is usually the magnitude of the quantity of interest. * * Components can represent almost anything. For example, we often use color-coded * arrows to display vector fields. The arrows's color is the first sample and its vertical and * horizonal components are the second and third components. This data is stored * in an internal array as follows: * <br> * <pre> * <code>data=new double [n][m][5]<\code> * <code>vertex=data[n][m]<\code> * * <code>vertex[0] = x <\code> * <code>vertex[1] = y <\code> * <code>vertex[2] = val_1 <\code> * <code>vertex[3] = val_2 <\code> * <code>vertex[4] = val_3 <\code> * <\pre> * * @author Wolfgang Christian * @created Feb 3, 2004 * @version 1.1 */ public class GridPointData 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 Data2D * * @param ix * @param iy * @param ncomponents */ public GridPointData(int ix, int iy, int ncomponents) { 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((ncomponents<1)) { throw new IllegalArgumentException("Number of 2d data components must be positive. Your ncomponents="+ncomponents); //$NON-NLS-1$ } data = new double[ix][iy][ncomponents+2]; // x, y, and components setScale(0, ix, 0, iy); names = new String[ncomponents]; for(int i = 0; i<ncomponents; i++) { names[i] = "Component_"+i; //$NON-NLS-1$ } } /** * Creates a new GridPointData object with the same grid points and the given number of components. * * @param ncomponents number of samples dataset. * @return the newly created Data2D */ public GridPointData createGridPointData(int ncomponents) { GridPointData data2d = new GridPointData(data.length, data[0].length, ncomponents+2); data2d.setScale(left, right, bottom, top); return data2d; } /** * 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[0][0].length-2; } /** * 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.length; int iy = data[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 usually negative because pixel cooridinates decrease as the world units increase. } double x = left; for(int i = 0; i<ix; i++) { double y = top; for(int j = 0; j<iy; j++) { data[i][j][0] = x; // x location data[i][j][1] = y; // y location y += dy; // iy loop } x += dx; // ix loop } } /** * 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.length; int ny = data[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 } double x = _left+dx/2; for(int i = 0; i<nx; i++) { double y = _top+dy/2; for(int j = 0; j<ny; j++) { data[i][j][0] = x; // x location data[i][j][1] = y; // y location y += dy; // iy loop } x += dx; // ix loop } 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.length; int ny = data[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); } /** * 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[ix][iy][component+2]; } /** * 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[ix][iy][component+2] = value; } /** * Gets the number of x entries. * @return nx */ public int getNx() { return data.length; } /** * Gets the number of y entries. * @return ny */ public int getNy() { return data[0].length; } /** * Gets the minimum and maximum values of the n-th component. * * @param n the component * @return {zmin,zmax} */ public double[] getZRange(int n) { int index = 2+n; double zmin = data[0][0][index]; double zmax = zmin; for(int i = 0, mx = data.length; i<mx; i++) { for(int j = 0, my = data[0].length; j<my; j++) { double v = data[i][j][index]; if(v>zmax) { zmax = v; } if(v<zmin) { zmin = v; } } } return new double[] {zmin, zmax}; } /** * Gets the vertex closest to the specified location * @param x * @param y * * @return vertex array */ public double[] getVertex(double x, double y) { int nx = (int) Math.floor((x-left)/dx); nx = Math.max(0, nx); // cannot be less than 0 nx = Math.min(nx, data.length-1); // cannot be greater than last element int ny = (int) Math.floor(-(top-y)/dy); ny = Math.max(0, ny); // cannot be less than 0 ny = Math.min(ny, data[0].length-1); // cannot be greater than last element return data[nx][ny]; } /** * 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 the component index * @return the interpolated sample */ public double interpolate(double x, double y, int index) { int ix = (int) ((x-data[0][0][0])/dx); ix = Math.max(0, ix); ix = Math.min(data.length-2, ix); int iy = (int) ((y-data[0][0][1])/dy); iy = Math.max(0, iy); iy = Math.min(data[0].length-2, iy); double t = (x-data[ix][iy][0])/dx; double u = (y-data[ix][iy][1])/dy; index += 2; return(1-t)*(1-u)*data[ix][iy][index]+t*(1-u)*data[ix+1][iy][index]+t*u*data[ix+1][iy+1][index]+(1-t)*u*data[ix][iy+1][index]; } /** * 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-data[0][0][0])/dx); ix = Math.max(0, ix); ix = Math.min(data.length-2, ix); int iy = (int) ((y-data[0][0][1])/dy); iy = Math.max(0, iy); iy = Math.min(data[0].length-2, iy); double t = (x-data[ix][iy][0])/dx; double u = (y-data[ix][iy][1])/dy; for(int i = 0, n = indexes.length; i<n; i++) { int index = indexes[i]+2; values[i] = (1-t)*(1-u)*data[ix][iy][index]+t*(1-u)*data[ix+1][iy][index]+t*u*data[ix+1][iy+1][index]+(1-t)*u*data[ix][iy+1][index]; } return values; } /** * Gets the array containing the data. * * @return the data */ public double[][][] getData() { return data; } /** * Sets the array containing the data. * * Use with caution. This method is included for backward compatibility. Users are responsible for setting the x and y coordinate values. * Users are also responsible for synchronization with clients. */ public void setData(double[][][] newdata) { data = newdata; int nx = data.length-1; int ny = data[0].length-1; left = data[0][0][0]; right = data[nx][ny][0]; top = data[0][0][1]; bottom = data[nx][ny][1]; dx = (right-left)/nx; dy = (bottom-top)/ny; cellData = false; } /** * 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)||(i>=data.length)) ? Double.NaN : data[i][0][0]; // use stored x value from first row } /** * Gets the y coordinate for the given index. * * @param i int * @return double the y coordinate */ public double indexToY(int i) { return((data==null)||(i>=data[0].length)) ? Double.NaN : data[0][i][1]; // use stored y value from first column } /** * 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) { GridPointData gpd = (GridPointData) 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 GridPointData(1, 1, 1); } public Object loadObject(XMLControl control, Object obj) { GridPointData gpd = (GridPointData) 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 */