/* * 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.Label; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.text.DecimalFormat; import javax.swing.JFrame; import org.opensourcephysics.controls.XML; import org.opensourcephysics.controls.XMLControl; import org.opensourcephysics.display.DrawingPanel; import org.opensourcephysics.display.False3D; /** * SurfacePlot draws a 3D surface of a scalar field. * Surfaceplot uses code from the Surface Plotter package by Yanto Suryono. * * @author Wolfgang Christian and Yanto Suryono * @version 1.0 */ public class ComplexSurfacePlot implements Plot2D, False3D { boolean visible = true; ComplexColorMapper colorMap = new ComplexColorMapper(1); // color map with ceiling=1. protected DecimalFormat labelFormat = new DecimalFormat("0.00"); //$NON-NLS-1$ private static final int TOP = 0; private static final int CENTER = 1; // // for splitting polygons // private static final int UPPER = 1; // private static final int COINCIDE = 0; // private static final int LOWER = -1; /** Field INIT_CALC_DIV */ public static final int INIT_CALC_DIV = 33; /** Field INIT_DISP_DIV */ public static final int INIT_DISP_DIV = INIT_CALC_DIV; private int calc_divisions = INIT_CALC_DIV; // number of divisions to calculate private int disp_divisions = INIT_DISP_DIV; // number of divisions to calculate private int plot_mode = ColorMapper.SPECTRUM; private boolean isBoxed, isMesh, isScaleBox, isDisplayXY, isDisplayZ, isDisplayGrids; private double zmin = -2, zmax = 2; private boolean autoscaleZ = true; private boolean symmetricZ = false; private GridData griddata; //private double color_factor; private Point projection; private ComplexSurfaceVertex cop; // center of projection private ComplexSurfaceVertex[] vertexArray; // vertices array private final ComplexSurfaceVertex values1[] = new ComplexSurfaceVertex[4]; // private final ComplexSurfaceVertex values2[] = new ComplexSurfaceVertex[4]; // private double color; // color of surface private Color line_color = Color.black; private int factor_x, factor_y; // conversion factors private int t_x, t_y, t_z; // determines ticks density //private boolean mouseDown = false; private int click_x, click_y; // previous mouse cursor position private boolean invalidProjection = true; private int iwidth = 0, iheight = 0; // the width and height of the last drawing operation private double xmin, xmax, ymin, ymax; private int ampIndex = 0; // amplitude index private int reIndex = 1; // real index private int imIndex = 2; // imaginary index // the following are needed by the SurfaceVertex SurfacePlotProjector projector; // the projector double zminV, zmaxV, zfactorV; int master_project_indexV = 0; // over 4 billion times to reset ZExpansion zMap; /** * Constructs the ComplexSurfacePlot without data. */ public ComplexSurfacePlot() { this(null); } /** * ComplexSurfacePlot constructor with the given data model. * * @param _griddata GridData */ public ComplexSurfacePlot(GridData _griddata) { griddata = _griddata; defaultVariables(); autoscaleZ = true; projector = new SurfacePlotProjector(); // projector.setDistance(70); projector.setDistance(200); // projector.set2DScaling(15); projector.set2DScaling(8); projector.setRotationAngle(125); projector.setElevationAngle(10); setGridData(_griddata); 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; copyComplexData(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; copyComplexData(val); if(griddata.isCellData()) { griddata.setCellScale(xmin, xmax, ymin, ymax); } else { griddata.setScale(xmin, xmax, ymin, ymax); } update(); } private void copyComplexData(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[][] mag = griddata.getData()[0]; double[][] reData = griddata.getData()[1]; double[][] imData = griddata.getData()[2]; // current grid has correct size int ny = vals[0][0].length; for(int i = 0, nx = vals[0].length; i<nx; i++) { System.arraycopy(vals[0][i], 0, reData[i], 0, ny); System.arraycopy(vals[1][i], 0, imData[i], 0, ny); for(int j = 0; j<ny; j++) { mag[i][j] = Math.sqrt(vals[0][i][j]*vals[0][i][j]+vals[1][i][j]*vals[1][i][j]); } } } /** * 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) throws IllegalArgumentException { griddata = _griddata; if(griddata==null) { return; } if(griddata.getNx()!=griddata.getNy()) { throw new IllegalArgumentException("SurfacePlot requires square grids."); //$NON-NLS-1$ } } private void generateVerticesFromArray(ArrayData griddata) { double[][] ampdata = griddata.getData()[ampIndex]; double[][] redata = griddata.getData()[reIndex]; double[][] imdata = griddata.getData()[imIndex]; int numRows = ampdata.length; int numCols = ampdata[0].length; calc_divisions = numRows-1; double xfactor = 20/(xmax-xmin); double yfactor = 20/(ymax-ymin); if((vertexArray==null)||(vertexArray.length!=numRows*numCols)) { vertexArray = new ComplexSurfaceVertex[numRows*numCols]; } double dx = Math.abs(griddata.getDx()); double dy = Math.abs(griddata.getDy()); double x = xmin; // left; for(int ix = 0; ix<numCols; ix++) { double y = ymin; // bottom; for(int iy = 0; iy<numRows; iy++) { int iyd = (griddata.getDy()>0) ? iy : numCols-iy-1; int ixd = (griddata.getDx()>0) ? ix : numCols-ix-1; double zval = ampdata[ixd][iyd]; if(zMap!=null) { zval = zMap.evaluate(zval); } vertexArray[ix*numRows+iy] = new ComplexSurfaceVertex(-10+(x-xmin)*xfactor, -10+(y-ymin)*yfactor, zval, redata[ixd][iyd], imdata[ixd][iyd], this); y += dy; } x += dx; } ampdata = null; } private void generateVerticesFromPoints(GridPointData griddata) throws IllegalArgumentException { double[][][] data = griddata.getData(); int numRows = data.length; int numCols = data[0].length; calc_divisions = numRows-1; double xfactor = 20/(xmax-xmin); double yfactor = 20/(ymax-ymin); if((vertexArray==null)||(vertexArray.length!=numRows*numCols)) { vertexArray = new ComplexSurfaceVertex[numRows*numCols]; } double dx = Math.abs(griddata.getDx()); double dy = Math.abs(griddata.getDy()); int ampIndex = this.ampIndex+2; int reIndex = this.reIndex+2; int imIndex = this.imIndex+2; double x = xmin; // left; for(int ix = 0; ix<numCols; ix++) { double y = ymin; // bottom; for(int iy = 0; iy<numRows; iy++) { double zval = data[ix][iy][ampIndex]; if(zMap!=null) { zval = zMap.evaluate(zval); } vertexArray[ix*numRows+iy] = new ComplexSurfaceVertex(-10+(x-xmin)*xfactor, -10+(y-ymin)*yfactor, zval, data[ix][iy][reIndex], data[ix][iy][imIndex], this); y += dy; } x += dx; } data = null; } /** * Sets the indexes for the data components that will be plotted. * * Indexes determine the postion of the amplitude, real-component, and imaginary-component * in the data array. * * @param indexes the sample-component indexes */ public void setIndexes(int[] indexes) { ampIndex = indexes[0]; reIndex = indexes[1]; imIndex = indexes[2]; } /** * Method projectVertexArray * */ void projectVertexArray() { ComplexSurfaceVertex[] tempArray = vertexArray; // reference to the array so it cannot change. if(tempArray==null) { return; } for(int i = 0, num = tempArray.length; i<num; i++) { tempArray[i].project(); } } /** * Sets the colors that will be used between the floor and ceiling values. * Not implemented. Color always maps to phase. * @param colors */ public void setColorPalette(Color[] colors) { // Not implemented. Color always maps to phase. } /** * Sets the visibility of the lattice. * Drawing will be disabled if visible is false. * * @param isVisible */ public void setVisible(boolean isVisible) { visible = isVisible; } /** * Shows a legend of phase angle and color. */ public JFrame showLegend() { return colorMap.showLegend(); } /** * Outlines the data grid's boundaries. * * @param show */ public void setShowGridLines(boolean show) { isMesh = show; } /** * Sets the color for grid line boundaries * * @param c */ public void setGridLineColor(Color c) { line_color = c; } /** * Paint the surface. * @param panel * @param g */ public void draw(DrawingPanel panel, Graphics g) { if(!visible||(griddata==null)) { return; } projector.setProjectionArea(new Rectangle(0, 0, panel.getBounds().width, panel.getBounds().height)); if(invalidProjection||(iwidth!=panel.getWidth())||(iheight!=panel.getHeight())) { master_project_indexV++; invalidProjection = false; projectVertexArray(); iwidth = panel.getWidth(); iheight = panel.getHeight(); } plotSurface(g); } private void defaultVariables() { plot_mode = ColorMapper.SPECTRUM; isBoxed = true; isMesh = true; isScaleBox = false; isDisplayXY = true; isDisplayZ = true; isDisplayGrids = false; } /** * Determines whether a plane is plottable, i.e: does not have * invalid vertex. * * @return <code>true</code> if the plane is plottable, * <code>false</code> otherwise * @param values vertices array of the plane */ private final boolean plottable(ComplexSurfaceVertex[] values) { try { return(!values[0].isInvalid()&&!values[1].isInvalid()&&!values[2].isInvalid()&&!values[3].isInvalid()); } catch(Exception ex) {} return false; } /** * Sets the axes scaling factor. Computes the proper axis lengths * based on the ratio of variable ranges. The axis lengths will * also affect the size of bounding box. */ private final void setAxesScale() { double scale_x, scale_y, scale_z, divisor; int longest; if(!isScaleBox) { projector.setScaling(1); t_x = t_y = t_z = 4; return; } scale_x = xmax-xmin; scale_y = ymax-ymin; scale_z = zmax-zmin; if(scale_x<scale_y) { if(scale_y<scale_z) { longest = 3; divisor = scale_z; } else { longest = 2; divisor = scale_y; } } else { if(scale_x<scale_z) { longest = 3; divisor = scale_z; } else { longest = 1; divisor = scale_x; } } scale_x /= divisor; scale_y /= divisor; scale_z /= divisor; if((scale_x<0.2f)||(scale_y<0.2f)&&(scale_z<0.2f)) { switch(longest) { case 1 : if(scale_y<scale_z) { scale_y /= scale_z; scale_z = 1.0f; } else { scale_z /= scale_y; scale_y = 1.0f; } break; case 2 : if(scale_x<scale_z) { scale_x /= scale_z; scale_z = 1.0f; } else { scale_z /= scale_x; scale_x = 1.0f; } break; case 3 : if(scale_y<scale_x) { scale_y /= scale_x; scale_x = 1.0f; } else { scale_x /= scale_y; scale_y = 1.0f; } break; } } if(scale_x<0.2f) { scale_x = 1.0f; } projector.setXScaling(scale_x); if(scale_y<0.2f) { scale_y = 1.0f; } projector.setYScaling(scale_y); if(scale_z<0.2f) { scale_z = 1.0f; } projector.setZScaling(scale_z); if(scale_x<0.5f) { t_x = 8; } else { t_x = 4; } if(scale_y<0.5f) { t_y = 8; } else { t_y = 4; } if(scale_z<0.5f) { t_z = 8; } else { t_z = 4; } } /** * Gets the number of divisions to be displayed. * Automatically fixes invalid values. * * @return valid number of divisions to be displayed */ private int getDispDivisions() { int plot_density; plot_density = disp_divisions; if(plot_density>calc_divisions) { plot_density = calc_divisions; } while((calc_divisions%plot_density)!=0) { plot_density++; } return plot_density; } /** * Creates a surface plot */ private final void plotSurface(Graphics g) { double zi, zx; int sx, sy; int start_lx, end_lx; int start_ly, end_ly; zi = zmin; zx = zmax; int plot_density = getDispDivisions(); int multiple_factor = calc_divisions/plot_density; disp_divisions = plot_density; zmin = zi; zmax = zx; /* color_factor = 0.8/(zmax-zmin); if((plot_mode==ColorMapper.DUALSHADE)||(plot_mode==ColorMapper.RED)||(plot_mode==ColorMapper.GREEN)||(plot_mode==ColorMapper.BLUE)) { color_factor *= 0.6/0.8; }*/ if(vertexArray==null) { drawBoxGridsTicksLabels(g, false); drawBoundingBox(g); return; } if(plot_mode==ColorMapper.NORENDER) { drawBoxGridsTicksLabels(g, true); drawBoundingBox(g); return; } drawBoxGridsTicksLabels(g, false); // SurfaceVertex.setZRange(zmin,zmax); zmaxV = zmax; zminV = zmin; zfactorV = 20/(zmaxV-zminV); // direction test double distance = projector.getDistance()*projector.getCosElevationAngle(); // cop : center of projection cop = new ComplexSurfaceVertex(distance*projector.getSinRotationAngle(), distance*projector.getCosRotationAngle(), projector.getDistance()*projector.getSinElevationAngle(), 1, 0, this); cop.transform(); boolean inc_x = cop.x>0; boolean inc_y = cop.y>0; // critical = false; if(inc_x) { start_lx = 0; end_lx = calc_divisions; sx = multiple_factor; } else { start_lx = calc_divisions; end_lx = 0; sx = -multiple_factor; } if(inc_y) { start_ly = 0; end_ly = calc_divisions; sy = multiple_factor; } else { start_ly = calc_divisions; end_ly = 0; sy = -multiple_factor; } if((cop.x>10)||(cop.x<-10)) { if((cop.y>10)||(cop.y<-10)) { plotArea(g, start_lx, start_ly, end_lx, end_ly, sx, sy); } else { // split in y direction int split_y = (int) ((cop.y+10)*plot_density/20)*multiple_factor; plotArea(g, start_lx, 0, end_lx, split_y, sx, multiple_factor); plotArea(g, start_lx, calc_divisions, end_lx, split_y, sx, -multiple_factor); } } else { if((cop.y>10)||(cop.y<-10)) { // split in x direction int split_x = (int) ((cop.x+10)*plot_density/20)*multiple_factor; plotArea(g, 0, start_ly, split_x, end_ly, multiple_factor, sy); plotArea(g, calc_divisions, start_ly, split_x, end_ly, -multiple_factor, sy); } else { // split in both x and y directions int split_x = (int) ((cop.x+10)*plot_density/20)*multiple_factor; int split_y = (int) ((cop.y+10)*plot_density/20)*multiple_factor; // critical = true; plotArea(g, 0, 0, split_x, split_y, multiple_factor, multiple_factor); plotArea(g, 0, calc_divisions, split_x, split_y, multiple_factor, -multiple_factor); plotArea(g, calc_divisions, 0, split_x, split_y, -multiple_factor, multiple_factor); plotArea(g, calc_divisions, calc_divisions, split_x, split_y, -multiple_factor, -multiple_factor); } } if(isBoxed) { drawBoundingBox(g); } } private final int poly_x[] = new int[9]; private final int poly_y[] = new int[9]; /** * Plots a single plane * * @param vertex vertices array of the plane * @param verticescount number of vertices to process */ private final void plotPlane(Graphics g, ComplexSurfaceVertex[] vertex, int verticescount) { double[] samples = new double[3]; int count, loop, index; double re, im, result; boolean low1, low2; boolean valid1, valid2; if(verticescount<3) { return; } count = 0; //z = 0.0f; re = 0.0f; im = 0.0f; line_color = Color.black; low1 = (vertex[0].z<zmin); valid1 = !low1&&(vertex[0].z<=zmax); index = 1; for(loop = 0; loop<verticescount; loop++) { low2 = (vertex[index].z<zmin); valid2 = !low2&&(vertex[index].z<=zmax); if((valid1||valid2)||(low1^low2)) { if(!valid1) { if(low1) { result = zmin; } else { result = zmax; } double ratio = (result-vertex[index].z)/(vertex[loop].z-vertex[index].z); double new_x = ratio*(vertex[loop].x-vertex[index].x)+vertex[index].x; double new_y = ratio*(vertex[loop].y-vertex[index].y)+vertex[index].y; if(low1) { projection = projector.project(new_x, new_y, -10); } else { projection = projector.project(new_x, new_y, 10); } poly_x[count] = projection.x; poly_y[count] = projection.y; count++; //z += result; } if(valid2) { projection = vertex[index].projection(); poly_x[count] = projection.x; poly_y[count] = projection.y; count++; //z += vertex[index].z; re += vertex[index].re; im += vertex[index].im; } else { if(low2) { result = zmin; } else { result = zmax; } double ratio = (result-vertex[loop].z)/(vertex[index].z-vertex[loop].z); double new_x = ratio*(vertex[index].x-vertex[loop].x)+vertex[loop].x; double new_y = ratio*(vertex[index].y-vertex[loop].y)+vertex[loop].y; if(low2) { projection = projector.project(new_x, new_y, -10); } else { projection = projector.project(new_x, new_y, 10); } poly_x[count] = projection.x; poly_y[count] = projection.y; count++; //z += result; } } if(++index==verticescount) { index = 0; } valid1 = valid2; low1 = low2; } if(count>0) { switch(plot_mode) { case ColorMapper.NORENDER : g.setColor(Color.lightGray); break; default : samples[0] = 0.99; samples[1] = re; samples[2] = im; g.setColor(colorMap.samplesToColor(samples)); } g.fillPolygon(poly_x, poly_y, count); g.setColor(line_color); if(isMesh) { poly_x[count] = poly_x[0]; poly_y[count] = poly_y[0]; count++; g.drawPolygon(poly_x, poly_y, count); } } } /** * Plots an area of group of planes * * @param start_lx start index in x direction * @param start_ly start index in y direction * @param end_lx end index in x direction * @param end_ly end index in y direction * @param sx step in x direction * @param sy step in y direction */ private final void plotArea(Graphics g, int start_lx, int start_ly, int end_lx, int end_ly, int sx, int sy) { start_lx *= calc_divisions+1; sx *= calc_divisions+1; end_lx *= calc_divisions+1; int lx = start_lx; int ly = start_ly; while(ly!=end_ly) { values1[1] = vertexArray[lx+ly]; values1[2] = vertexArray[lx+ly+sy]; while(lx!=end_lx) { values1[0] = values1[1]; values1[1] = vertexArray[lx+sx+ly]; values1[3] = values1[2]; values1[2] = vertexArray[lx+sx+ly+sy]; if(plottable(values1)) { plotPlane(g, values1, 4); } lx += sx; } ly += sy; lx = start_lx; } } /** * Draws non-surface parts, i.e: bounding box, axis grids, axis ticks, * axis labels, base plane. * * @param g the graphics context to draw * @param draw_axes if <code>true</code>, only draws base plane and z axis */ private final void drawBoxGridsTicksLabels(Graphics g, boolean draw_axes) { Point projection, tickpos; boolean x_left = false, y_left = false; int x[], y[], i; x = new int[5]; y = new int[5]; if(projector==null) { return; } if(draw_axes) { drawBase(g, x, y); projection = projector.project(0, 0, -10); x[0] = projection.x; y[0] = projection.y; projection = projector.project(10.5f, 0, -10); g.drawLine(x[0], y[0], projection.x, projection.y); if(projection.x<x[0]) { outString(g, (int) (1.05*(projection.x-x[0]))+x[0], (int) (1.05*(projection.y-y[0]))+y[0], "x", Label.RIGHT, TOP); //$NON-NLS-1$ } else { outString(g, (int) (1.05*(projection.x-x[0]))+x[0], (int) (1.05*(projection.y-y[0]))+y[0], "x", Label.LEFT, TOP); //$NON-NLS-1$ } projection = projector.project(0, 11.5f, -10); g.drawLine(x[0], y[0], projection.x, projection.y); if(projection.x<x[0]) { outString(g, (int) (1.05*(projection.x-x[0]))+x[0], (int) (1.05*(projection.y-y[0]))+y[0], "y", Label.RIGHT, TOP); //$NON-NLS-1$ } else { outString(g, (int) (1.05*(projection.x-x[0]))+x[0], (int) (1.05*(projection.y-y[0]))+y[0], "y", Label.LEFT, TOP); //$NON-NLS-1$ } projection = projector.project(0, 0, 10.5f); g.drawLine(x[0], y[0], projection.x, projection.y); outString(g, (int) (1.05*(projection.x-x[0]))+x[0], (int) (1.05*(projection.y-y[0]))+y[0], "z", Label.CENTER, CENTER); //$NON-NLS-1$ } else { factor_x = factor_y = 1; projection = projector.project(0, 0, -10); x[0] = projection.x; projection = projector.project(10.5f, 0, -10); y_left = projection.x>x[0]; i = projection.y; projection = projector.project(-10.5f, 0, -10); if(projection.y>i) { factor_x = -1; y_left = projection.x>x[0]; } projection = projector.project(0, 10.5f, -10); x_left = projection.x>x[0]; i = projection.y; projection = projector.project(0, -10.5f, -10); if(projection.y>i) { factor_y = -1; x_left = projection.x>x[0]; } setAxesScale(); drawBase(g, x, y); if(isBoxed) { projection = projector.project(-factor_x*10, -factor_y*10, -10); x[0] = projection.x; y[0] = projection.y; projection = projector.project(-factor_x*10, -factor_y*10, 10); x[1] = projection.x; y[1] = projection.y; projection = projector.project(factor_x*10, -factor_y*10, 10); x[2] = projection.x; y[2] = projection.y; projection = projector.project(factor_x*10, -factor_y*10, -10); x[3] = projection.x; y[3] = projection.y; x[4] = x[0]; y[4] = y[0]; if(plot_mode!=ColorMapper.WIREFRAME) { if(plot_mode==ColorMapper.NORENDER) { g.setColor(Color.lightGray); } else { g.setColor(new Color(192, 220, 192)); } g.fillPolygon(x, y, 4); } g.setColor(Color.black); g.drawPolygon(x, y, 5); projection = projector.project(-factor_x*10, factor_y*10, 10); x[2] = projection.x; y[2] = projection.y; projection = projector.project(-factor_x*10, factor_y*10, -10); x[3] = projection.x; y[3] = projection.y; x[4] = x[0]; y[4] = y[0]; if(plot_mode!=ColorMapper.WIREFRAME) { if(plot_mode==ColorMapper.NORENDER) { g.setColor(Color.lightGray); } else { g.setColor(new Color(192, 220, 192)); } g.fillPolygon(x, y, 4); } g.setColor(Color.black); g.drawPolygon(x, y, 5); } else if(isDisplayZ) { projection = projector.project(factor_x*10, -factor_y*10, -10); x[0] = projection.x; y[0] = projection.y; projection = projector.project(factor_x*10, -factor_y*10, 10); g.drawLine(x[0], y[0], projection.x, projection.y); projection = projector.project(-factor_x*10, factor_y*10, -10); x[0] = projection.x; y[0] = projection.y; projection = projector.project(-factor_x*10, factor_y*10, 10); g.drawLine(x[0], y[0], projection.x, projection.y); } for(i = -9; i<=9; i++) { if(isDisplayXY||isDisplayGrids) { if(!isDisplayGrids||(i%(t_y/2)==0)||isDisplayXY) { if(isDisplayGrids&&(i%t_y==0)) { projection = projector.project(-factor_x*10, i, -10); } else { if(i%t_y!=0) { projection = projector.project(factor_x*9.8f, i, -10); } else { projection = projector.project(factor_x*9.5f, i, -10); } } tickpos = projector.project(factor_x*10, i, -10); g.drawLine(projection.x, projection.y, tickpos.x, tickpos.y); if((i%t_y==0)&&isDisplayXY) { tickpos = projector.project(factor_x*10.5f, i, -10); if(y_left) { outFloat(g, tickpos.x, tickpos.y, (double) (i+10)/20*(ymax-ymin)+ymin, Label.LEFT, TOP); } else { outFloat(g, tickpos.x, tickpos.y, (double) (i+10)/20*(ymax-ymin)+ymin, Label.RIGHT, TOP); } } } if(!isDisplayGrids||(i%(t_x/2)==0)||isDisplayXY) { if(isDisplayGrids&&(i%t_x==0)) { projection = projector.project(i, -factor_y*10, -10); } else { if(i%t_x!=0) { projection = projector.project(i, factor_y*9.8f, -10); } else { projection = projector.project(i, factor_y*9.5f, -10); } } tickpos = projector.project(i, factor_y*10, -10); g.drawLine(projection.x, projection.y, tickpos.x, tickpos.y); if((i%t_x==0)&&isDisplayXY) { tickpos = projector.project(i, factor_y*10.5f, -10); if(x_left) { outFloat(g, tickpos.x, tickpos.y, (double) (i+10)/20*(xmax-xmin)+xmin, Label.LEFT, TOP); } else { outFloat(g, tickpos.x, tickpos.y, (double) (i+10)/20*(xmax-xmin)+xmin, Label.RIGHT, TOP); } } } } if(isDisplayXY) { tickpos = projector.project(0, factor_y*14, -10); outString(g, tickpos.x, tickpos.y, "X", Label.CENTER, TOP); //$NON-NLS-1$ tickpos = projector.project(factor_x*14, 0, -10); outString(g, tickpos.x, tickpos.y, "Y", Label.CENTER, TOP); //$NON-NLS-1$ } // z grids and ticks if(isDisplayZ||(isDisplayGrids&&isBoxed)) { if(!isDisplayGrids||(i%(t_z/2)==0)||isDisplayZ) { if(isBoxed&&isDisplayGrids&&(i%t_z==0)) { projection = projector.project(-factor_x*10, -factor_y*10, i); tickpos = projector.project(-factor_x*10, factor_y*10, i); } else { if(i%t_z==0) { projection = projector.project(-factor_x*10, factor_y*9.5f, i); } else { projection = projector.project(-factor_x*10, factor_y*9.8f, i); } tickpos = projector.project(-factor_x*10, factor_y*10, i); } g.drawLine(projection.x, projection.y, tickpos.x, tickpos.y); if(isDisplayZ) { tickpos = projector.project(-factor_x*10, factor_y*10.5f, i); if(i%t_z==0) { if(x_left) { outFloat(g, tickpos.x, tickpos.y, (double) (i+10)/20*(zmax-zmin)+zmin, Label.LEFT, CENTER); } else { outFloat(g, tickpos.x, tickpos.y, (double) (i+10)/20*(zmax-zmin)+zmin, Label.RIGHT, CENTER); } } } if(isDisplayGrids&&isBoxed&&(i%t_z==0)) { projection = projector.project(-factor_x*10, -factor_y*10, i); tickpos = projector.project(factor_x*10, -factor_y*10, i); } else { if(i%t_z==0) { projection = projector.project(factor_x*9.5f, -factor_y*10, i); } else { projection = projector.project(factor_x*9.8f, -factor_y*10, i); } tickpos = projector.project(factor_x*10, -factor_y*10, i); } g.drawLine(projection.x, projection.y, tickpos.x, tickpos.y); if(isDisplayZ) { tickpos = projector.project(factor_x*10.5f, -factor_y*10, i); if(i%t_z==0) { if(y_left) { outFloat(g, tickpos.x, tickpos.y, (double) (i+10)/20*(zmax-zmin)+zmin, Label.LEFT, CENTER); } else { outFloat(g, tickpos.x, tickpos.y, (double) (i+10)/20*(zmax-zmin)+zmin, Label.RIGHT, CENTER); } } } if(isDisplayGrids&&isBoxed) { if(i%t_y==0) { projection = projector.project(-factor_x*10, i, -10); tickpos = projector.project(-factor_x*10, i, 10); g.drawLine(projection.x, projection.y, tickpos.x, tickpos.y); } if(i%t_x==0) { projection = projector.project(i, -factor_y*10, -10); tickpos = projector.project(i, -factor_y*10, 10); g.drawLine(projection.x, projection.y, tickpos.x, tickpos.y); } } } } } } } /** * Draws the base plane. The base plane is the x-y plane. * * @param g the graphics context to draw. * @param x used to retrieve x coordinates of drawn plane from this method. * @param y used to retrieve y coordinates of drawn plane from this method. */ private final void drawBase(Graphics g, int[] x, int[] y) { Point projection = projector.project(-10, -10, -10); x[0] = projection.x; y[0] = projection.y; projection = projector.project(-10, 10, -10); x[1] = projection.x; y[1] = projection.y; projection = projector.project(10, 10, -10); x[2] = projection.x; y[2] = projection.y; projection = projector.project(10, -10, -10); x[3] = projection.x; y[3] = projection.y; x[4] = x[0]; y[4] = y[0]; if(plot_mode!=ColorMapper.WIREFRAME) { if(plot_mode==ColorMapper.NORENDER) { g.setColor(Color.lightGray); } else { g.setColor(new Color(192, 220, 192)); } g.fillPolygon(x, y, 4); } g.setColor(Color.black); g.drawPolygon(x, y, 5); } /** * Draws the bounding box of surface. */ private final void drawBoundingBox(Graphics g) { Point startingpoint, projection; startingpoint = projector.project(factor_x*10, factor_y*10, 10); g.setColor(Color.black); projection = projector.project(-factor_x*10, factor_y*10, 10); g.drawLine(startingpoint.x, startingpoint.y, projection.x, projection.y); projection = projector.project(factor_x*10, -factor_y*10, 10); g.drawLine(startingpoint.x, startingpoint.y, projection.x, projection.y); projection = projector.project(factor_x*10, factor_y*10, -10); g.drawLine(startingpoint.x, startingpoint.y, projection.x, projection.y); } /** * Draws string at the specified coordinates with the specified alignment. * * @param g graphics context to draw * @param x the x coordinate * @param y the y coordinate * @param s the string to draw * @param x_align the alignment in x direction * @param y_align the alignment in y direction */ private final void outString(Graphics g, int x, int y, String s, int x_align, int y_align) { switch(y_align) { case TOP : y += g.getFontMetrics(g.getFont()).getAscent(); break; case CENTER : y += g.getFontMetrics(g.getFont()).getAscent()/2; break; } switch(x_align) { case Label.LEFT : g.drawString(s, x, y); break; case Label.RIGHT : g.drawString(s, x-g.getFontMetrics(g.getFont()).stringWidth(s), y); break; case Label.CENTER : g.drawString(s, x-g.getFontMetrics(g.getFont()).stringWidth(s)/2, y); break; } } /** * Draws double at the specified coordinates with the specified alignment. * * @param g graphics context to draw * @param x the x coordinate * @param y the y coordinate * @param f the double to draw * @param x_align the alignment in x direction * @param y_align the alignment in y direction */ private final void outFloat(Graphics g, int x, int y, double f, int x_align, int y_align) { // String s = Double.toString(f); String s = labelFormat.format(f); outString(g, x, y, s, x_align, y_align); } /** * Determines the palette type that will be used. * @param type */ public void setPaletteType(int type) { plot_mode = type; } /** * Sets the format for the axis labels. * * For example, _format=0.000 will produce three digits to the right of decimal point * * @param _format the format string */ public void setLabelFormat(String _format) { labelFormat = new DecimalFormat(_format); } /** * 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); } } } /** * 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; } /** * 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 zmin; } /** * Gets the ceiling for scaling the z data. * @return double */ public double getCeiling() { return zmax; } /** * Sets the floor and ceiling colors. * * @param floorColor * @param ceilColor */ public void setFloorCeilColor(Color floorColor, Color ceilColor) { colorMap.setCeilColor(ceilColor); } /** * 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; } } /** * Updates the surface plot using the current data. */ public synchronized void update() { 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); } } double left = griddata.getLeft(), right = griddata.getRight(), top = griddata.getTop(), bottom = griddata.getBottom(); xmin = Math.min(left, right); xmax = Math.max(left, right); ymin = Math.min(bottom, top); ymax = Math.max(bottom, top); if(griddata instanceof ArrayData) { generateVerticesFromArray((ArrayData) griddata); } else if(griddata instanceof GridPointData) { generateVerticesFromPoints((GridPointData) griddata); } } /** * Translates the view by the specified number of pixels. * * @param xpix the x translation in pixels * @param ypix the y translation in pixels */ public void setTranslation(int xpix, int ypix) { projector.set2DTranslation(xpix, ypix); } /** * Sets the viewing rotation angle. * * @param angle the rotation angle in degrees */ public void setRotationAngle(double angle) { projector.setRotationAngle(angle); } /** * Sets the viewing elevation angle. * * @param angle the elevation angle in degrees */ public void setElevationAngle(double angle) { projector.setElevationAngle(angle); } /** * Sets the viewing distance. * * @param distance the distance */ public void setDistance(double distance) { projector.setDistance(distance); } /** * Sets the 2D scaling factor. * * @param scale the scaling factor */ public void set2DScaling(double scale) { projector.set2DScaling(scale); } /** * <code>mouseDown</code> event handler. Sets internal tracking variables * for dragging operations. * * @param e the event * @param drawingPanel * * @return mouse pressed flag */ public boolean mousePressed(MouseEvent e, DrawingPanel drawingPanel) { click_x = e.getX(); click_y = e.getY(); //mouseDown = true; return true; } /** * Method mouseReleased * * @param e * @param drawingPanel */ public void mouseReleased(MouseEvent e, DrawingPanel drawingPanel) { //mouseDown = false; } /** * <code>mouseDrag<code> event handler. Tracks dragging operations. * Checks the delay regeneration flag and does proper actions. * * @param e the event * @param drawingPanel */ public void mouseDragged(MouseEvent e, DrawingPanel drawingPanel) { double new_value = 0.0; int x = e.getX(); int y = e.getY(); if(e.isControlDown()) { projector.set2D_xTranslation(projector.get2D_xTranslation()+(x-click_x)); projector.set2D_yTranslation(projector.get2D_yTranslation()+(y-click_y)); } else if(e.isShiftDown()) { new_value = projector.get2DScaling()+(y-click_y)*0.5; if(new_value>60.0f) { new_value = 60.0f; } if(new_value<2.0f) { new_value = 2.0f; } projector.set2DScaling(new_value); } else { new_value = projector.getRotationAngle()+(x-click_x); while(new_value>360) { new_value -= 360; } while(new_value<0) { new_value += 360; } projector.setRotationAngle(new_value); new_value = projector.getElevationAngle()+(y-click_y); if(new_value>90) { new_value = 90; } else if(new_value<0) { new_value = 0; } projector.setElevationAngle(new_value); } click_x = x; click_y = y; invalidProjection = true; drawingPanel.render(); } /** * Gets the minimum x needed to draw this object. * @return minimum */ public double getXMin() { return 0; } /** * Gets the maximum x needed to draw this object. * @return maximum */ public double getXMax() { return 0; } /** * Gets the minimum y needed to draw this object. * @return minimum */ public double getYMin() { return 0; } /** * Gets the maximum y needed to draw this object. * @return minimum */ public double getYMax() { return 0; } /** * Determines if information is available to set min/max values. * X y values have no meaning for this plot. * * @return false */ public boolean isMeasured() { return false; } /** * 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 ComplexSurfacePlot(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 */