/*
* 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 java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import javax.swing.JFrame;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.display.DrawingPanel;
import org.opensourcephysics.display.Grid;
/**
* VectorPlot renders a vector field in a drawing panel using arrows centered on
* each grid point in the GridPointData.
*
* The default representation of the vector field uses fixed length arrows to
* show direction and color to show magnitude.
*
* @author Wolfgang Christian
* @version 1.0
*/
public class VectorPlot implements Plot2D {
public static final int STROKEDARROW = 0;
public static final int FILLEDARROW = 1;
private GeneralPath vectorpath;
private int arrowType = STROKEDARROW; // draw the arrow with a solid arrowhead
private boolean visible = true;
private GridData griddata;
private boolean autoscaleZ = true;
private boolean scaleArrowToGrid = true;
private VectorColorMapper colorMap;
private int ampIndex = 0; // amplitude index
private int aIndex = 1; // x componnet index
private int bIndex = 2; // y component index
private double xmin, xmax, ymin, ymax;
Grid grid;
/**
* Constructs a VectorPlot without data.
*/
public VectorPlot() {
this(null);
}
/**
* Constructs a VectorPlot that renders the given grid data.
*
* @param _griddata the data
*/
public VectorPlot(GridData _griddata) {
griddata = _griddata;
colorMap = new VectorColorMapper(256, 1.0);
if(griddata==null) {
return;
}
grid = (griddata.isCellData()) ? new Grid(griddata.getNx(), griddata.getNy()) : new Grid(griddata.getNx()-1, griddata.getNy()-1);
grid.setColor(Color.lightGray);
grid.setVisible(false);
update();
}
/**
* Gets closest index from the given x world coordinate.
*
* @param x double the coordinate
* @return int the index
*/
public int xToIndex(double x) {
return griddata.xToIndex(x);
}
/**
* Gets closest index from the given y world coordinate.
*
* @param y double the coordinate
* @return int the index
*/
public int yToIndex(double y) {
return griddata.yToIndex(y);
}
/**
* Gets the x coordinate for the given index.
*
* @param i int
* @return double the x coordinate
*/
public double indexToX(int i) {
return griddata.indexToX(i);
}
/**
* Gets the y coordinate for the given index.
*
* @param i int
* @return double the y coordinate
*/
public double indexToY(int i) {
return griddata.indexToY(i);
}
/**
* Sets the data to new values.
*
* The grid is resized to fit the new data if needed.
*
* @param obj
*/
public void setAll(Object obj) {
double[][][] val = (double[][][]) obj;
copyVecData(val);
update();
}
/**
* Sets the values and the scale.
*
* The grid is resized to fit the new data if needed.
*
* @param obj array of new values
* @param xmin double
* @param xmax double
* @param ymin double
* @param ymax double
*/
public void setAll(Object obj, double xmin, double xmax, double ymin, double ymax) {
double[][][] val = (double[][][]) obj;
copyVecData(val);
if(griddata.isCellData()) {
griddata.setCellScale(xmin, xmax, ymin, ymax);
} else {
griddata.setScale(xmin, xmax, ymin, ymax);
}
update();
}
private void copyVecData(double vals[][][]) {
if((griddata!=null)&&!(griddata instanceof ArrayData)) {
throw new IllegalStateException("SetAll only supports ArrayData for data storage."); //$NON-NLS-1$
}
if((griddata==null)||(griddata.getNx()!=vals[0].length)||(griddata.getNy()!=vals[0][0].length)) {
griddata = new ArrayData(vals[0].length, vals[0][0].length, 3);
setGridData(griddata);
}
double[][] colorValue = griddata.getData()[0];
double[][] xComp = griddata.getData()[1];
double[][] yComp = griddata.getData()[2];
int ny = vals[0][0].length;
for(int i = 0, nx = vals[0].length; i<nx; i++) {
for(int j = 0; j<ny; j++) {
// map vector magniture to color
colorValue[i][j] = Math.sqrt(vals[0][i][j]*vals[0][i][j]+vals[1][i][j]*vals[1][i][j]);
// normalize vector lengths
xComp[i][j] = (colorValue[i][j]==0) ? 0 : vals[0][i][j]/colorValue[i][j];
yComp[i][j] = (colorValue[i][j]==0) ? 0 : vals[1][i][j]/colorValue[i][j];
}
}
}
/**
* Gets the GridData object.
* @return GridData
*/
public GridData getGridData() {
return griddata;
}
/**
* Sets the data storage to the given value.
*
* @param _griddata the new data storage
*/
public void setGridData(GridData _griddata) {
griddata = _griddata;
if(griddata==null) {
return;
}
Grid newgrid = (griddata.isCellData()) ? new Grid(griddata.getNx(), griddata.getNy()) : new Grid(griddata.getNx()-1, griddata.getNy()-1);
newgrid.setColor(Color.lightGray);
newgrid.setVisible(false);
if(grid!=null) {
newgrid.setColor(grid.getColor());
newgrid.setVisible(grid.isVisible());
}
grid = newgrid;
}
/**
* Sets the indexes for the data components that will be plotted.
*
* Indexes determine the postion of the amplitude, x-component, and y-component
* in the data array.
*
* @param indexes the sample-component indexes
*/
public void setIndexes(int[] indexes) {
ampIndex = indexes[0];
aIndex = indexes[1];
bIndex = indexes[2];
}
/**
* Sets this vector field to draw vectors with filled shafts and arrowheads.
*
* @param type
*/
public void setArrowType(int type) {
arrowType = type;
}
/**
* Sets the type of palette.
*
* Palette types are defined in the ColorMapper class and include: SPECTRUM, BLACK, RED, and BLUE.
* The default type is SPECTRUM.
*
* @param mode
*/
public void setPaletteType(int mode) {
colorMap.setPaletteType(mode);
}
/**
* Sets the colors that will be used between the floor and ceiling values.
* Not implemented in this class.
* @param colors
*/
public void setColorPalette(Color[] colors) {
// not implemented
}
/**
* Sets this vector field to be visible.
* Drawing will be disabled if visible is false.
*
* @param vis
*/
public void setVisible(boolean vis) {
visible = vis;
}
/**
* Outlines the data grid's boundaries.
*
* @param showGrid
*/
public void setShowGridLines(boolean showGrid) {
if(grid==null) {
grid = new Grid(0);
}
grid.setVisible(showGrid);
}
/**
* Sets the color for grid line boundaries
*
* @param c
*/
public void setGridLineColor(Color c) {
grid.setColor(c);
}
/**
* Draws this vector field in the given drawing panel.
* @param panel
* @param g
*/
public void draw(DrawingPanel panel, Graphics g) {
if(!visible||(griddata==null)) {
return;
}
if(grid.isVisible()) {
// grid.setMinMax(xmin, xmax, ymin, ymax);
grid.draw(panel, g);
}
colorMap.checkPallet(panel.getBackground());
GridData griddata = this.griddata;
double[][][] data = griddata.getData();
double dx = griddata.getDx();
double dy = griddata.getDy();
double left = griddata.getLeft();
double top = griddata.getTop();
double aspectRatio = panel.getAspectRatio();
float arrowLength = (float) Math.abs(panel.getYPixPerUnit()); // arrows will use panel scale
if(scaleArrowToGrid) { // arrows will adjust size to fit grid
arrowLength = Math.max(1, panel.getSize().width/(float) data.length/(float) aspectRatio-1);
arrowLength = Math.min(18, arrowLength*0.72f);
}
switch(arrowType) {
case STROKEDARROW :
vectorpath = createVectorPath(arrowLength);
break;
case FILLEDARROW :
vectorpath = createFilledVectorPath(arrowLength);
break;
default :
vectorpath = createVectorPath(arrowLength);
}
int sgnx = (panel.getXPixPerUnit()<0) ? sgnx = -1 : 1;
int sgny = (panel.getYPixPerUnit()<0) ? sgny = -1 : 1;
double amp = 0, a = 0, b = 0, x = 0, y = 0;
Color background = panel.getBackground();
for(int i = 0, nx = griddata.getNx(); i<nx; i++) {
for(int j = 0, ny = griddata.getNy(); j<ny; j++) {
if(griddata instanceof GridPointData) {
x = data[i][j][0];
y = data[i][j][1];
amp = data[i][j][ampIndex+2];
a = data[i][j][aIndex+2];
b = data[i][j][bIndex+2];
} else if(griddata instanceof ArrayData) {
x = left+i*dx;
y = top+j*dy;
amp = data[ampIndex][i][j];
a = data[aIndex][i][j];
b = data[bIndex][i][j];
}
// start in-line code for speed
Graphics2D g2 = (Graphics2D) g;
Color c = colorMap.doubleToColor(amp);
if(background==c) {
continue;
}
g2.setColor(colorMap.doubleToColor(amp));
AffineTransform at = new AffineTransform(sgnx*aspectRatio*a, // cos
-sgny*b, // -sin
sgnx*aspectRatio*b, // sin
sgny*a, // cos
panel.xToPix(x), // translation x
panel.yToPix(y) // translation y
);
Shape s = vectorpath.createTransformedShape(at);
switch(arrowType) {
case STROKEDARROW :
g2.draw(s);
break;
case FILLEDARROW :
g2.fill(s);
break;
default :
g2.draw(s);
}
// end in-line code
}
}
}
/**
* Sets the autoscale flag for the arrow length.
* @param scaleToGrid
*/
public void scaleArrowLenghToGrid(boolean scaleToGrid) {
scaleArrowToGrid = scaleToGrid;
}
/**
* Sets the autoscale flag and the floor and ceiling values.
*
* If autoscaling is true, then the min and max values of z are set using the data.
* If autoscaling is false, then floor and ceiling values become the max and min.
* Values below min map to the first color; values above max map to the last color.
*
* @param isAutoscale
* @param floor
* @param ceil
*/
public void setAutoscaleZ(boolean isAutoscale, double floor, double ceil) {
autoscaleZ = isAutoscale;
if(autoscaleZ) {
update();
} else {
colorMap.setScale(ceil);
}
}
/**
* Forces the z-scale to be symmetric about zero.
* Not applicable in vector map because vector amplitude is always positive
*
* @param symmetric
*/
public void setSymmetricZ(boolean symmetric){
}
/**
* Gets the symmetric z flag.
*/
public boolean isSymmetricZ(){
return false;
}
/**
* Gets the autoscale flag for z.
*
* @return boolean
*/
public boolean isAutoscaleZ() {
return autoscaleZ;
}
/**
* Gets the floor for scaling the z data.
* @return double
*/
public double getFloor() {
return 0;
}
/**
* Gets the ceiling for scaling the z data.
* @return double
*/
public double getCeiling() {
return colorMap.getCeiling();
}
/**
* Sets the floor and ceiling colors.
* Not implemented in this class.
* @param floorColor
* @param ceilColor
*/
public void setFloorCeilColor(Color floorColor, Color ceilColor) {
// not implemented
}
/**
* Shows how values map to colors.
*/
public JFrame showLegend() {
return colorMap.showLegend();
}
/**
* Updates the vector field using the data array.
*/
public void update() {
if(griddata==null) {
return;
}
if(autoscaleZ) {
double[] minmax = griddata.getZRange(ampIndex);
colorMap.setScale(minmax[1]);
}
if(griddata.isCellData()) {
double dx = griddata.getDx();
double dy = griddata.getDy();
xmin = griddata.getLeft()-dx/2;
xmax = griddata.getRight()+dx/2;
ymin = griddata.getBottom()+dy/2;
ymax = griddata.getTop()-dy/2;
} else {
xmin = griddata.getLeft();
xmax = griddata.getRight();
ymin = griddata.getBottom();
ymax = griddata.getTop();
}
grid.setMinMax(xmin, xmax, ymin, ymax);
}
/**
* Expands the z scale so as to enhance values close to zero.
*
* @param expanded boolean
* @param expansionFactor double
*/
public void setExpandedZ(boolean expanded, double expansionFactor) {
// does nothing for now.
}
// /**
// * Draws the arrow.
// *
// * @param g the graphics context upon which to draw
// * @param vertex the location of the arrow and its components
// */
// private void drawLine(Graphics2D g2, double[] vertex, DrawingPanel panel) {
// g2.setColor(colorMap.doubleToColor(vertex[2]));
// int pixx = panel.xToPix(vertex[0]);
// int pixy = panel.yToPix(vertex[1]);
// int pixa = panel.xToPix(vertex[0]+vertex[3]);
// int pixb = panel.yToPix(vertex[1]+vertex[4]);
// g2.drawLine(pixx, pixy, pixa, pixb);
// }
//
static GeneralPath createVectorPath(float size) {
float head = Math.min(15, 1+size/5);
GeneralPath path = new GeneralPath();
path.moveTo(-size/2, 0); // start drawing at the base
path.lineTo(size/2, 0); // line to the tip of the head
path.lineTo(size/2-head, (float) 2.0*head/3); // draw one side
path.lineTo(size/2, 0); // back to the tip
path.lineTo(size/2-head, (float) -2.0*head/3); // draw the other side
return path;
}
static GeneralPath createFilledVectorPath(float size) {
float head = Math.min(15, 1+size/5);
GeneralPath path = new GeneralPath();
path.moveTo(-size/2, 1); // start drawing at the base
path.lineTo(size/2-head, 1); // line to base tip of the head
path.lineTo(size/2-head, 2*head/3); // draw to one side
path.lineTo(size/2, 0); // up to the tip
path.lineTo(size/2-head, -2*head/3); // the other side
path.lineTo(size/2-head, -1); // back to base tip of the head
path.moveTo(-size/2, -1); // back to the base
return path;
}
/* The following methods are requried for the measurable interface */
public double getXMin() {
return xmin;
}
public double getXMax() {
return xmax;
}
public double getYMin() {
return ymin;
}
public double getYMax() {
return ymax;
}
public boolean isMeasured() {
return griddata!=null;
}
/**
* Gets an XML.ObjectLoader to save and load data for this program.
*
* @return the object loader
*/
public static XML.ObjectLoader getLoader() {
return new Plot2DLoader() {
public Object createObject(XMLControl control) {
return new VectorPlot(null);
}
};
}
}
/*
* 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
*/