/*
* 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 javax.swing.JFrame;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.display.DrawingPanel;
/**
* ContourPlot draws a contour plot of a scalar field.
*
* Contour uses code from the Surface Plotter package by Yanto Suryono.
*
* @author Wolfgang Christian
* @version 1.0
*/
public class ContourPlot implements Plot2D {
private GridData griddata;
private Color lineColor = new Color(0, 64, 0); // dark green
private boolean visible = true;
private int contour_lines = 12; // number of contour lines
private boolean showContourLines = true;
private boolean showColoredLevels = true; // fill with colors
private double contour_stepz; // contour spacing
private int[] xpoints = new int[8];
private int[] ypoints = new int[8];
private int[] contour_x = new int[8];
private int[] contour_y = new int[8];
private double[] delta = new double[4];
private double[] intersection = new double[4];
private double[][] contour_vertex = new double[4][3];
private ContourAccumulator accumulator = new ContourAccumulator();
private double zmin = 0, zmax = 1.0; // the range for contour levels
private boolean autoscaleZ = true;
private boolean symmetricZ=false;
protected ZExpansion zMap = null;
protected ColorMapper colorMap = new ColorMapper(contour_lines, zmin, zmax, ColorMapper.SPECTRUM);
private Color[] contourColors = new Color[contour_lines+2];
private double[][] internalData = new double[1][1];
private int ampIndex = 0; // amplitude index
private int nx = 0, ny = 0;
private int maxGridSize = 48;
protected boolean interpolateLargeGrids = true; // interpolates a large grid onto a smaller grid to speed the computation of contour lines
/**
* Constructs a ContourPlot without any data.
*
*/
public ContourPlot() {}
/**
* Constructs a ContourPlot that renders the given GridData.
*
* @param _griddata data storage
*/
public ContourPlot(GridData _griddata) {
griddata = _griddata;
if(griddata==null) {
return;
}
nx = (interpolateLargeGrids&&(griddata.getNx()>maxGridSize)) ? 32 : griddata.getNx();
ny = (interpolateLargeGrids&&(griddata.getNy()>maxGridSize)) ? 32 : griddata.getNy();
internalData = new double[nx][ny];
update();
}
/**
* 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);
}
/**
* 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);
}
/**
* Sets the data to new values.
*
* The grid is resized to fit the new data if needed.
*
* @param obj double[][] the new values
*/
public void setAll(Object obj) {
double[][] val = (double[][]) obj;
copyData(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;
copyData(val);
if(griddata.isCellData()) {
griddata.setCellScale(xmin, xmax, ymin, ymax);
} else {
griddata.setScale(xmin, xmax, ymin, ymax);
}
update();
}
private void copyData(double val[][]) {
if((griddata!=null)&&!(griddata instanceof ArrayData)) {
throw new IllegalStateException("SetAll only supports ArrayData for data storage."); //$NON-NLS-1$
}
if((griddata==null)||(griddata.getNx()!=val.length)||(griddata.getNy()!=val[0].length)) {
griddata = new ArrayData(val.length, val[0].length, 1);
setGridData(griddata);
}
double[][] data = griddata.getData()[0];
int ny = data[0].length;
for(int i = 0, nx = data.length; i<nx; i++) {
System.arraycopy(val[i], 0, data[i], 0, ny);
}
}
/**
* Gets the GridData object.
* @return GridData
*/
public GridData getGridData() {
return griddata;
}
/**
* Sets the data storage to the given value.
*
* @param _griddata
*/
public void setGridData(GridData _griddata) {
griddata = _griddata;
if(griddata==null) {
return;
}
nx = (interpolateLargeGrids&&(griddata.getNx()>maxGridSize)) ? 32 : griddata.getNx();
ny = (interpolateLargeGrids&&(griddata.getNy()>maxGridSize)) ? 32 : griddata.getNy();
internalData = new double[nx][ny];
}
/**
* Sets the visibility of the contour plot.
* Drawing will be disabled if visible is false.
*
* @param isVisible
*/
public void setVisible(boolean isVisible) {
visible = isVisible;
}
/**
* Shows how values map to colors.
*/
public JFrame showLegend() {
return colorMap.showLegend(zMap);
}
/**
* Shows the contour lines.
*
* @param showLines
*/
public void setShowGridLines(boolean showLines) {
showContourLines = showLines;
}
/**
* Sets the contour line color.
* The default line color is dark green.
* @param color
*/
public void setGridLineColor(Color color) {
lineColor = color;
}
public void setShowColorLevels(boolean show){
showColoredLevels=show;
}
/**
* Paint the contour.
* @param g
*/
public synchronized void draw(DrawingPanel panel, Graphics g) {
if(!visible||(griddata==null)) {
return;
}
if(!autoscaleZ && showColoredLevels) {
g.setColor(colorMap.getFloorColor());
int w = panel.getWidth()-panel.getLeftGutter()-panel.getRightGutter();
int h = panel.getHeight()-panel.getTopGutter()-panel.getBottomGutter();
g.fillRect(panel.getLeftGutter(), panel.getTopGutter(), Math.max(w, 0), Math.max(h, 0));
}
accumulator.clearAccumulator();
contour_stepz = (zmax-zmin)/(contour_lines+1);
double z = zmin;
for(int c = 0; c<contourColors.length; c++) {
if(!autoscaleZ&&(c==contourColors.length-1)) {
contourColors[c] = colorMap.getCeilColor();
} else {
contourColors[c] = colorMap.doubleToColor(z);
}
z += contour_stepz;
}
double x = griddata.getLeft(), dx = (griddata.getRight()-griddata.getLeft())/(nx-1);
double y = griddata.getTop(), dy = -(griddata.getTop()-griddata.getBottom())/(ny-1);
for(int i = 0, mx = internalData.length-1; i<mx; i++) {
y = griddata.getTop();
for(int j = 0, my = internalData[0].length-1; j<my; j++) {
contour_vertex[0][0] = x;
contour_vertex[0][1] = y;
contour_vertex[0][2] = internalData[i][j];
contour_vertex[1][0] = x;
contour_vertex[1][1] = y+dy;
contour_vertex[1][2] = internalData[i][j+1];
contour_vertex[2][0] = x+dx;
contour_vertex[2][1] = y+dy;
contour_vertex[2][2] = internalData[i+1][j+1];
contour_vertex[3][0] = x+dx;
contour_vertex[3][1] = y;
contour_vertex[3][2] = internalData[i+1][j];
createContour(panel, g);
y += dy;
}
x += dx;
}
if(showContourLines) {
g.setColor(lineColor);
accumulator.drawAll(g);
int lpix = panel.xToPix(griddata.getLeft());
int tpix = panel.yToPix(griddata.getTop());
int rpix = panel.xToPix(griddata.getRight());
int bpix = panel.yToPix(griddata.getBottom());
g.drawRect(Math.min(lpix, rpix), Math.min(tpix, bpix), Math.abs(lpix-rpix), Math.abs(tpix-bpix));
}
}
/**
* 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 {
zmax = ceil;
zmin = floor;
if(zMap!=null) {
zMap.setMinMax(zmin, zmax);
}
colorMap.setScale(zmin, zmax);
}
}
/**
* Forces the z-scale to be symmetric about zero.
* Forces zmax to be positive and zmin=-zmax when in autoscale mode.
*
* @param symmetric
*/
public void setSymmetricZ(boolean symmetric){
symmetricZ=symmetric;
}
/**
* Gets the symmetric z flag.
*/
public boolean isSymmetricZ(){
return symmetricZ;
}
/**
* Sets flag to interpolates a large grid onto a smaller grid to speed the computation of contour lines.
*
* @param interpolate boolean
*/
public void setInterpolateLargeGrids(boolean interpolate) {
interpolateLargeGrids = interpolate;
}
/**
* Retruns true if plot interpolates a large grid onto a smaller grid to speed the computation of contour lines.
* @return boolean
*/
public boolean isInterpolateLargeGrids() {
return interpolateLargeGrids;
}
/**
* 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) {
if(expanded&&(expansionFactor>0)) {
zMap = new ZExpansion(expansionFactor);
zMap.setMinMax(zmin, zmax);
} else {
zMap = null;
}
}
/**
* 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 colorMap.getFloor();
}
/**
* Gets the ceiling for scaling the z data.
* @return double
*/
public double getCeiling() {
return colorMap.getCeil();
}
/**
* Updates the contour data.
*/
public void update() {
if(griddata==null) {
return;
}
if((interpolateLargeGrids&&(nx!=griddata.getNx()))||(ny!=griddata.getNy())) {
updateInterpolated(griddata);
} else {
updateDirect(griddata);
}
colorMap.updateLegend(zMap);
}
/**
* Updates the internal data by interpolating large grids onto a smaller array.
*/
void updateInterpolated(GridData griddata) {
if(autoscaleZ) {
double[] minmax = griddata.getZRange(ampIndex);
if(symmetricZ){
zmax=Math.max(Math.abs(minmax[1]),Math.abs(minmax[0]));
zmin=-zmax;
}else{
zmax = minmax[1];
zmin = minmax[0];
}
if(zMap!=null) {
zMap.setMinMax(zmin, zmax);
}
colorMap.setScale(zmin, zmax);
}
double x = griddata.getLeft(), dx = (griddata.getRight()-griddata.getLeft())/(nx-1);
double y = griddata.getTop(), dy = -(griddata.getTop()-griddata.getBottom())/(ny-1);
for(int i = 0; i<nx; i++) {
y = griddata.getTop();
for(int j = 0; j<ny; j++) {
internalData[i][j] = griddata.interpolate(x, y, ampIndex);
if(zMap!=null) {
internalData[i][j] = zMap.evaluate(internalData[i][j]);
}
y += dy;
}
x += dx;
}
}
/**
* Updates the contour data my directly copying values.
*/
void updateDirect(GridData griddata) {
if(griddata==null) {
return;
}
if(autoscaleZ) {
double[] minmax = griddata.getZRange(ampIndex);
if(symmetricZ){
zmax=Math.max(Math.abs(minmax[1]),Math.abs(minmax[0]));
zmin=-zmax;
}else{
zmax = minmax[1];
zmin = minmax[0];
}
if(zMap!=null) {
zMap.setMinMax(zmin, zmax);
}
colorMap.setScale(zmin, zmax);
}
if(griddata instanceof ArrayData) {
double[][] arrayData = griddata.getData()[ampIndex];
for(int i = 0; i<nx; i++) { // copy the rows
System.arraycopy(arrayData[i], 0, internalData[i], 0, ny);
if(zMap!=null) {
for(int j = 0; j<ny; j++) {
internalData[i][j] = zMap.evaluate(internalData[i][j]);
}
}
}
} else if(griddata instanceof GridPointData) {
double[][][] ptdata = griddata.getData();
for(int i = 0, nx = ptdata.length; i<nx; i++) {
for(int j = 0, ny = ptdata[0].length; j<ny; j++) {
internalData[i][j] = ptdata[i][j][2+ampIndex];
if(zMap!=null) {
internalData[i][j] = zMap.evaluate(internalData[i][j]);
}
}
}
}
}
/**
* Creates contour plot of a single area division. Called by
* <code>draw</code> method
*
* @see #draw
*/
private final void createContour(DrawingPanel panel, Graphics g) {
double z = zmin;
xpoints[0] = panel.xToPix(contour_vertex[0][0])+1;
xpoints[2] = panel.xToPix(contour_vertex[1][0])+1;
xpoints[4] = panel.xToPix(contour_vertex[2][0])+1;
xpoints[6] = panel.xToPix(contour_vertex[3][0])+1;
xpoints[1] = xpoints[3] = xpoints[5] = xpoints[7] = -1;
ypoints[0] = panel.yToPix(contour_vertex[0][1])+1;
ypoints[4] = panel.yToPix(contour_vertex[2][1])+1;
ypoints[2] = ypoints[3] = panel.yToPix(contour_vertex[1][1])+1;
ypoints[6] = ypoints[7] = panel.yToPix(contour_vertex[3][1])+1;
int xmin = xpoints[0];
int xmax = xpoints[4];
for(int counter = 0; counter<=contour_lines+1; counter++) {
// Analyzes edges
for(int edge = 0; edge<4; edge++) {
int index = (edge<<1)+1;
int nextedge = (edge+1)&3;
if(z>contour_vertex[edge][2]) {
xpoints[index-1] = -2;
if(z>contour_vertex[nextedge][2]) {
xpoints[(index+1)&7] = -2;
xpoints[index] = -2;
}
} else if(z>contour_vertex[nextedge][2]) {
xpoints[(index+1)&7] = -2;
}
if(xpoints[index]!=-2) {
if(xpoints[index]!=-1) {
intersection[edge] += delta[edge];
if((index==1)||(index==5)) {
ypoints[index] = panel.yToPix(intersection[edge])+1;
} else {
xpoints[index] = panel.xToPix(intersection[edge])+1;
}
} else {
if((z>contour_vertex[edge][2])||(z>contour_vertex[nextedge][2])) {
switch(index) {
case 1 :
delta[edge] = (contour_vertex[nextedge][1]-contour_vertex[edge][1])*contour_stepz/(contour_vertex[nextedge][2]-contour_vertex[edge][2]);
intersection[edge] = (contour_vertex[nextedge][1]*(z-contour_vertex[edge][2])+contour_vertex[edge][1]*(contour_vertex[nextedge][2]-z))/(contour_vertex[nextedge][2]-contour_vertex[edge][2]);
xpoints[index] = xmin;
ypoints[index] = panel.yToPix(intersection[edge])+1;
break;
case 3 :
delta[edge] = (contour_vertex[nextedge][0]-contour_vertex[edge][0])*contour_stepz/(contour_vertex[nextedge][2]-contour_vertex[edge][2]);
intersection[edge] = (contour_vertex[nextedge][0]*(z-contour_vertex[edge][2])+contour_vertex[edge][0]*(contour_vertex[nextedge][2]-z))/(contour_vertex[nextedge][2]-contour_vertex[edge][2]);
xpoints[index] = panel.xToPix(intersection[edge])+1;
break;
case 5 :
delta[edge] = (contour_vertex[edge][1]-contour_vertex[nextedge][1])*contour_stepz/(contour_vertex[edge][2]-contour_vertex[nextedge][2]);
intersection[edge] = (contour_vertex[edge][1]*(z-contour_vertex[nextedge][2])+contour_vertex[nextedge][1]*(contour_vertex[edge][2]-z))/(contour_vertex[edge][2]-contour_vertex[nextedge][2]);
xpoints[index] = xmax;
ypoints[index] = panel.yToPix(intersection[edge])+1;
break;
case 7 :
delta[edge] = (contour_vertex[edge][0]-contour_vertex[nextedge][0])*contour_stepz/(contour_vertex[edge][2]-contour_vertex[nextedge][2]);
intersection[edge] = (contour_vertex[edge][0]*(z-contour_vertex[nextedge][2])+contour_vertex[nextedge][0]*(contour_vertex[edge][2]-z))/(contour_vertex[edge][2]-contour_vertex[nextedge][2]);
xpoints[index] = panel.xToPix(intersection[edge])+1;
break;
}
}
}
}
}
// Creates polygon
int contour_n = 0;
for(int index = 0; index<8; index++) {
if(xpoints[index]>=0) {
contour_x[contour_n] = xpoints[index];
contour_y[contour_n] = ypoints[index];
contour_n++;
}
}
if(showColoredLevels&&(colorMap.getPaletteType()!=ColorMapper.WIREFRAME)) {
g.setColor(contourColors[counter]);
if(contour_n>0) {
g.fillPolygon(contour_x, contour_y, contour_n);
}
}
// Creates contour lines
if(showContourLines) {
int x = -1;
int y = -1;
for(int index = 1; index<8; index += 2) {
if(xpoints[index]>=0) {
if(x!=-1) {
accumulator.addLine(x, y, xpoints[index], ypoints[index]);
}
x = xpoints[index];
y = ypoints[index];
}
}
if((xpoints[1]>0)&&(x!=-1)) {
accumulator.addLine(x, y, xpoints[1], ypoints[1]);
}
}
if(contour_n<3) {
break;
}
z += contour_stepz;
}
}
/**
* Determines the palette type that will be used.
*
* @param colors Color[]
*/
public void setColorPalette(Color[] colors) {
colorMap.setColorPalette(colors);
}
/**
* Sets the type of palette.
*
* Palette types are defined in the ColorMapper class and include: SPECTRUM, GRAYSCALE, and DUALSHADE.
*
* @param mode
*/
public void setPaletteType(int mode) {
colorMap.setPaletteType(mode);
}
/**
* Sets the floor, ceiling, and line colors.
*
* @param floorColor
* @param ceilColor
*/
public void setFloorCeilColor(Color floorColor, Color ceilColor) {
colorMap.setFloorCeilColor(floorColor, ceilColor);
}
/**
* Sets the indexes for the data components that will be plotted.
*
* @param indexes the sample-component indexes
*/
public void setIndexes(int[] indexes) {
ampIndex = indexes[0];
}
/**
* Sets the number of contour levels.
*
* @param n number of levels.
*/
public void setNumberOfLevels(int n) { // Create colors array
contour_lines = n;
colorMap.setNumberOfColors(n); // Paco changed next line by this one
contourColors = new Color[contour_lines+2];
}
/* The following methods are requried for the measurable interface */
public double getXMin() {
return griddata.getLeft();
}
public double getXMax() {
return griddata.getRight();
}
public double getYMin() {
return griddata.getBottom();
}
public double getYMax() {
return griddata.getTop();
}
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 void saveObject(XMLControl control, Object obj) {
super.saveObject(control, obj);
ContourPlot plot = (ContourPlot) obj;
control.setValue("line color", plot.lineColor); //$NON-NLS-1$
control.setValue("color map", plot.colorMap); //$NON-NLS-1$
}
public Object createObject(XMLControl control) {
return new ContourPlot(null);
}
public Object loadObject(XMLControl control, Object obj) {
super.loadObject(control, obj);
ContourPlot plot = (ContourPlot) obj;
plot.lineColor = (Color) control.getObject("line color"); //$NON-NLS-1$
plot.colorMap = (ColorMapper) control.getObject("color map"); //$NON-NLS-1$
return plot;
}
};
}
}
/*
* 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
*/