/* ==============================================
* Simtools : The tools library used in JSynoptic
* ==============================================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This library is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This library 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 1999-2003, by :
* Corporate:
* Astrium SAS
* EADS CRC
* Individual:
* Claude Cazenave
* Nicolas Brodu
*
*
* $Id: CurveShape.java,v 1.39 2008/11/27 11:23:29 ogor Exp $
*
* Changes
* -------
* 25-Sep-2003 : Initial public release (NB);
*
*/
package simtools.shapes;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.text.NumberFormat;
import simtools.data.DataException;
import simtools.data.DataInfo;
import simtools.data.DataSource;
import simtools.data.DataSourceListener;
import simtools.data.DataSourcePool;
import simtools.data.NoSuchIndex;
import simtools.data.UnsupportedOperation;
import simtools.shapes.AbstractShape.AbstractShapePropertiesNames;
import simtools.ui.PlotInformationDialog;
import simtools.util.ListenerManager;
public class CurveShape implements Shape, Cloneable, java.io.Serializable, DataSourceListener {
protected static final long serialVersionUID = 3969204429218131993L;
protected static final int MAXSTEPS = 4000;
protected transient DataSource xSource;
protected transient DataSource ySource;
protected long min;
protected long max;
protected transient double orgx;
protected transient double orgy;
protected transient double maxx;
protected transient double maxy;
protected transient double scalex;
protected transient double scaley;
protected transient boolean useCache;
protected transient double[] xCache;
protected transient double[] yCache;
protected transient double[] ySourceCache;
protected transient ListenerManager listeners = new ListenerManager();
protected boolean drawNewOnly;
protected transient long iteratorMinIndex;
protected transient long iteratorMaxIndex;
protected transient boolean xSourceChanged, ySourceChanged;
protected transient long xSourceNewStartIndex, xSourceNewLastIndex, ySourceNewStartIndex, ySourceNewLastIndex;
protected transient long lastDrawnIndex;
protected transient boolean logx, logy;
/**The value of ln(10), used to compute LOG10_VALUE*/
public static final double LOG10_VALUE = Math.log(10);
/** Smallest arbitrarily-close-to-zero value allowed. */
public static final double LOG_MIN = 1e-1;
public static final int SLOP_LENGTH = 25;
// Statistics on current range
/** Display the specified number of digits in the fraction portion of all curve statistics */
public static int STATISTIC_ACCURACY = 10;
/** Display the curve X minimum value in the curve statistic */
public static boolean SHOW_X_MIN = false;
/** Display the curve X maximum value in the curve statistic */
public static boolean SHOW_X_MAX = false;
/** Display the curve Y minimum value in the curve statistic */
public static boolean SHOW_Y_MIN = true;
/** Display the curve Y maximum value in the curve statistic */
static public boolean SHOW_Y_MAX = true;
/** Display the curve number of points in the curve statistic */
public static boolean SHOW_POINT_NUMBER = true;
/** Display the curve mean in the curve statistic */
public static boolean SHOW_MEAN = false;
/** Display the curve deviation in the curve statistic */
public static boolean SHOW_DEVIATION = false;
/** Display the curve integral in the curve statistic */
public static boolean SHOW_INTEGRAL = false;
protected transient long localRangeNbPoints;
protected transient CurvePoint localRangeMaxPoint;
protected transient CurvePoint localRangeMinPoint;
protected transient long localMinIndex;
protected transient long localMaxIndex;
/** show curve points by drawing a circle on each point position */
protected transient boolean showPoints;
/** Display the curve values using vertical lines */
protected transient boolean drawBars;
/** Local mean: */
protected transient double localRangeMean;
/** Local integral: */
protected transient double localRangeIntegral;
/** Local standard deviation: */
protected transient double localRangeDeviation;
// Curve point the mouse aims at
protected transient CurvePoint currentPoint;
// Curve point defined as reference
protected transient CurvePoint referencePoint;
public CurveShape(){
xSource=null;
ySource=null;
min=-1;
max=-1;
iteratorMinIndex = -1;
iteratorMaxIndex = -1;
xSourceChanged = ySourceChanged = false;
xSourceNewStartIndex = xSourceNewLastIndex = ySourceNewStartIndex = ySourceNewLastIndex = -1;
drawNewOnly = false;
showPoints = false;
drawBars = false;
useCache=false;
xCache=new double[2*MAXSTEPS];
yCache=new double[2*MAXSTEPS];
ySourceCache = new double[2*MAXSTEPS];
localRangeNbPoints = 0;
localRangeMaxPoint = new CurvePoint();
localRangeMinPoint = new CurvePoint();
currentPoint = new CurvePoint();
referencePoint = null;
}
public CurveShape(DataSource xSource, DataSource ySource) {
this();
setData(xSource, ySource);
}
public void setShowPoints(boolean showPoints) {
this.showPoints = showPoints;
if (showPoints && drawBars){
drawBars = false;
}
}
public void setDrawBars(boolean drawBars) {
this.drawBars = drawBars;
if (showPoints && drawBars){
showPoints = false;
}
}
public void setData(DataSource xSource, DataSource ySource){
if (this.xSource!=null){
this.xSource.removeListener(this);
}
if (this.ySource!=null){
this.ySource.removeListener(this);
}
this.xSource = xSource;
this.ySource = ySource;
this.xSource.addListener(this);
this.ySource.addListener(this);
useCache=false;
}
public DataSource getXSource() throws DataException{
if (xSource==null)
throw new DataException();
return xSource;
}
public DataSource getYSource() throws DataException{
if (ySource==null)
throw new DataException();
return ySource;
}
public void setSlice(long min, long max){
if(min!=min || max!=max){
useCache=false;
}
this.min = min;
this.max = max;
if (xSource != null){
xSource.setSlice(min, max);
}
if (ySource != null){
ySource.setSlice(min, max);
}
}
// Shape Interface
public boolean contains(double x, double y){
return getBounds2D().contains(x,y);
}
public boolean contains(double x, double y, double w, double h){
return getBounds2D().contains(x,y,w,h);
}
public boolean contains(Point2D p){
return getBounds2D().contains(p);
}
public boolean contains(Rectangle2D r){
return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight());
}
public boolean intersects(double x, double y, double w, double h){
return getBounds2D().intersects(x,y,w,h);
}
public boolean intersects(Rectangle2D r){
return getBounds2D().intersects(r);
}
public Rectangle getBounds(){
Rectangle r=new Rectangle();
r.setRect(getBounds2D());
return r;
}
public Rectangle2D getBounds2D(){
if((ySource==null)||(xSource==null)){
return new Rectangle2D.Double(0.,0.,0.,0.);
}
else{
try {
return new Rectangle2D.Double(xSource.getDoubleMin(), ySource.getDoubleMin(),
xSource.getDoubleMax()-xSource.getDoubleMin(),
ySource.getDoubleMax()-ySource.getDoubleMin());
} catch (UnsupportedOperation uop) {
} catch (DataException e) {
}
return new Rectangle2D.Double(-Double.MAX_VALUE/2, -Double.MAX_VALUE/2, Double.MAX_VALUE, Double.MAX_VALUE);
}
}
/**
* @param g2 - the current graphics
* @param ox
* @param oy
* @param sx
* @param sy
*/
public void drawReferencePoint(Graphics2D g2, double ox, double oy, double sx, double sy){
if (referencePoint != null) {
Color oldColor = g2.getColor();
Stroke oldStroke = g2.getStroke();
// Get current point coordinates in the graphical frame
int posX, posY;
if (logx) {
if(referencePoint.x < LOG_MIN){
referencePoint.x= LOG_MIN;
}
posX = (int)Math.round(((Math.log(referencePoint.x)/LOG10_VALUE - ox) * sx));
} else {
posX = (int)Math.round(((referencePoint.x - ox) * sx));
}
if (logy) {
if(referencePoint.y < LOG_MIN){
referencePoint.y= LOG_MIN;
}
posY = (int)Math.round( ((oy - Math.log(referencePoint.y)/LOG10_VALUE) * sy));
} else {
posY= (int)Math.round(((oy - referencePoint.y) * sy));
}
// draw current point
g2.setStroke(new BasicStroke());
g2.setColor(new Color(0,100,0)); //dark green
g2.fillOval(posX - 6, posY - 6, 12, 12);
g2.setColor(Color.WHITE);
g2.fillOval(posX - 5, posY - 5, 10, 10);
g2.setColor(new Color(0,100,0)); //dark green
g2.fillOval(posX - 3, posY - 3, 6, 6);
// Restore old preferences
g2.setColor(oldColor);
g2.setStroke(oldStroke);
}
}
/**
* Draws the computed magnetized point into the graphic
* @param g2 the current graphics
*/
public void drawMagnetizedPoint(Graphics2D g2, double ox, double oy, double sx, double sy){
Color oldColor = g2.getColor();
Stroke oldStroke = g2.getStroke();
// Get current point coordinates in the graphical frame
int posX, posY;
if (logx) {
if(currentPoint.x < LOG_MIN){
currentPoint.x= LOG_MIN;
}
posX = (int)Math.round(((Math.log(currentPoint.x)/LOG10_VALUE - ox) * sx));
} else {
posX = (int)Math.round(((currentPoint.x - ox) * sx));
}
if (logy) {
if(currentPoint.y < LOG_MIN){
currentPoint.y= LOG_MIN;
}
posY = (int)Math.round( ((oy - Math.log(currentPoint.y)/LOG10_VALUE) * sy));
} else {
posY= (int)Math.round(((oy - currentPoint.y) * sy));
}
// draw current point
g2.setColor(Color.RED);
g2.setStroke(new BasicStroke());
g2.fillOval(posX - 5, posY - 5, 10, 10);
// Display slope
double slopeX = currentPoint.slop_x * sx;
double slopeY = currentPoint.slop_y * sy;
// Normalize slope vector
double lenth = Math.sqrt(slopeX*slopeX + slopeY*slopeY);
slopeX = (slopeX/lenth) * SLOP_LENGTH;
slopeY = (slopeY/lenth) * SLOP_LENGTH;
int slopeEndposX = (int)Math.round(posX + slopeX );
int slopeEndposY = (int)Math.round(posY -slopeY);
g2.setStroke(new BasicStroke(2));
g2.draw(new Line2D.Double(posX, posY, slopeEndposX, slopeEndposY));
// Draw arrow
double theta = Math.atan2((slopeEndposY-posY),(slopeEndposX-posX));
AffineTransform affineTransform = new AffineTransform();
affineTransform.setToTranslation(slopeEndposX, slopeEndposY);
affineTransform.rotate(theta);
Polygon first = new Polygon(new int[] {-5,0,-5},new int[] {2,0,-2},3);
Shape firstArrow = affineTransform.createTransformedShape(first);
g2.fill(firstArrow);
g2.draw(firstArrow);
// Restore old preferences
g2.setColor(oldColor);
g2.setStroke(oldStroke);
}
/**
* Method <b>setLogsProperties</b>
* <br><b>Summary:</b><br>
* Use this method to set logarithmic modes on curve axis.
* Parameters:
* @param _logx True to use logarithmic mode on x axis. False otherwise.
* @param _logy True to use logarithmic mode on y axis. False otherwise.
*
*/
public void setLogsProperties(boolean _logx, boolean _logy){
logx=_logx;
logy=_logy;
}
/**
* Draws it
* @param g2 the current graphics
*/
public void draw(Graphics2D g2, double ox, double oy, double mx, double my, double sx, double sy, int height) {
if(orgx!=ox || orgy!=oy || maxx!=mx || maxy!=my || scalex!=sx || scaley!=sy){
useCache=false;
}
orgx=ox;
orgy=oy;
maxx=mx;
maxy=my;
scalex=sx;
scaley=sy;
if (drawBars){
PathIterator p=getPathIterator(null);
double[] v=new double[2];
double currentValue=Double.NEGATIVE_INFINITY;
if (p instanceof CurveIterator){
CurveIterator cu = (CurveIterator)p;
while(!cu.isDone()){
cu.currentSegment(v);
if (cu.currentSegmentIsValid){
int xpos = (int)Math.round(v[0]);
g2.drawLine(xpos, 0, xpos, -height);
// display a label on the bar
double value = cu.currentYSourceValue;
if (value!=currentValue){
currentValue=value;
String ySourceValue = new Double(currentValue).toString();
LabelShape label = new LabelShape(ySourceValue,xpos, -height, LabelShape.RIGHTUP, true);
label.draw(g2);
}
}
cu.next();
}
}
}else{
g2.draw(this);
if(showPoints){
Color oldColor = g2.getColor();
float[] hsbs = new float[3];
Color.RGBtoHSB(oldColor.getRed(), oldColor.getGreen(), oldColor.getBlue(), hsbs);
float bb=(float)((hsbs[2]+0.5)%1.0);
Color newColor = Color.getHSBColor(hsbs[0], hsbs[1], bb);
g2.setColor(newColor);
Stroke oldStroke = g2.getStroke();
g2.setStroke(new BasicStroke());
PathIterator p=getPathIterator(null);
if (p instanceof CurveIterator){
CurveIterator cu = (CurveIterator)p;
double[] v=new double[2];
while(!cu.isDone()){
cu.currentSegment(v);
if (cu.currentSegmentIsValid){
g2.drawOval((int)Math.round(v[0]-2.),(int)Math.round(v[1]-2.), 4, 4);
}
cu.next();
}
}
g2.setColor(oldColor);
g2.setStroke(oldStroke);
}
}
}
public PathIterator getPathIterator(AffineTransform at){
if((ySource==null)||(xSource==null)){
return new NoDrawIterator();
}
return new CurveIterator(at,orgx,orgy,maxx, maxy, scalex,scaley);
}
public PathIterator getPathIterator(AffineTransform at, double flatness){
if((ySource==null)||(xSource==null)){
return new NoDrawIterator();
}
return new CurveIterator(at,orgx,orgy,maxx, maxy, scalex,scaley);
}
// Shape Interface end
public class NoDrawIterator implements PathIterator {
public int currentSegment(double[] coords){
coords[0] = 0.0;
coords[1] = 0.0;
return SEG_MOVETO;
}
public int currentSegment(float[] coords){
coords[0] = 0.0f;
coords[1] = 0.0f;
return SEG_MOVETO;
}
public int getWindingRule(){
return WIND_NON_ZERO;
}
public boolean isDone() {
return true;
}
public void next(){
}
}
protected AffineTransform _aff;
public class CurveIterator implements PathIterator {
protected long _index;
protected long _maxi;
protected long _mini;
protected long _indexStep;
protected double _ox;
protected double _oy;
protected double _sx;
protected double _sy;
protected double _mx;
protected double _my;
protected double[] currentValue;
protected double currentYSourceValue;
/** If current segment is invalid: move to next segment */
protected boolean currentSegmentIsValid;
/** currentValue is computed */
protected boolean currentValueIsComputed;
protected boolean odd;
protected double optimizedValue;
protected boolean firstPoint;
protected int iCache;
protected boolean _useCache;
public CurveIterator(AffineTransform aff, double ox, double oy,
double mx, double my,
double sx, double sy) {
_aff=aff;
_ox=ox;
_oy=oy;
_mx=mx;
_my=my;
_sx=sx;
_sy=sy;
odd=true;
iCache=0;
if (iteratorMinIndex != -1) _mini = _index = iteratorMinIndex;
else try {
long minx = xSource.getStartIndex();
long miny = ySource.getStartIndex();
// use greater index as min => no out of bounds
_mini=_index = (minx > miny) ? minx : miny;
if (min > _mini) _mini = _index = min;
} catch (UnsupportedOperation uop1) {
_mini = 0;
_index = 0;
}
if (iteratorMaxIndex != -1) _maxi = iteratorMaxIndex;
else try {
long maxx = xSource.getLastIndex();
long maxy = ySource.getLastIndex();
// use smaller index as max => no out of bounds
_maxi = (maxx < maxy) ? maxx : maxy;
if ((max>=0) && (max < _maxi)) _maxi = max;
} catch (UnsupportedOperation uop2) {
_maxi = -1;
}
if(xSource.sortedOrder()!=0 && _maxi>_mini ){
// optimize in case of zoom
boolean computeStep = (_maxi-_mini)>MAXSTEPS;
try {
double ds=xSource.getDoubleValue(_mini);
double dl=xSource.getDoubleValue(_maxi);
double ln2 = Math.log(2);
if ((ds<_ox) && computeStep){
long s = (_maxi-_mini) / ( MAXSTEPS<100? 1 : MAXSTEPS/100);
long min = _mini;
long max =_maxi;
long pivot;
long nbIteration = (int)Math.ceil((Math.log(s) /ln2));
for(int i =0;i<nbIteration;i++){
pivot = (max-min) /2 + min;
double rr = xSource.getDoubleValue(pivot);
if (rr<=_ox){
min = pivot;
}else{
max = pivot;
}
}
if(min>_mini && min<_maxi && xSource.getDoubleValue(min)<=_ox){
_index=_mini=min;
}
}
if ((dl>_mx) && computeStep) {
long s = (_maxi-_mini) / ( MAXSTEPS<100? 1 : MAXSTEPS/100);
long min = _mini;
long max =_maxi;
long pivot;
long nbIteration = (int)Math.ceil((Math.log(s) /ln2));
for(int i =0;i<nbIteration;i++){
pivot = (max-min) /2 + min;
double rr = xSource.getDoubleValue(pivot);
if (rr<=_mx){
min = pivot;
}else{
max = pivot;
}
}
if(max>_mini && max<_maxi && xSource.getDoubleValue(max)>=_mx){
_maxi=max;
}
}
} catch (DataException e) {
}
}
_indexStep=1;
if(_maxi!=-1 && _mini!=-1){
if((_maxi-_mini)>MAXSTEPS){
// we need to keep at least two points
// one for the min and one for the max
// benefits of this optimisation are
// taken into account if we suppress
// at least half of the points
// thus the step is 2*2=4
_indexStep=(_maxi-_mini)/MAXSTEPS;
if(_indexStep>=4){
//System.out.println("Optim indexStep = "+_indexStep);
}
else{
// not enough benefits, go back
// to nominal computation
_indexStep=1;
}
}
}
currentValue = new double[2];
currentValueIsComputed = false;
currentSegmentIsValid = false;
firstPoint = true;
}
/**
* Tries to compute the current values.
* <ul>
* <li>Gets the data sources values for the current index, if they exist.
* <li>Check if data values are valid.
* <li>Apply coordinate conversions and affine transforms.
*/
protected void computeValues() {
try {
updateCurrentValueValue(xSource.getDoubleValue(_index), true, ySource.getDoubleValue(_index), true);
if (_aff!=null) {
_aff.transform(currentValue,0,currentValue,0,1);
}
currentSegmentIsValid = true;
} catch (NoSuchIndex nsi) {
} catch (DataException de) {
de.printStackTrace();
throw new java.util.NoSuchElementException(de.toString());
}
}
/**
* Method <b>updateCurrentValueValue</b>
* <br><b>Summary:</b><br>
* Use this method to update the current Value.
* Using the log property, and scale if needed.
*
*/
protected void updateCurrentValueValue(double x, boolean updateX, double y, boolean updateY){
if (updateX) {
if (logx) {
if(x < LOG_MIN){
x= LOG_MIN;
}
currentValue[0] = (Math.log(x)/LOG10_VALUE - _ox) * _sx;
} else {
currentValue[0] = (x - _ox) * _sx;
}
}
if (updateY) {
currentYSourceValue = y;
if (logy) {
if(y < LOG_MIN){
y= LOG_MIN;
}
currentValue[1] = (_oy - Math.log(y)/LOG10_VALUE) * _sy;
} else {
currentValue[1] = (_oy - y) * _sy;
}
}
}
// Common to the 2 others
public int currentSegment(){
// check user specified bounds
if ((_maxi!=-1) && (_index>_maxi)) {
firstPoint = true; // restart from the next valid point
return SEG_MOVETO;
}
// try to get values for current index
if (!currentValueIsComputed){
computeValues();
}
if (!currentSegmentIsValid) {
firstPoint = true; // restart from the next valid point
return SEG_MOVETO;
}
// Detects NaN. Spec says x!=x is true if and only if x is NaN
if ((currentValue[0]!=currentValue[0]) || (currentValue[1]!=currentValue[1])) {
firstPoint = true; // restart from the next valid point
return SEG_MOVETO; // Move to nowhere...
}
if (firstPoint) {
firstPoint = false;
return SEG_MOVETO;
}
return SEG_LINETO;
}
// PathIterator Interface
public int currentSegment(double[] coords){
int ret = currentSegment();
coords[0] = currentValue[0];
coords[1] = currentValue[1];
return ret;
}
public int currentSegment(float[] coords){
int ret = currentSegment();
coords[0] = (float)currentValue[0];
coords[1] = (float)currentValue[1];
return ret;
}
public int getWindingRule(){
return WIND_NON_ZERO;
}
public boolean isDone() {
boolean res;
// if user specified a range, use it
if (_maxi != -1) {
res = _index > _maxi;
} else {
// otherwise, try to get new values and see what happens
if (!currentValueIsComputed) {
computeValues();
}
res = !currentValueIsComputed; // On success, they exist => not done yet
}
return res;
}
public void next(){
currentSegmentIsValid = false;
if(_indexStep>1) {
iCache++;
if(useCache){
currentValue[0]=xCache[iCache];
currentValue[1]=yCache[iCache];
if (_aff!=null) _aff.transform(currentValue,0,currentValue,0,1);
_index+=_indexStep;
currentSegmentIsValid = true;
} else {
long k=_index+_indexStep;
odd=!odd;
if(k<=_maxi){ // if _indexStep > 1 _maxi>0
if(odd){
try {
double min=ySource.getDoubleValue(_index);
double max=min;
double v;
for(long i=_index+1;i<k;i++){
// assume x is sorted_xSource.getDoubleValue(i);
v=ySource.getDoubleValue(i);
if(v<min) min=v;
if(v>max) max=v;
}
if(ySource.sortedOrder()>=0){
v=min;
optimizedValue=max;
}
else{
v=max;
optimizedValue=min;
}
updateCurrentValueValue(xSource.getDoubleValue(_index), true, v, true);
xCache[iCache]=currentValue[0];
yCache[iCache]=currentValue[1];
ySourceCache[iCache]=currentYSourceValue;
if (_aff!=null) _aff.transform(currentValue,0,currentValue,0,1);
currentSegmentIsValid = true;
} catch (NoSuchIndex nsi) {
} catch (DataException de) {
de.printStackTrace();
throw new java.util.NoSuchElementException(de.toString());
}
}
else{
try {
updateCurrentValueValue(xSource.getDoubleValue(_index), true, optimizedValue, true);
xCache[iCache]=currentValue[0];
yCache[iCache]=currentValue[1];
ySourceCache[iCache]=currentYSourceValue;
if (_aff!=null) _aff.transform(currentValue,0,currentValue,0,1);
currentSegmentIsValid = true;
} catch (DataException e) {
e.printStackTrace();
throw new java.util.NoSuchElementException(e.toString());
}
}
} else {
// end is reached
try { // get last value
currentValue[0] = (xSource.getDoubleValue(_maxi)-_ox)*_sx;
currentValue[1] = (_oy-ySource.getDoubleValue(_maxi))*_sy;
xCache[iCache]=currentValue[0];
yCache[iCache]=currentValue[1];
ySourceCache[iCache]=currentYSourceValue;
useCache=true;
currentSegmentIsValid = true;
} catch (DataException e) {
e.printStackTrace();
throw new java.util.NoSuchElementException(e.toString());
}
}
_index=k;
}
currentValueIsComputed = true;
}
else {
_index++;
currentValueIsComputed = false;
}
}
// PathIterator Interface end
}
// Take care of serialisation. Special handling for datasources
private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
out.defaultWriteObject();
DataSourcePool.global.writeDataSource(out, xSource);
DataSourcePool.global.writeDataSource(out, ySource);
}
private void readObject(java.io.ObjectInputStream in) throws java.lang.ClassNotFoundException, java.io.IOException {
in.defaultReadObject();
localRangeMaxPoint = new CurvePoint();
localRangeMinPoint = new CurvePoint();
currentPoint = new CurvePoint();
listeners=new ListenerManager();
xCache=new double[2*MAXSTEPS];
yCache=new double[2*MAXSTEPS];
ySourceCache=new double[2*MAXSTEPS];
useCache=false;
iteratorMinIndex=-1;
iteratorMaxIndex=-1;
xSource = DataSourcePool.global.readDataSource(in);
ySource = DataSourcePool.global.readDataSource(in);
if(xSource==null || ySource==null){
return;
}
xSource.addListener(this);
ySource.addListener(this);
if (min!=-1) {
try {
long ximin = xSource.computeStartIndex();
long yimin = ySource.computeStartIndex();
min = (ximin>yimin)? ximin : yimin;
}
catch (UnsupportedOperation uo1) {
min = -1;
}
}
if (max!=-1) {
try {
long ximax = xSource.computeLastIndex();
long yimax = ySource.computeLastIndex();
max = (ximax<yimax)? ximax : yimax;
}
catch (UnsupportedOperation uo1) {
max = -1;
}
}
if ((min!=-1) && (max!=-1)) {
xSource.setSlice(min, max);
ySource.setSlice(min, max);
}
}
/* (non-Javadoc)
* @see java.lang.Object#clone()
*/
public Object clone() throws CloneNotSupportedException {
CurveShape cs = (CurveShape)super.clone();
// object cloning this curve should re-register on cline
cs.listeners = new ListenerManager();
cs.localRangeMaxPoint = new CurvePoint();
cs.localRangeMinPoint = new CurvePoint();
cs.currentPoint = new CurvePoint();
cs.referencePoint = null;
cs.useCache = false;
cs.xCache = new double[2*MAXSTEPS];
cs.yCache = new double[2*MAXSTEPS];
cs.ySourceCache = new double[2*MAXSTEPS];
if (cs.xSource != null){
cs.xSource.addListener(cs);
}
if (cs.ySource != null){
cs.ySource.addListener(cs);
}
return cs;
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceValueChanged(simtools.data.DataSource, long, long)
*/
public void DataSourceValueChanged(DataSource ds,long minIndex,long maxIndex) {
// Filter out of range changes
if ((min!=-1) && (min>maxIndex)) return;
if ((max!=-1) && (max<minIndex)) return;
notifyChange();
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceIndexRangeChanged(simtools.data.DataSource, long, long)
*/
public void DataSourceIndexRangeChanged(DataSource ds, long startIndex, long lastIndex) {
if ( (ds.equals(xSource)) || (ds.equals(ySource))) {
if (ds.equals(xSource)) {
xSourceChanged = true;
xSourceNewStartIndex = startIndex;
xSourceNewLastIndex = lastIndex;
}
if (ds.equals(ySource)) {
ySourceChanged = true;
ySourceNewStartIndex = startIndex;
ySourceNewLastIndex = lastIndex;
}
// Wait for both sources to change before propagating change info
if (xSourceChanged && ySourceChanged) {
long min = Math.max(xSourceNewStartIndex, ySourceNewStartIndex);
long max = Math.min(xSourceNewLastIndex, ySourceNewLastIndex);
if ((this.min==min) && (this.max==max)) return; // no change
if (min!=-1) {
this.min = min;
}
if (max!=-1) {
this.max = max;
}
if (drawNewOnly) {
iteratorMinIndex = lastDrawnIndex;
iteratorMaxIndex = max;
}
notifyChange();
}
}
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceInfoChanged(simtools.data.DataSource, simtools.data.DataInfo)
*/
public void DataSourceInfoChanged(DataSource ds, DataInfo newInfo) {
// We don't care about that
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceValueRangeChanged(simtools.data.DataSource, java.lang.Object, java.lang.Object)
*/
public void DataSourceValueRangeChanged(DataSource ds) {
notifyChange(); // Our bounds may have changed
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceOrderChanged(simtools.data.DataSource, int)
*/
public void DataSourceOrderChanged(DataSource ds, int newOrder) {
notifyChange(); // May destroy user's optimizations
}
/* (non-Javadoc)
* @see simtools.data.DataSourceListener#DataSourceReplaced(simtools.data.DataSource, simtools.data.DataSource)
*/
public void DataSourceReplaced(DataSource oldData, DataSource newData) {
if (xSource==oldData){
if(xSource!=null){
xSource.removeListener(this);
}
xSource=newData;
if(xSource!=null){
xSource.addListener(this);
}
synchronized(listeners) {
int n = listeners.size(); // only one call outside loop
for (int i=0; i<n; ++i) {
CurveShapeListener csl = (CurveShapeListener)listeners.get(i);
if (csl!=null) csl.shapeDataChanged(this, oldData,newData,true);
}
}
}
if (ySource==oldData){
if(ySource!=null) {
ySource.removeListener(this);
}
ySource=newData;
if(ySource!=null){
ySource.addListener(this);
}
synchronized(listeners) {
int n = listeners.size(); // only one call outside loop
for (int i=0; i<n; ++i) {
CurveShapeListener csl = (CurveShapeListener)listeners.get(i);
if (csl!=null) csl.shapeDataChanged(this, oldData,newData,true);
}
}
}
}
/**
* Give an opportunity to reload shape attribute when the shape is being restored
* By default do nothing. This method shall be overloaded by sub classes.
*/
public void processShapeRestoring(){
if (xSource != null){
xSource.addListener(this);
}
if (ySource != null){
ySource.addListener(this);
}
}
/**
* Give an opportunity to unload shape attribute when the shape is being deleted
* By default do nothing. This method shall be overloaded by sub classes.
*/
public void processShapeRemoving(){
if (xSource != null){
xSource.removeListener(this);
}
if (ySource != null){
ySource.removeListener(this);
}
}
// Listeners related functions
public void addListener(CurveShapeListener csl) {
listeners.add(csl);
}
public void removeListener(CurveShapeListener csl) {
listeners.remove(csl);
}
protected void notifyChange() {
synchronized(listeners) {
int n = listeners.size(); // only one call outside loop
for (int i=0; i<n; ++i) {
CurveShapeListener csl = (CurveShapeListener)listeners.get(i);
if (csl!=null) csl.shapeChanged(this);
}
}
useCache=false;
}
/**
* @return boolean
*/
public boolean isDrawNewOnly() {
return drawNewOnly;
}
/**
* Sets the drawNewOnly.
* @param drawNewOnly The drawNewOnly to set
*/
public boolean tryDrawNewOnly() {
drawNewOnly = (xSource!= null) && (xSource.sortedOrder() != 0);
return drawNewOnly;
}
// Should be synchronized because of the static, or allocate each time
// For now, keep static to avoid allocation and don't synchronize...
static private double [] drawOnlyCurrentValue = new double[4];
public Rectangle2D getDrawNewOnlyArea() {
if ( (xSource== null) || (xSource.sortedOrder()==0) || (iteratorMinIndex==-1) || (iteratorMinIndex==-1))
return getBounds();
// use iterator min/max index
try {
drawOnlyCurrentValue[0] = (xSource.getDoubleValue(iteratorMinIndex)-orgx)*scalex;
drawOnlyCurrentValue[1] = (orgy-ySource.getDoubleValue(iteratorMinIndex))*scaley;
drawOnlyCurrentValue[2] = (xSource.getDoubleValue(iteratorMaxIndex)-orgx)*scalex;
drawOnlyCurrentValue[3] = (orgy-ySource.getDoubleValue(iteratorMaxIndex))*scaley;
} catch (DataException e) {
return null;
}
// Affine transform on both points at the same time
if (_aff!=null) _aff.transform(drawOnlyCurrentValue,0,drawOnlyCurrentValue,0,2);
double x = Math.min(drawOnlyCurrentValue[0],drawOnlyCurrentValue[2]);
double y = Math.min(drawOnlyCurrentValue[1],drawOnlyCurrentValue[3]);
double w = Math.max(drawOnlyCurrentValue[0],drawOnlyCurrentValue[2]) - x;
double h = Math.max(drawOnlyCurrentValue[1],drawOnlyCurrentValue[3]) - y;
return new Rectangle2D.Double(x,y,w,h);
}
protected void computeLocalRangeIndex(double xmin, double xmax) throws DataException{
if ( xSource==null) {
throw new DataException();
}
// Get curve min and curve max indexes related to current zoom
double ln2 = Math.log(2);
localMinIndex = xSource.getStartIndex();
localMaxIndex= xSource.getLastIndex();
double ds=xSource.getDoubleValue(localMinIndex);
double dl=xSource.getDoubleValue(localMaxIndex);
boolean twoTimes = (localMaxIndex-localMinIndex)>MAXSTEPS; // get max and min indexes in 2 times
if (xSource.sortedOrder()==0)
throw new DataException();
if(localMaxIndex>localMinIndex){
// Fisrt compute...
if ( (( xSource.sortedOrder()==-1 && (ds>xmin)) || ( xSource.sortedOrder()==1 && (ds<xmin))) && twoTimes){
long mini = localMinIndex;
long maxi = localMaxIndex;
long pivot;
long s = (maxi-mini) / ( MAXSTEPS<100? 1 : MAXSTEPS/100);
long nbIteration = (int)Math.ceil((Math.log(s) /ln2));
for(int i =0;i<nbIteration;i++){
pivot = (maxi-mini) /2 + mini;
double rr = xSource.getDoubleValue(pivot);
if (rr<=xmin){
mini = pivot;
}else{
maxi = pivot;
}
}
localMinIndex=mini;
}
if ( ((xSource.sortedOrder()==-1 && (dl<xmax)) || (xSource.sortedOrder()==1 && (dl>xmax))) && twoTimes) {
long mini = localMinIndex;
long maxi = localMaxIndex;
long pivot;
long s = (maxi-mini) / ( MAXSTEPS<100? 1 : MAXSTEPS/100);
long nbIteration = (int)Math.ceil((Math.log(s) /ln2));
for(int i =0;i<nbIteration;i++){
pivot = (maxi-mini) /2 + mini;
double rr = xSource.getDoubleValue(pivot);
if (rr<=xmax){
mini = pivot;
}else{
maxi = pivot;
}
}
localMaxIndex=maxi;
}
}
// Second compute (more accurate)
ds=xSource.getDoubleValue(localMinIndex);
dl=xSource.getDoubleValue(localMaxIndex);
if(localMaxIndex>localMinIndex){
if (( xSource.sortedOrder()==-1 && (ds>xmin)) || ( xSource.sortedOrder()==1 && (ds<xmin))){
long mini = localMinIndex;
long maxi = (!twoTimes || (localMinIndex + ( MAXSTEPS<100? 1 : MAXSTEPS/100) >localMaxIndex))? localMaxIndex : localMinIndex + ( MAXSTEPS<100? 1 : MAXSTEPS/100);
long pivot;
long s = (maxi-mini);
long nbIteration = (int)Math.ceil((Math.log(s) /ln2));
for(int i =0;i<nbIteration;i++){
pivot = (maxi-mini) /2 + mini;
double rr = xSource.getDoubleValue(pivot);
if (rr<=xmin){
mini = pivot;
}else{
maxi = pivot;
}
}
if (xSource.getDoubleValue(mini) == xmin)
localMinIndex=mini;
else
localMinIndex=mini+1;
}
if ((xSource.sortedOrder()==-1 && (dl<xmax)) || (xSource.sortedOrder()==1 && (dl>xmax))) {
long mini = (!twoTimes || (localMinIndex + ( MAXSTEPS<100? 1 : MAXSTEPS/100) >localMaxIndex))? localMinIndex : localMaxIndex - ( MAXSTEPS<100? 1 : MAXSTEPS/100);
long maxi = localMaxIndex;
long pivot;
long s = (maxi-mini);
long nbIteration = (int)Math.ceil((Math.log(s) /ln2));
for(int i =0;i<nbIteration;i++){
pivot = (maxi-mini) /2 + mini;
double rr = xSource.getDoubleValue(pivot);
if (rr<=xmax){
mini = pivot;
}else{
maxi = pivot;
}
}
if (xSource.getDoubleValue(maxi) == xmax)
localMaxIndex=maxi;
else
localMaxIndex=maxi-1;
}
}
if (localMaxIndex<localMinIndex){
localMaxIndex = localMinIndex;
}
}
public void computeYLocalRange(double xmin, double xmax) throws DataException{
if ( (xSource==null) || (ySource==null)) {
throw new DataException();
}
computeLocalRangeIndex(xmin, xmax);
// maxAfter is true if the point found for max position is after xmax
boolean maxAfter = xSource.getDoubleValue(localMaxIndex) > xmax;
// minAfter is true if the point found for min position is before xmax
boolean minAfter = xSource.getDoubleValue(localMinIndex) > xmax;
// maxBefore is true if the point found for max position is before xmin
boolean maxBefore = xSource.getDoubleValue(localMaxIndex) < xmin;
// minBefore is true if the point found for min position is before xmin
boolean minBefore = xSource.getDoubleValue(localMinIndex) < xmin;
localRangeNbPoints =0;
if ( (minBefore && maxBefore) || (maxAfter && minAfter)){
// No point
localRangeNbPoints=0;
localRangeMaxPoint.x = 0;
localRangeMaxPoint.y = 0;
localRangeMinPoint.x = 0;
localRangeMinPoint.y = 0;
} else {
// Get max and min curve values, between curveMinIndex and curveMaxIndex
localRangeMinPoint.x = Double.MAX_VALUE;
localRangeMaxPoint.x = - Double.MAX_VALUE;
localRangeMinPoint.y = Double.MAX_VALUE;
localRangeMaxPoint.y = - Double.MAX_VALUE;
for(long index=localMinIndex; index<=localMaxIndex; index++){
localRangeNbPoints++;
double yvalue = ySource.getDoubleValue(index);
double xvalue = xSource.getDoubleValue(index);
if (yvalue<localRangeMinPoint.y) {
localRangeMinPoint.y = yvalue;
localRangeMinPoint.x = xvalue;
}
if (yvalue>localRangeMaxPoint.y) {
localRangeMaxPoint.y = yvalue;
localRangeMaxPoint.x = xvalue;
}
}
}
}
/**
* Method computeCurveStatisticsAndIntegral
* Compute min, max, mean, and integral for a function y=f(x) in a specified interval [xmin, xmax]
* Data source attached to X values <b>must be sorted</b>, otherwise no compute is performed.
* @param xmin fisrt bound of interval in which compute is performed
* @param xmax last bound of interval in which compute is performed
* @throws DataException
*/
public void computeStatistics(double xmin, double xmax) throws DataException{
if ( (xSource==null) || (ySource==null)) {
throw new DataException();
}
computeLocalRangeIndex(xmin, xmax);
// maxAfter is true if the point found for max position is after xmax
boolean maxAfter = xSource.getDoubleValue(localMaxIndex) > xmax;
// minAfter is true if the point found for min position is before xmax
boolean minAfter = xSource.getDoubleValue(localMinIndex) > xmax;
// maxBefore is true if the point found for max position is before xmin
boolean maxBefore = xSource.getDoubleValue(localMaxIndex) < xmin;
// minBefore is true if the point found for min position is before xmin
boolean minBefore = xSource.getDoubleValue(localMinIndex) < xmin;
localRangeNbPoints = 0;
if ( (minBefore && maxBefore) || (maxAfter && minAfter)){
// No point
localRangeNbPoints=0;
localRangeMaxPoint.x = 0;
localRangeMaxPoint.y = 0;
localRangeMinPoint.x = 0;
localRangeMinPoint.y = 0;
localRangeMean = 0;
localRangeIntegral=0;
localRangeDeviation = 0;
} else {
localRangeMinPoint.x = Double.MAX_VALUE;
localRangeMaxPoint.x = - Double.MAX_VALUE;
localRangeMinPoint.y = Double.MAX_VALUE;
localRangeMaxPoint.y = - Double.MAX_VALUE;
localRangeMean = 0;
localRangeIntegral=0;
localRangeDeviation=0;
if ((localMaxIndex - localMinIndex)>=2)
localRangeIntegral= (xSource.getDoubleValue(localMinIndex+1) - xSource.getDoubleValue(localMinIndex)) * (ySource.getDoubleValue(localMinIndex+1) - ySource.getDoubleValue(localMinIndex));
// Compute mean, max, min, integral, deviation
for(long index=localMinIndex; index<=localMaxIndex; index++){
localRangeNbPoints++;
double yvalue = ySource.getDoubleValue(index);
double xvalue = xSource.getDoubleValue(index);
if (yvalue < localRangeMinPoint.y) {
localRangeMinPoint.y = yvalue;
localRangeMinPoint.x = xvalue;
}
if (yvalue > localRangeMaxPoint.y) {
localRangeMaxPoint.y = yvalue;
localRangeMaxPoint.x = xvalue;
}
localRangeMean += yvalue;
if (index!=localMaxIndex){
//TODO if index + 1 is NOT valid ?
localRangeIntegral+= (xSource.getDoubleValue(index+1) - xvalue) * ((ySource.getDoubleValue(index+1) + yvalue)/2);
}
}
localRangeMean /= localRangeNbPoints;
// once mean has been computed, standard deviation can be computed also
// Compute mean, max, min, integral, deviation
for(long index=localMinIndex; index<=localMaxIndex; index++){
double yvalue = ySource.getDoubleValue(index);
localRangeDeviation += Math.pow( (yvalue - localRangeMean), 2);
}
localRangeDeviation /= localRangeNbPoints;
localRangeDeviation = Math.sqrt(localRangeDeviation);
}
}
public void setReferencePoint(CurvePoint curvePoint) {
if (curvePoint != null) {
referencePoint = new CurvePoint(curvePoint);
} else {
referencePoint = null;
}
}
/**
* Get curve point the closest to X mouse position
* Set this point as current point.
* x data source values must sorted in descending or ascending order.
* y data source indexes must fit with x indexes
* If x and y data sources are not compliant with these contraints, an exception is thrown
*
* - Compute magnetized point slope with next point (or previous point if it is the last point)
*/
public void setCurrentPoint(double pos_x) throws DataException{
if ( (xSource==null) || (ySource==null))
throw new DataException();
double ln2 = Math.log(2);
long magnetizedPointMin = xSource.getStartIndex();
long magnetizedPointMax = xSource.getLastIndex();
boolean twoTimes = (magnetizedPointMax-magnetizedPointMin)>MAXSTEPS; // get max and min indexes in 2 times
if (xSource.sortedOrder()==0)
throw new DataException();
if(magnetizedPointMax>magnetizedPointMin){
// Fisrt compute...
if (twoTimes){
long mini = magnetizedPointMin;
long maxi = magnetizedPointMax;
long pivot;
long s = (maxi-mini) / ( MAXSTEPS<100? 1 : MAXSTEPS/100);
long nbIteration = (int)Math.ceil((Math.log(s) /ln2));
for(int i =0;i<nbIteration;i++){
pivot = (maxi-mini) /2 + mini;
double rr = xSource.getDoubleValue(pivot);
if (rr<=pos_x){
mini = pivot;
}else{
maxi = pivot;
}
}
magnetizedPointMin = mini;
}
// Second compute (more accurate)
if(magnetizedPointMax>magnetizedPointMin){
long mini = magnetizedPointMin;
long maxi = (!twoTimes ||(magnetizedPointMin + ( MAXSTEPS<100? 1 : MAXSTEPS/100) >magnetizedPointMax)) ? magnetizedPointMax : magnetizedPointMin + ( MAXSTEPS<100? 1 : MAXSTEPS/100);
long pivot;
long s = (maxi-mini);
long nbIteration = (int)Math.ceil((Math.log(s) /ln2));
for(int i =0;i<nbIteration;i++){
pivot = (maxi-mini) /2 + mini;
double rr = xSource.getDoubleValue(pivot);
if (rr<=pos_x){
mini = pivot;
}else{
maxi = pivot;
}
}
magnetizedPointMin=mini;
}
}
// Look for nearest point between 2 points surrounding pos_x position
long magnetizedPoint = (pos_x- xSource.getDoubleValue(magnetizedPointMin) < xSource.getDoubleValue(magnetizedPointMin+1) - pos_x )? magnetizedPointMin : magnetizedPointMin+1;
// Set _magnetized point x and y:
currentPoint.x = xSource.getDoubleValue(magnetizedPoint);
currentPoint.y = ySource.getDoubleValue(magnetizedPoint);
// Compute slope
if (xSource.getLastIndex()-xSource.getStartIndex()>=2){
if (xSource.getLastIndex()!=magnetizedPoint){
currentPoint.slop_y = ySource.getDoubleValue(magnetizedPoint+1) - ySource.getDoubleValue(magnetizedPoint);
currentPoint.slop_x = xSource.getDoubleValue(magnetizedPoint+1) - xSource.getDoubleValue(magnetizedPoint);
}else{
currentPoint.slop_y = -(ySource.getDoubleValue(magnetizedPoint) - ySource.getDoubleValue(magnetizedPoint-1));
currentPoint.slop_x = - (xSource.getDoubleValue(magnetizedPoint) - xSource.getDoubleValue(magnetizedPoint-1));
}
}
}
public CurvePoint getLocalRangeMaxPoint(){
return new CurvePoint(localRangeMaxPoint);
}
public CurvePoint getLocalRangeMinPoint(){
return new CurvePoint(localRangeMinPoint);
}
public long getLocalNbPoints() {
return localRangeNbPoints;
}
public double getLocalRangeMean() {
return localRangeMean;
}
public CurvePoint getCurrentPoint() {
return new CurvePoint(currentPoint);
}
public CurvePoint getReferencePoint() {
if (referencePoint != null) {
return new CurvePoint(referencePoint);
}
return null;
}
public double getLocalIntegralValue() {
return localRangeIntegral;
}
public double getLocalDeviationValue() {
return localRangeDeviation;
}
/**
* @return a list of information about the curve.
*
*/
public String getCurveInformation(double xmin, double xmax){
try{
computeStatistics(xmin, xmax);
NumberFormat nf = NumberFormat.getInstance();
nf.setMaximumFractionDigits(STATISTIC_ACCURACY);
String curveStat ="";
if (SHOW_X_MIN){
curveStat += "X min = " + nf.format(localRangeMinPoint.x) + "; ";
}
if (SHOW_X_MAX){
curveStat += "X max = " + nf.format(localRangeMaxPoint.x) + "; ";
}
if (SHOW_Y_MIN){
curveStat += "Y min = " + nf.format(localRangeMinPoint.y) + "; ";
}
if (SHOW_Y_MAX){
curveStat += "Y max = " + nf.format(localRangeMaxPoint.y) + "; ";
}
if (SHOW_POINT_NUMBER){
curveStat += "Nb pts = " + getLocalNbPoints() + "; ";
}
if (SHOW_MEAN){
curveStat += "Mean = " + nf.format(getLocalRangeMean()) + "; ";
}
if (SHOW_DEVIATION){
curveStat += "Deviation = " + nf.format(getLocalDeviationValue()) + "; ";
}
if (SHOW_INTEGRAL){
curveStat += "Integral = " + nf.format(getLocalIntegralValue()) + "; ";
}
return curveStat;
}catch (DataException e){
return ("\t" + PlotInformationDialog.resources.getString("noStatisticsAvailable"));
}
}
public static class CurveShapePropertiesNames extends AbstractShapePropertiesNames{
private static transient String[] props = new String[] { "PLOT_CURVE_LIST" };
public CurveShapePropertiesNames(){
for (int i=0;i<props.length;i++){
propertyNames.add(props[i]);
}
}
}
/**
* A curve point representing a location in (x, y) coordinate space, specified in double precision.
* @author zxpletran007
*
*/
public static class CurvePoint {
public double x;
public double y;
public double slop_x;
public double slop_y;
public CurvePoint(CurvePoint ref){
this(ref.x,ref.y,ref.slop_x,ref.slop_y);
}
public CurvePoint(){
this(0,0,0,0);
}
public CurvePoint(double x, double y) {
this(x,y,0,0);
}
public CurvePoint(double x, double y, double slop_x, double slop_y) {
this.x = x;
this.y = y;
this.slop_x = slop_x;
this.slop_y = slop_y;
}
}
}