package jas.hist;
import jas.plot.Overlay;
import jas.util.ColorConverter;
import java.io.Serializable;
import java.util.Observable;
class JASHist1DHistogramData extends JASHistData {
JASHist1DHistogramData(DataManager dm,DataSource ds) {
super(dm);
dataSource = ds;
initTransientData();
JASHistStyle s = null;
if (ds instanceof HasStyle) s = ((HasStyle) ds).getStyle();
if (s == null) s = new JASHist1DHistogramStyle();
setStyle(s);
String property = System.getProperty("hurry", "false");
hurry = property != null && property.equalsIgnoreCase("true");
}
private void initTransientData() {
yLimitsValid = false;
isBinned = false;
}
public void setStyle(JASHistStyle style) {
if (!(style instanceof JASHist1DHistogramStyle))
throw new IllegalArgumentException("Style is not subclass of JASHist1DHistogramStyle");
if (this.style != null) this.style.deleteObserver(this);
this.style = (JASHist1DHistogramStyle) style;
this.style.addObserver(this);
}
public String getTitle() {
return dataSource.getTitle();
}
String[] getAxisLabels() {
return dataSource instanceof Rebinnable1DHistogramData ? ((Rebinnable1DHistogramData) dataSource).getAxisLabels() : null;
}
Overlay createOverlay() {
return new OneDOverlay(this);
}
void writeAsXML(XMLPrintWriter pw, boolean snapshot) {
pw.setAttribute("axis","y"+getYAxis());
pw.openTag("data1d");
String theAxisType = pw.convertAxisTypeToString(getAxisType());
if (snapshot) {
if (dataSource instanceof Rebinnable1DHistogramData) {
pw.setAttribute("title",getTitle());
pw.openTag("bins1d");
for (int i=0; i < data.length; i++) {
pw.print(data[i]);
pw.print(",");
pw.print(plusError[i]);
pw.print(",");
pw.print(minusError[i]);
pw.println();
}
pw.closeTag();
pw.printBinnedDataAxisAttributes(
"x", "" + xLow, "" + xHigh,
"" + xBins, theAxisType);
if (theAxisType.equals("string")) {
pw.setAttribute("type","x0");
pw.openTag("axisLabels");
String[] labels = getAxisLabels();
for (int i=0; i < labels.length; i++) {
pw.setAttribute("value",labels[i]);
pw.printTag("axisLabel");
}
pw.closeTag();
}
}
else {
pw.setAttribute("title",getTitle());
pw.openTag("points");
for (int i=0; i < data.length; i++) {
pw.print(dataX[i]);
pw.print(',');
pw.print(data[i]);
pw.print(',');
pw.print(plusError[i]);
pw.print(',');
pw.print(minusError[i]);
pw.println();
}
pw.closeTag();
pw.setAttribute("axis","x");
pw.setAttribute("type",theAxisType);
pw.printTag("pointDataAxisAttributes");
}
if (dataSource instanceof HasStatistics) {
Statistics stats = ((HasStatistics) dataSource).getStatistics();
if (stats != null) {
pw.openTag("statistics");
String[] names = stats.getStatisticNames();
for (int i=0; i<names.length; i++) {
String name = names[i];
pw.setAttribute("name",name);
String valueString = null;
if (stats instanceof ExtendedStatistics)
{
Object value = ((ExtendedStatistics) stats).getExtendedStatistic(name);
if (value != null) valueString = value.toString();
}
if (valueString == null) valueString = String.valueOf(stats.getStatistic(name));
pw.setAttribute("value",valueString);
pw.printTag("statistic");
}
pw.closeTag();
}
}
}
else // !snapshot
{
if (dataSource instanceof jas.util.xml.HasXMLRepresentation) {
((jas.util.xml.HasXMLRepresentation) dataSource).writeAsXML(pw);
}
else {
if (dataSource instanceof HasDataSource) pw.setAttribute("name",dataSource.getClass().getName());
else pw.setAttribute("name","???");
pw.setAttribute("param","???");
pw.printTag("class");
}
}
pw.setAttribute("histogramBarsFilled",style.getHistogramFill());
pw.setAttribute("histogramBarColor",
ColorConverter.colorToString(style.getHistogramBarColor()));
pw.setAttribute("errorBarColor",
ColorConverter.colorToString(style.getErrorBarColor()));
pw.setAttribute("dataPointColor",
ColorConverter.colorToString(style.getDataPointColor()));
pw.setAttribute("dataPointStyle",pw.convertStyleToString(style.getDataPointStyle()));
pw.setAttribute("dataPointSize",style.getDataPointSize());
pw.setAttribute("lineColor",ColorConverter.colorToString(style.getLineColor()));
pw.setAttribute("showHistogramBars",style.getShowHistogramBars());
pw.setAttribute("showErrorBars",style.getShowErrorBars());
pw.setAttribute("showDataPoints",style.getShowDataPoints());
pw.setAttribute("showLinesBetweenPoints",style.getShowLinesBetweenPoints());
pw.printTag("style1d");
pw.closeTag();
}
boolean isRebinnable() {
return dataSource instanceof Rebinnable1DHistogramData ? ((Rebinnable1DHistogramData) dataSource).isRebinnable() : false;
}
double getXMin() {
if (dataSource instanceof Rebinnable1DHistogramData) return ((Rebinnable1DHistogramData) dataSource).getMin();
if (!xLimitsValid) calcXLimits();
return xMin;
}
double getXMax() {
if (dataSource instanceof Rebinnable1DHistogramData) return ((Rebinnable1DHistogramData) dataSource).getMax();
if (!xLimitsValid) calcXLimits();
return xMax;
}
void setXRange(int xBins,double xLow, double xHigh) {
if (isRebinnable()) {
if (xBins != this.xBins || xLow != this.xLow || xHigh != this.xHigh) {
this.xBins = xBins;
isBinned = false;
}
}
else if (dataSource instanceof Rebinnable1DHistogramData) {
this.xBins = ((Rebinnable1DHistogramData) dataSource).getBins();
}
yLimitsValid = false;
this.xLow = xLow;
this.xHigh = xHigh;
}
private void doXYBin() {
isBinned = true;
XYDataSource xy = (XYDataSource) dataSource;
int n = xy.getNPoints();
dataX = new double[n];
data = new double[n];
plusError = new double[n];
minusError = new double[n];
for (int i=0; i<n; i++) {
dataX[i] = xy.getX(i);
data[i] = xy.getY(i);
plusError[i] = xy.getPlusError(i);
minusError[i] = xy.getMinusError(i);
}
// apply normalization
if (normalization != null) {
double factor = 1./normalization.getNormalizationFactor();
double[] normalizedData = new double[n];
double[] normalizedPlusError = new double[n];
double[] normalizedMinusError = new double[n];
for (int i=0; i<n; i++) {
normalizedData[i] = data[i] * factor;
normalizedPlusError[i] = plusError[i] * factor;
normalizedMinusError[i] = minusError[i] * factor;
}
if (overlay instanceof OneDOverlay) {
((OneDOverlay) overlay).setData(dataX,normalizedData,normalizedPlusError,normalizedMinusError);
}
}
else if (overlay instanceof OneDOverlay) {
((OneDOverlay) overlay).setData(dataX,data,plusError,minusError);
}
}
private void doBin() {
if ( dataSource instanceof XYDataSource )
doXYBin();
else {
int type = getAxisType();
String[] labels = null;
double xl,xh;
if (type == dataSource.STRING) {
labels = getAxisLabels();
xBins = labels.length;
xl = 0;
xh = labels.length;
}
else if (isRebinnable()) {
xl = xLow;
xh = xHigh;
}
else {
xl = ((Rebinnable1DHistogramData) dataSource).getMin();
xh = ((Rebinnable1DHistogramData) dataSource).getMax();
}
isBinned = true; // Set before call to rebin to avoid race condition
double[][] result = ((Rebinnable1DHistogramData) dataSource).rebin(xBins,xl,xh,true,hurry);
if (result == null) result = new double[1][xBins];
data = result[0];
if (data.length != xBins)
System.err.println("Warning xbins="+xBins+" data.length="+data.length);
if (result.length > 1) {
plusError = result[1];
if (result.length > 2) minusError = result[2];
else minusError = plusError;
}
else {
plusError = new double[xBins];
for (int i=0; i<xBins; i++) {
plusError[i] = Math.sqrt(Math.abs(data[i]));
}
minusError = plusError;
}
if (plusError.length != xBins)
System.err.println("Warning xbins="+xBins+" plusError.length="+plusError.length);
if (minusError.length != xBins)
System.err.println("Warning xbins="+xBins+" minusError.length="+minusError.length);
// apply normalization
if (normalization != null) {
double factor = 1./normalization.getNormalizationFactor();
double[] normalizedData = new double[xBins];
double[] normalizedPlusError = new double[xBins];
double[] normalizedMinusError = new double[xBins];
for (int i=0; i<data.length; i++) {
normalizedData[i] = data[i] * factor;
normalizedPlusError[i] = plusError[i] * factor;
normalizedMinusError[i] = minusError[i] * factor;
}
if (overlay instanceof OneDOverlay) {
if (type == dataSource.STRING) ((OneDOverlay) overlay).setData(normalizedData,normalizedPlusError,normalizedMinusError,labels);
else ((OneDOverlay) overlay).setData(normalizedData,normalizedPlusError,normalizedMinusError,xl,xh);
}
}
else {
if (overlay instanceof OneDOverlay) {
if (type == dataSource.STRING) ((OneDOverlay) overlay).setData(data,plusError,minusError,labels);
else ((OneDOverlay) overlay).setData(data,plusError,minusError,xl,xh);
}
}
}
}
// Note: This set xMin and xMax to NaN if the number of points == 0. See JAS=334.
private void calcXLimits() {
xLimitsValid = true;
XYDataSource xy = (XYDataSource) dataSource;
int n = xy.getNPoints();
if (n == 0)
{
xMin = Double.NaN;
xMax = Double.NaN;
}
else
{
xMin = Double.MAX_VALUE;
xMax = -Double.MAX_VALUE;
for (int i=0; i<n; i++) {
double x = xy.getX(i);
if (x<xMin) xMin = x;
if (x>xMax) xMax = x;
}
if (xMin > xMax) {
xMin = 0;
xMax = 1;
}
else {
// Allow a little extra space for the size of the points
double delta = (xMax-xMin)/25;
xMin -= delta;
xMax += delta;
}
}
}
private void calcYLimits() {
if (!isBinned) {
if (dataSource instanceof XYDataSource) doXYBin();
else doBin();
fittableDataSource.binningChanged();
}
yLimitsValid = true;
// Note: If the data member for a bin is NaN then we should
// skip that point, and not count it in the Y limits.
// Note2: In the case of non-rebinnable histograms, not all of the points may
// be visible. Only include the visible points in the calculation.
int nGoodPoints = 0;
yLow = Double.MAX_VALUE;
yHigh = -Double.MAX_VALUE; // not the same as MIN_VALUE
// Also remember points just outside the [xLow, xHigh] region -
// if there are no good points in this region, will use those
// to set the Y Axis limits
yNearLow = Double.MAX_VALUE;
yNearHigh = -Double.MAX_VALUE;
xNearLow = -Double.MAX_VALUE;
xNearHigh = Double.MAX_VALUE;
int iNearLow = -1;
int iNearHigh = -1;
int ndp = -1;
double dm = 0.;
double dp = 0.;
boolean eb = style.getShowErrorBars();
boolean fixed = !isRebinnable() && getAxisType() != dataSource.STRING;
if (dataSource instanceof Rebinnable1DHistogramData) {
double x = getXMin();
double bw = (getXMax() - getXMin())/xBins;
ndp = xBins;
for (int i=0; i<xBins; i++) {
if (fixed) {
double xBinMin = x;
x += bw;
double xBinMax = x;
if (xBinMax < xLow) {
if (x>xNearLow) {
xNearLow = x;
iNearLow = i;
}
continue;
}
if (xBinMin > xHigh) {
if (x<xNearHigh) {
xNearHigh = x;
iNearHigh = i;
}
continue;
}
}
double d = data[i];
if (Double.isNaN(d)) continue;
nGoodPoints++;
yLow = Math.min(yLow,d-(eb ? minusError[i] : 0));
yHigh = Math.max(yHigh,d+(eb ? plusError[i] : 0));
}
} else {
ndp = dataX.length;
for (int i=0; i<dataX.length; i++) {
double x = dataX[i];
double d = data[i];
if (Double.isNaN(d)) continue;
if (x<xLow) {
if (x>xNearLow) {
xNearLow = x;
iNearLow = i;
}
continue;
}
if (x>xHigh) {
if (x<xNearHigh) {
xNearHigh = x;
iNearHigh = i;
}
continue;
}
nGoodPoints++;
yLow = Math.min(yLow , d-(eb ? minusError[i] : 0));
yHigh = Math.max(yHigh , d+(eb ? plusError[i] : 0));
}
// Allow a little extra space for the size of the points
double delta = (yHigh-yLow)/25;
if ( delta == 0 )
delta = 1;
yLow -= delta;
yHigh += delta;
}
if (iNearHigh < 0 && iNearLow < 0 && nGoodPoints == 0) {
yLow = 0;
yHigh = 1;
} else {
if (nGoodPoints == 0) {
if (iNearLow >= 0) {
double d = data[iNearLow];
dm = d-(eb ? minusError[iNearLow] : 0);
dp = d+(eb ? minusError[iNearLow] : 0);
if (dm < yNearLow) yNearLow = dm;
if (dp > yNearHigh) yNearHigh = dp;
}
if (iNearHigh >= 0) {
double d = data[iNearHigh];
dm = d-(eb ? minusError[iNearHigh] : 0);
dp = d+(eb ? minusError[iNearHigh] : 0);
if (dm < yNearLow) yNearLow = dm;
if (dp > yNearHigh) yNearHigh = dp;
}
yLow = yNearLow;
yHigh = yNearHigh;
}
}
if (iNearHigh < 0 && iNearLow < 0 && nGoodPoints == 0) {
yLow = 0;
yHigh = 1;
} else {
if (nGoodPoints == 0) {
if (iNearLow >= 0) {
double d = data[iNearLow];
dm = d-(eb ? minusError[iNearLow] : 0);
dp = d+(eb ? minusError[iNearLow] : 0);
if (dm < yNearLow) yNearLow = dm;
if (dp > yNearHigh) yNearHigh = dp;
}
if (iNearHigh >= 0) {
double d = data[iNearHigh];
dm = d-(eb ? minusError[iNearHigh] : 0);
dp = d+(eb ? minusError[iNearHigh] : 0);
if (dm < yNearLow) yNearLow = dm;
if (dp > yNearHigh) yNearHigh = dp;
}
yLow = yNearLow;
yHigh = yNearHigh;
} else {
// apply normalization
if (normalization != null) {
double factor = 1./normalization.getNormalizationFactor();
yLow *= factor;
yHigh *= factor;
}
}
}
// Round yLow and yHigh for a more sane mumbers
yLow = JASHistUtil.roundDown(yLow, 2);
yHigh = JASHistUtil.roundUp(yHigh, 2);
}
public void update(Observable o, Object arg) {
// Dragons: Likely to be called by different thread
if (o == dataSource) {
// Currently there are several types of update notifications
// - DataUpdate
// - RangeUpdate
// - FinalUpdate
// - TitleUpdate
// - Reset (Ususlly for when partition changes)
HistogramUpdate hu = (HistogramUpdate) arg;
// If we are not binned, we must pass the update onto any fits.
// If we are binned then hopefully our observer will ask us to rebin
// ourselves, and the fit will be informed then.
isBinned = false;
yLimitsValid = false;
xLimitsValid = false;
//if (hu.isReset())
//parent.resetNumberOfBins(this);
//if (hu.isRangeUpdate() || hu.isReset())
//parent.invalidateXAxis();
//long delay = hu.isFinalUpdate() || hu.isReset() ? 0 : 1000;
//parent.scheduleDataUpdate(delay);
parent.update(hu, this);
}
else if (o == style) {
yLimitsValid = false; //. in case error bar settings changed
parent.styleUpdate(this);
}
else if (o == normalization) {
normalizationChanged(false);
}
}
void normalizationChanged(boolean now) {
// treat the same as dataChanged
// Could be more efficient by just renormalizing the cached copy of the data
// It would be more efficient just to change the normalization of the fit,
// there is no need to refit the plot just because the normalization changed
HistogramUpdate hu = new HistogramUpdate(HistogramUpdate.DATA_UPDATE,now);
if (!isBinned || countObservers() == 0) fittableDataSource.update(hu);
isBinned = false;
yLimitsValid = false;
parent.update(hu,this);
}
public boolean hasChanged() {
return !isBinned;
}
// Note YMin and YMax include allowances for error bars, if shown
double getYMin() {
if (!yLimitsValid) calcYLimits();
return yLow;
}
double getYMax() {
if (!yLimitsValid) calcYLimits();
return yHigh;
}
void validate() {
if (!isBinned) doBin();
}
void axisChanged() {
parent.axisChanged(this);
}
public DataSource getDataSource() {
return dataSource;
}
public DataSource getFittableDataSource() {
return fittableDataSource;
}
public boolean getBinnable() {
return true;
}
public int getBins() {
return dataSource instanceof Rebinnable1DHistogramData ? ((Rebinnable1DHistogramData) dataSource).getBins() : ((XYDataSource) dataSource).getNPoints();
}
int getAxisType() {
if (dataSource instanceof Rebinnable1DHistogramData) return ((Rebinnable1DHistogramData) dataSource).getAxisType();
else return ((XYDataSource) dataSource).getAxisType();
}
public JASHistStyle getStyle() {
return style;
}
void destroy() {
if (dataSource instanceof Observable) ((Observable) dataSource).deleteObserver(this);
style.deleteObserver(this);
super.deleteNormalizationObserver();
}
private DataSource dataSource;
JASHist1DHistogramStyle style; // directly accessed by Overlay
private boolean hurry;
private boolean isBinned = false;
private boolean yLimitsValid = false;
private boolean xLimitsValid = false;
private double[] data;
private double[] dataX;
private double[] plusError;
private double[] minusError;
private int xBins;
private double xMin;
private double xMax;
private double xLow;
private double xHigh;
private double yLow;
private double yHigh;
private double xNearLow;
private double xNearHigh;
private double yNearLow;
private double yNearHigh;
private static final HistogramUpdate hu = new HistogramUpdate(HistogramUpdate.DATA_UPDATE,true);
static final long serialVersionUID = -3529869583896718619L;
private FittableDataSource fittableDataSource = new jas.hist.JASHist1DHistogramData.FittableDataSource();
/**
* Fits are done to binned data, so the result of the fit
* depends on the current binning. FittableDataSource is
* a DataSource that reflects the current binning.
*/
class FittableDataSource extends Observable implements XYDataSource, Serializable {
public int getAxisType() {
return DOUBLE;
}
public String getTitle() {
return dataSource.getTitle();
}
void update(Object obj) {
this.setChanged();
this.notifyObservers(obj);
}
void binningChanged() {
this.setChanged();
this.notifyObservers(hu);
}
JASHist1DHistogramData parent() // Used in FitManager
{
return JASHist1DHistogramData.this;
}
public double getMinusError(int index) {
return minusError[index];
}
public double getPlusError(int index) {
return plusError[index];
}
public double getX(int index) {
if (dataX != null) return dataX[index];
else {
double bw = (xHigh-xLow)/xBins;
return xLow+bw*index+bw/2;
}
}
public double getY(int index) {
return data[index];
}
public int getNPoints() {
if (data == null) return 0;
return data.length;
}
public String toString() {
return getTitle();
}
}
}