/*******************************************************************************
* Mission Control Technologies, Copyright (c) 2009-2012, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* The MCT platform is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* MCT includes source code licensed under additional open source licenses. See
* the MCT Open Source Licenses file included with this distribution or the About
* MCT Licenses dialog available at runtime from the MCT Help menu for additional
* information.
*******************************************************************************/
package plotter.xy;
import java.util.HashSet;
import java.util.Set;
import plotter.DoubleData;
/**
* Default implementation of an XY dataset that supports truncation.
* Note that the min and max values may be invalid if you manipulate the DoubleData buffers directly.
* @author Adam Crume
*/
public class SimpleXYDataset implements XYDataset {
/** Plot line that displays the data. */
private XYPlotLine line;
/** Holds the X data. */
private DoubleData xData;
/** Holds the Y data. */
private DoubleData yData;
/** Maximum size (inclusive) before truncating. */
private int maxCapacity = Integer.MAX_VALUE;
/** Cached minimum X value of all points in the dataset. */
private double minX = Double.POSITIVE_INFINITY;
/** Cached maximum X value of all points in the dataset. */
private double maxX = Double.NEGATIVE_INFINITY;
/** Cached minimum Y value of all points in the dataset. */
private double minY = Double.POSITIVE_INFINITY;
/** Cached maximum Y value of all points in the dataset. */
private double maxY = Double.NEGATIVE_INFINITY;
/** Listeners that get notified if the X min or max changes. May be null. */
private Set<MinMaxChangeListener> xMinMaxListeners;
/** Listeners that get notified if the Y min or max chagnes. May be null. */
private Set<MinMaxChangeListener> yMinMaxListeners;
/** Value of {@link #minX} before a modification. */
private double oldMinX;
/** Value of {@link #maxX} before a modification. */
private double oldMaxX;
/** Value of {@link #minY} before a modification. */
private double oldMinY;
/** Value of {@link #maxY} before a modification. */
private double oldMaxY;
/**
* Creates a dataset.
* @param line line to plot the data
*/
public SimpleXYDataset(XYPlotLine line) {
this.line = line;
xData = line.getXData();
yData = line.getYData();
}
/**
* Adds a point, truncating the buffer if necessary.
* The X value must be greater than or equal to all other X values in the dataset.
* @param x the X coordinate
* @param y the Y coordinate
*/
@Override
public void add(double x, double y) {
preMod();
int length = xData.getLength() + 1; // length after add
if(length > maxCapacity) {
_removeFirst(length - maxCapacity);
length = maxCapacity;
}
line.add(x, y);
updateMinMax(x, y);
postMod();
}
/**
* Called after a modification.
* Notifies any relevant listeners of changes.
*/
protected void postMod() {
if(xMinMaxListeners != null) {
if(minX != oldMinX || maxX != oldMaxX) {
for(MinMaxChangeListener listener : xMinMaxListeners) {
listener.minMaxChanged(this, XYDimension.X);
}
}
}
if(yMinMaxListeners != null) {
if(minY != oldMinY || maxY != oldMaxY) {
for(MinMaxChangeListener listener : yMinMaxListeners) {
listener.minMaxChanged(this, XYDimension.Y);
}
}
}
}
/**
* Called before a modification.
* Stores any state needed for {@link #postMod()}.
*/
protected void preMod() {
oldMinX = minX;
oldMaxX = maxX;
oldMinY = minY;
oldMaxY = maxY;
}
/**
* Removes the first <code>removeCount</code> points from the dataset.
* @param removeCount number of points to remove
*/
public void removeFirst(int removeCount) {
preMod();
_removeFirst(removeCount);
postMod();
}
/**
* Removes the first <code>removeCount</code> points from the dataset.
* Does not call {@link #preMod()} or {@link #postMod()}.
* @param removeCount number of points to remove
*/
private void _removeFirst(int removeCount) {
// TODO: Use a more efficient method than rescanning the entire arrays. For example, cache min/max values for subranges.
int length = xData.getLength();
boolean rescanX = false;
boolean rescanY = false;
for(int i = 0; i < removeCount; i++) {
double xd = xData.get(i);
double yd = yData.get(i);
if(xd == minX || xd == maxX) {
rescanX = true;
}
if(yd == minY || yd == maxY) {
rescanY = true;
}
}
if(rescanX) {
if(line.getIndependentDimension() == XYDimension.X) {
if(removeCount < length) {
minX = xData.get(removeCount);
maxX = xData.get(length - 1);
} else {
minX = Double.POSITIVE_INFINITY;
maxX = Double.NEGATIVE_INFINITY;
}
} else {
minX = Double.POSITIVE_INFINITY;
maxX = Double.NEGATIVE_INFINITY;
for(int i = removeCount; i < length; i++) {
double xd = xData.get(i);
if(xd > maxX) {
maxX = xd;
}
if(xd < minX) {
minX = xd;
}
}
}
}
if(rescanY) {
if(line.getIndependentDimension() == XYDimension.Y) {
if(removeCount < length) {
minY = yData.get(removeCount);
maxY = yData.get(length - 1);
} else {
minY = Double.POSITIVE_INFINITY;
maxY = Double.NEGATIVE_INFINITY;
}
} else {
minY = Double.POSITIVE_INFINITY;
maxY = Double.NEGATIVE_INFINITY;
for(int i = removeCount; i < length; i++) {
double yd = yData.get(i);
if(yd > maxY) {
maxY = yd;
}
if(yd < minY) {
minY = yd;
}
}
}
}
line.removeFirst(removeCount);
}
@Override
public void prepend(double[] x, int xoff, double[] y, int yoff, int len) {
preMod();
for(int i = 0; i < len; i++) {
updateMinMax(x[xoff + i], y[yoff + i]);
}
// TODO: Only add data that wouldn't be truncated
line.prepend(x, xoff, y, yoff, len);
postMod();
}
@Override
public void prepend(DoubleData x, DoubleData y) {
preMod();
int len = x.getLength();
for(int i = 0; i < len; i++) {
updateMinMax(x.get(i), y.get(i));
}
// TODO: Only add data that wouldn't be truncated
line.prepend(x, y);
postMod();
}
/**
* Updates the min/max cache to include this point.
* @param x X coordinate
* @param y Y coordinate
*/
private void updateMinMax(double x, double y) {
if(x > maxX) {
maxX = x;
}
if(x < minX) {
minX = x;
}
if(y > maxY) {
maxY = y;
}
if(y < minY) {
minY = y;
}
}
@Override
public void removeAllPoints() {
preMod();
line.removeAllPoints();
minX = Double.POSITIVE_INFINITY;
maxX = Double.NEGATIVE_INFINITY;
minY = Double.POSITIVE_INFINITY;
maxY = Double.NEGATIVE_INFINITY;
postMod();
}
@Override
public int getPointCount() {
return xData.getLength();
}
/**
* Returns the X data.
* @return the X data
*/
public DoubleData getXData() {
return xData;
}
/**
* Sets the X data.
* @param xData the X data
*/
public void setXData(DoubleData xData) {
this.xData = xData;
}
/**
* Returns the Y data.
* @return the Y data
*/
public DoubleData getYData() {
return yData;
}
/**
* Sets the Y data.
* @param yData the Y data
*/
public void setYData(DoubleData yData) {
this.yData = yData;
}
/**
* Returns the maximum size this dataset will hold.
* @return the maximum size this dataset will hold
*/
public int getMaxCapacity() {
return maxCapacity;
}
/**
* Sets the maximum size this dataset will hold.
* @param maxCapacity the maximum size this dataset will hold
*/
public void setMaxCapacity(int maxCapacity) {
this.maxCapacity = maxCapacity;
}
/**
* Returns the minimum X value.
* @return the minimum X value
*/
public double getMinX() {
return minX;
}
/**
* Returns the maximum X value.
* @return the maximum X value
*/
public double getMaxX() {
return maxX;
}
/**
* Returns the minimum Y value.
* @return the minimum Y value
*/
public double getMinY() {
return minY;
}
/**
* Returns the maximum Y value.
* @return the maximum Y value
*/
public double getMaxY() {
return maxY;
}
/**
* Adds a listener for min/max changes to the X data.
* @param listener listener to add
*/
public void addXMinMaxChangeListener(MinMaxChangeListener listener) {
if(xMinMaxListeners == null) {
xMinMaxListeners = new HashSet<MinMaxChangeListener>();
}
xMinMaxListeners.add(listener);
}
/**
* Adds a listener for min/max changes to the Y data.
* @param listener listener to add
*/
public void addYMinMaxChangeListener(MinMaxChangeListener listener) {
if(yMinMaxListeners == null) {
yMinMaxListeners = new HashSet<MinMaxChangeListener>();
}
yMinMaxListeners.add(listener);
}
/**
* Removes all {@link MinMaxChangeListener}s for the Y data.
*/
public void removeAllYMinMaxChangeListeners() {
yMinMaxListeners = null;
}
/**
* Removes all {@link MinMaxChangeListener}s for the X data.
*/
public void removeAllXMinMaxChangeListeners() {
xMinMaxListeners = null;
}
@Override
public void removeLast(int count) {
line.removeLast(count);
}
/**
* Listens to min/max changes.
* @author Adam Crume
*/
public interface MinMaxChangeListener {
/**
* Notifies the listener that the min or max changed.
* @param dataset dataset containing the data
* @param dimension specifies whether this notification is for the X or Y data
*/
public void minMaxChanged(SimpleXYDataset dataset, XYDimension dimension);
}
}