/*
* 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.display;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.geom.AffineTransform;
import org.opensourcephysics.controls.XML;
import org.opensourcephysics.controls.XMLControl;
import org.opensourcephysics.display.axes.CartesianAxes;
import org.opensourcephysics.display.axes.CartesianInteractive;
import org.opensourcephysics.display.axes.CustomAxes;
import org.opensourcephysics.display.axes.DrawableAxes;
import org.opensourcephysics.display.axes.PolarAxes;
import org.opensourcephysics.display.axes.PolarType2;
import org.opensourcephysics.display.axes.XYAxis;
import org.opensourcephysics.numerics.FunctionTransform;
import org.opensourcephysics.numerics.LogBase10Function;
/**
* A Drawing Panel that has an X axis, a Y axis, and a title.
*
* @author Wolfgang Christian
*/
public class PlottingPanel extends InteractivePanel {
protected DrawableAxes axes;
protected FunctionTransform functionTransform = new FunctionTransform();
protected final static double log10 = Math.log(10);
protected final static LogBase10Function logBase10Function = new LogBase10Function();
/**
* Constructs a new PlottingPanel that uses the given X axis label, Y axis
* label, and plot title.
*
* @param xlabel The X axis label.
* @param ylabel The Y axis label.
* @param plotTitle The plot title.
*/
public PlottingPanel(String xlabel, String ylabel, String plotTitle) {
this(xlabel, ylabel, plotTitle, XYAxis.LINEAR, XYAxis.LINEAR);
}
/**
* Constructs a new PlottingPanel that uses the given X axis type and Y axis
* type.
*
* @param _xAxisType The X axis type.
* @param _yAxisType The Y axis type.
*/
public PlottingPanel(int _xAxisType, int _yAxisType) {
this("x", "y", DisplayRes.getString("PlottingPanel.DefaultTitle"), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
_xAxisType, _yAxisType);
}
/**
* Constructs a new PlottingPanel with cartesian axes that use the given X axis label, Y axis
* label, and plot title.
*
* @param xlabel The X axis label.
* @param ylabel The Y axis label.
* @param plotTitle The plot title.
* @param xAxisType Description of Parameter
* @param yAxisType Description of Parameter
*/
public PlottingPanel(String xlabel, String ylabel, String plotTitle, int xAxisType, int yAxisType) {
// axes = new CartesianType1(this);
// axes changed to interactive by default. D Brown 2012-01-27
axes = new CartesianInteractive(this);
axes.setXLabel(xlabel, null);
axes.setYLabel(ylabel, null);
axes.setTitle(plotTitle, null);
functionTransform.setXFunction(logBase10Function); // set function transforms but do not apply functions
functionTransform.setYFunction(logBase10Function);
if(xAxisType==XYAxis.LOG10) {
logScaleX = true;
}
if(yAxisType==XYAxis.LOG10) {
logScaleY = true;
}
setLogScale(logScaleX, logScaleY);
}
/**
* Gets the interactive drawable that was accessed by the last mouse event.
*
* This methods overrides the default implemenation in order to check for draggable axes.
*
* @return the interactive object
*/
public Interactive getInteractive() {
Interactive iad = null;
iad = super.getInteractive();
if((iad==null)&&(axes instanceof Interactive)) {
// check for draggable axes
iad = ((Interactive) axes).findInteractive(this, mouseEvent.getX(), mouseEvent.getY());
}
return iad;
}
/**
* Gets the axes.
*
* @return the axes
*/
public DrawableAxes getAxes() {
return axes;
}
/**
* Sets the axes.
*
* @param _axes the new axes
*/
public void setAxes(DrawableAxes _axes) {
axes = _axes;
if(axes==null) {
axes = new CustomAxes(this);
setPreferredGutters(0, 0, 0, 0);
setClipAtGutter(false);
axes.setVisible(false);
} else {
setClipAtGutter(true);
}
}
/**
* Converts this panel to polar coordinates
*
* @param plotTitle String
* @param deltaR double
*/
public void setPolar(String plotTitle, double deltaR) {
if(logScaleX||logScaleY) {
System.err.println("The axes type cannot be swithed when using logarithmetic scales."); //$NON-NLS-1$
return;
}
PolarAxes axes = new PolarType2(this);
axes.setDeltaR(deltaR); // radial coordinate separation
axes.setDeltaTheta(Math.PI/8); // spokes are separate by PI/8
setTitle(plotTitle);
setSquareAspect(true);
setClipAtGutter(true);
}
/**
* Converts this panel to cartesian coordinates.
*
*
* @param xLabel String
* @param yLabel String
* @param plotTitle String
*/
public void setCartesian(String xLabel, String yLabel, String plotTitle) {
// axes = new CartesianType1(this);
// axes changed to interactive by default. D Brown 2012-01-27
axes = new CartesianInteractive(this);
axes.setXLabel(xLabel, null);
axes.setYLabel(yLabel, null);
axes.setTitle(plotTitle, null);
setClipAtGutter(true);
}
/**
* Sets the label for the X (horizontal) axis.
*
* @param label the label
*/
public void setXLabel(String label) {
axes.setXLabel(label, null);
}
/**
* Sets the label for the Y (vertical) axis.
*
* @param label the label
*/
public void setYLabel(String label) {
axes.setYLabel(label, null);
}
/**
* Sets the title.
*
* @param title the title
*/
public void setTitle(String title) {
axes.setTitle(title, null);
}
/**
* Sets the label and font for the X (horizontal) axis.
* If the font name is null, the font is unchanged.
*
* @param label the label
* @param font_name the optional new font
*/
public void setXLabel(String label, String font_name) {
axes.setXLabel(label, font_name);
}
/**
* Sets the label and font for the Y (vertical) axis.
* If the font name is null, the font is unchanged.
*
* @param label the label
* @param font_name the optional new font
*/
public void setYLabel(String label, String font_name) {
axes.setYLabel(label, font_name);
}
/**
* Sets the title and font.
* If the font name is null, the font is unchanged.
*
* @param title
* @param font_name the optional new font
*/
public void setTitle(String title, String font_name) {
axes.setTitle(title, font_name);
}
/**
* Sets the visibility of the axes.
* Axes that are not visible will not be drawn.
*
* @param isVisible
*/
public void setAxesVisible(boolean isVisible) {
axes.setVisible(isVisible);
}
/**
* Sets Cartesian axes to log scale.
* @param _logScaleX The new logScale value
* @param _logScaleY The new logScale value
*/
public void setLogScale(boolean _logScaleX, boolean _logScaleY) {
if(axes instanceof CartesianAxes) {
((CartesianAxes) axes).setXLog(_logScaleX);
logScaleX = _logScaleX;
} else {
logScaleX = false;
}
if(axes instanceof CartesianAxes) {
((CartesianAxes) axes).setYLog(_logScaleY);
logScaleY = _logScaleY;
} else {
logScaleY = false;
}
}
/**
* Sets Cartesian x-axes to log scale.
* @param _logScaleX The new logScale value
*/
public void setLogScaleX(boolean _logScaleX) {
if(axes instanceof CartesianAxes) {
((CartesianAxes) axes).setXLog(_logScaleX);
logScaleX = _logScaleX;
} else {
logScaleX = false;
}
}
/**
* Sets Cartesian axes to log scale.
* @param _logScaleY The new logScale value
*/
public void setLogScaleY(boolean _logScaleY) {
if(axes instanceof CartesianAxes) {
((CartesianAxes) axes).setYLog(_logScaleY);
logScaleY = _logScaleY;
} else {
logScaleY = false;
}
}
/**
* Computes the size of the gutters using a Dimensioned object.
*
* Gutters are usually set by the axes to insure that there is enough space for axes labels. Other objects
* can, however, perform this function by implementing the Dimensioned interface.
*/
protected void computeGutters() {
resetGutters();
Dimension interiorDimension = null;
// dimensionSetter specifies the size of the drawable area
if(dimensionSetter!=null) {
interiorDimension = dimensionSetter.getInterior(this);
}
// give the axes a chance to set the gutters and the dimension
if(axes instanceof Dimensioned) {
Dimension axesInterior = ((Dimensioned) axes).getInterior(this);
if(axesInterior!=null) {
interiorDimension = axesInterior;
}
}
if(interiorDimension!=null) { // use the dimensionSetter to set the gutters
squareAspect = false;
adjustableGutter = false;
leftGutter = rightGutter = Math.max(0, getWidth()-interiorDimension.width)/2;
topGutter = bottomGutter = Math.max(0, getHeight()-interiorDimension.height)/2;
}
}
/**
* Paints before the panel iterates through its list of Drawables.
* @param g Graphics
*/
protected void paintFirst(Graphics g) {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight()); // fill the component with the background color
g.setColor(Color.black); // restore the default drawing color
if((leftGutterPreferred>0)||(topGutterPreferred>0)||(rightGutterPreferred>0)||(bottomGutterPreferred>0)) {
axes.draw(this, g); // draw the axes
}
}
/**
* Converts pixel to x world units.
*
* @param pix
* @return x panel units
*/
public double pixToX(int pix) {
if(logScaleX) {
return Math.pow(10, super.pixToX(pix));
}
return super.pixToX(pix);
}
/**
* Converts x from world to pixel units.
*
* @param x
* @return the pixel value of the x coordinate
*/
public int xToPix(double x) {
if(logScaleX) {
if(x<=0) {
x = Math.max(Float.MIN_VALUE, xmin);
}
return super.xToPix(logBase10(x));
}
return super.xToPix(x);
}
/**
* Converts x from world to graphics device units.
* @param x
* @return the graphics device value of the x coordinate
*/
public float xToGraphics(double x) {
if(logScaleX) {
if(x<=0) {
x = Math.max(Float.MIN_VALUE, xmin);
}
return super.xToGraphics(logBase10(x));
}
return super.xToGraphics(x);
}
/**
* Converts pixel to x world units.
*
* @param pix
* @return x panel units
*/
public double pixToY(int pix) {
if(logScaleY) {
return Math.pow(10, super.pixToY(pix));
}
return super.pixToY(pix);
}
/**
* Converts y from world to pixel units.
*
* @param y
* @return the pixel value of the y coordinate
*/
public int yToPix(double y) {
if(logScaleY) {
if(y<=0) {
y = Math.max(Float.MIN_VALUE, ymin);
}
return super.yToPix(logBase10(y));
}
return super.yToPix(y);
}
/**
* Converts y from world to graphics device units.
* @param y
* @return the graphics device value of the x coordinate
*/
public float yToGraphics(double y) {
if(logScaleY) {
if(y<=0) {
y = Math.max(Float.MIN_VALUE, ymin);
}
return super.yToGraphics(logBase10(y));
}
return super.yToGraphics(y);
}
/**
* Gets the bottom gutter of this DrawingPanel.
*
* @return bottom gutter
*/
public int getBottomGutter() {
return Math.max(bottomGutter, bottomGutterPreferred);
}
/**
* Gets the bottom gutter of this DrawingPanel.
*
* @return right gutter
*/
public int getTopGutter() {
return Math.max(topGutter, topGutterPreferred);
}
/*
* TO DO: Fix setPixelScale-what to do if min or max is 0 and using log scale
*/
/**
* Calculates min and max values and the affine transformation based on the
* current size of the panel and the squareAspect boolean.
*/
public void setPixelScale() {
xmin = xminPreferred; // start with the preferred values.
xmax = xmaxPreferred;
ymin = yminPreferred;
ymax = ymaxPreferred;
if((dimensionSetter==null)) { // gutters have not been set by dimension setter
leftGutter = Math.max(leftGutter, leftGutterPreferred); // no smaller than preferred gutters
topGutter = Math.max(topGutter, topGutterPreferred);
rightGutter = Math.max(rightGutter, rightGutterPreferred);
bottomGutter = Math.max(bottomGutter, bottomGutterPreferred);
}
if(logScaleX) {
xmin = logBase10(Math.max(xmin, 1.0e-30));
xmax = logBase10(Math.max(xmax, 1.0e-30));
if(xmin==0) { // FIX_ME
xmin = 0.00000001;
}
if(xmax==0) { // FIX_ME
xmax = Math.max(xmin+0.00000001, 0.00000001);
}
}
if(logScaleY) {
ymin = logBase10(Math.max(ymin, 1.0e-30));
ymax = logBase10(Math.max(ymax, 1.0e-30));
if(ymin==0) { // FIX_ME
ymin = 0.00000001;
}
if(ymax==0) { // FIX_ME
ymax = Math.max(ymin+0.00000001, 0.00000001);
}
}
width = getWidth();
height = getHeight();
if(fixedPixelPerUnit) { // the user has specified a fixed pixel scale
xmin = (xmaxPreferred+xminPreferred)/2-Math.max(width-leftGutter-rightGutter-1, 1)/xPixPerUnit/2;
xmax = (xmaxPreferred+xminPreferred)/2+Math.max(width-leftGutter-rightGutter-1, 1)/xPixPerUnit/2;
ymin = (ymaxPreferred+yminPreferred)/2-Math.max(height-bottomGutter-topGutter-1, 1)/yPixPerUnit/2;
ymax = (ymaxPreferred+yminPreferred)/2+Math.max(height-bottomGutter-topGutter-1, 1)/yPixPerUnit/2;
functionTransform.setTransform(xPixPerUnit, 0, 0, -yPixPerUnit, -xmin*xPixPerUnit+leftGutter, ymax*yPixPerUnit+topGutter);
functionTransform.setApplyXFunction(false);
functionTransform.setApplyYFunction(false);
functionTransform.getMatrix(pixelMatrix); // puts the transformation into the pixel matrix
return;
}
xPixPerUnit = (width-leftGutter-rightGutter)/(xmax-xmin);
yPixPerUnit = (height-bottomGutter-topGutter)/(ymax-ymin); // the y scale in pixels
if(squareAspect&&!adjustableGutter) {
double stretch = Math.abs(xPixPerUnit/yPixPerUnit);
if(stretch>=1) { // make the x range bigger so that aspect ratio is one
stretch = Math.min(stretch, width); // limit the stretch
xmin = xminPreferred-(xmaxPreferred-xminPreferred)*(stretch-1)/2.0;
xmax = xmaxPreferred+(xmaxPreferred-xminPreferred)*(stretch-1)/2.0;
xPixPerUnit = (width-leftGutter-rightGutter)/(xmax-xmin); // the x scale in pixels per unit
} else { // make the y range bigger so that aspect ratio is one
stretch = Math.max(stretch, 1.0/height); // limit the stretch
ymin = yminPreferred-(ymaxPreferred-yminPreferred)*(1.0/stretch-1)/2.0;
ymax = ymaxPreferred+(ymaxPreferred-yminPreferred)*(1.0/stretch-1)/2.0;
yPixPerUnit = (height-bottomGutter-topGutter)/(ymax-ymin); // the y scale in pixels per unit
}
}
if(squareAspect&&adjustableGutter) { // axis min-max do not change but gutters change
if(Math.abs(xPixPerUnit/yPixPerUnit)>=1) { // x range is smaller so make the x gutters bigger
xPixPerUnit = yPixPerUnit;
float gutter = (width-(float) Math.abs((xmax-xmin)*xPixPerUnit));
leftGutter = (int) (gutter/2.0f+leftGutterPreferred-rightGutterPreferred+0.5f);
rightGutter = (int) (gutter-leftGutter-0.5);
leftGutter = Math.max(0, leftGutter);
rightGutter = Math.max(0, rightGutter);
} else { // make the y gutters bigger
yPixPerUnit = xPixPerUnit;
float gutter = height-(float) Math.abs((ymax-ymin)*yPixPerUnit);
topGutter = (int) (gutter/2.0f+topGutterPreferred-bottomGutterPreferred+0.5f);
bottomGutter = (int) (gutter-topGutter);
topGutter = Math.max(0, topGutter);
bottomGutter = Math.max(0, bottomGutter);
}
}
functionTransform.setTransform(xPixPerUnit, 0, 0, -yPixPerUnit, -xmin*xPixPerUnit+leftGutter, ymax*yPixPerUnit+topGutter);
if(logScaleX) {
functionTransform.setApplyXFunction(true);
} else {
functionTransform.setApplyXFunction(false);
}
if(logScaleY) {
functionTransform.setApplyYFunction(true);
} else {
functionTransform.setApplyYFunction(false);
}
functionTransform.getMatrix(pixelMatrix);
}
/**
* Recomputes the pixel transformation based on the current minimum and maximum values and the gutters.
*/
public void recomputeTransform() {
xPixPerUnit = Math.max(width-leftGutter-rightGutter, 1)/(xmax-xmin);
yPixPerUnit = Math.max(height-bottomGutter-topGutter, 1)/(ymax-ymin); // the y scale in pixels
functionTransform.setTransform(xPixPerUnit, 0, 0, -yPixPerUnit, -xmin*xPixPerUnit+leftGutter, ymax*yPixPerUnit+topGutter);
if(logScaleX) {
functionTransform.setApplyXFunction(true);
} else {
functionTransform.setApplyXFunction(false);
}
if(logScaleY) {
functionTransform.setApplyYFunction(true);
} else {
functionTransform.setApplyYFunction(false);
}
functionTransform.getMatrix(pixelMatrix);
}
/**
* Gets the affine transformation that converts from world to pixel coordinates.
* @return the affine transformation
*/
public AffineTransform getPixelTransform() {
return(AffineTransform) functionTransform.clone();
}
/**
* Method logBase10
*
* @param x
*
* @return the log
*/
static double logBase10(double x) {
return Math.log(x)/log10;
}
/**
* Returns an XML.ObjectLoader to save and load object data.
*
* @return the XML.ObjectLoader
*/
public static XML.ObjectLoader getLoader() {
return new PlottingPanelLoader();
}
/**
* A class to save and load PlottingPanel data.
*/
static class PlottingPanelLoader extends DrawingPanelLoader {
/**
* Saves PlottingPanel data in an XMLControl.
*
* @param control the control
* @param obj the DrawingPanel to save
*/
public void saveObject(XMLControl control, Object obj) {
PlottingPanel panel = (PlottingPanel) obj;
control.setValue("title", panel.axes.getTitle()); //$NON-NLS-1$
control.setValue("x axis label", panel.axes.getXLabel()); //$NON-NLS-1$
control.setValue("y axis label", panel.axes.getYLabel()); //$NON-NLS-1$
super.saveObject(control, obj);
}
/**
* Creates a PlottingPanel.
*
* @param control the control
* @return the newly created panel
*/
public Object createObject(XMLControl control) {
String title = control.getString("title"); //$NON-NLS-1$
String xlabel = control.getString("x axis label"); //$NON-NLS-1$
String ylabel = control.getString("y axis label"); //$NON-NLS-1$
return new PlottingPanel(xlabel, ylabel, title);
}
/**
* Loads a PlottingPanel with data from an XMLControl.
*
* @param control the control
* @param obj the object
* @return the loaded object
*/
public Object loadObject(XMLControl control, Object obj) {
PlottingPanel panel = (PlottingPanel) obj;
panel.setTitle(control.getString("title")); //$NON-NLS-1$
panel.setXLabel(control.getString("x axis label")); //$NON-NLS-1$
panel.setYLabel(control.getString("y axis label")); //$NON-NLS-1$
super.loadObject(control, obj);
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
*/