/******************************************************************************* * 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 gov.nasa.arc.mct.fastplot.bridge; import gov.nasa.arc.mct.fastplot.bridge.PlotConstants.AxisOrientationSetting; import gov.nasa.arc.mct.fastplot.bridge.PlotConstants.PlotLineConnectionType; import gov.nasa.arc.mct.fastplot.view.legend.AbstractLegendEntry; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Polygon; import java.awt.Shape; import java.awt.Stroke; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import plotter.DoubleData; import plotter.xy.CompressingXYDataset; import plotter.xy.CompressingXYDataset.MinMaxChangeListener; import plotter.xy.DefaultCompressor; import plotter.xy.LinearXYPlotLine; import plotter.xy.LinearXYPlotLine.LineMode; import plotter.xy.XYDimension; /** * Holds the information for a Quinn-Curtis data series representing a single * entry on a plot. This includes, the data, the line plot representing it, the * plotting color, the legend, and the code for compressing data as it is put * into the buffers. */ class PlotDataSeries implements MinMaxChangeListener, AbstractPlotDataSeries { public static final Stroke EMPTY_STROKE = new Stroke() { private final Shape EMPTY_SHAPE = new Polygon(); @Override public Shape createStrokedShape(Shape p) { return EMPTY_SHAPE; } }; private final static Logger logger = LoggerFactory .getLogger(PlotDataSeries.class); private PlotterPlot plot; CompressingXYDataset dataset; Color color; LinearXYPlotLine linePlot; LegendEntry legendEntry; LinearXYPlotLine regressionLine; private boolean updateRegressionLine = true; private String dataSetName; PlotDataSeries(PlotterPlot thePlot, String dataSetName, Color theColor) { plot = thePlot; color = theColor; this.dataSetName = dataSetName; setupLinePlot(); setupDataSet(dataSetName); }; /** * Resets the series' buffered data. */ void resetData() { logger.debug("PlotDataSeries.resetData()"); // remove the process variable for this item from the plot plot.getPlotView().getContents().remove(linePlot); if (regressionLine != null) { removeRegressionLine(); } setupLinePlot(); setupDataSet(dataSetName); } private void setupDataSet(String dataSetName) { assert plot.getPlotView() != null : "Plot Object not initalized"; assert linePlot != null; dataset = new CompressingXYDataset(linePlot, new DefaultCompressor()); // Listen for min/max changes on the non-time axis if(plot.getAxisOrientationSetting() == AxisOrientationSetting.X_AXIS_AS_TIME) { dataset.addYMinMaxChangeListener(this); } else { dataset.addXMinMaxChangeListener(this); } // add limit manager // Add alarms which under pin notifications for when data goes out of // range // and the limit triangles are put on the plot. plot.getLimitManager().addLimitAlarms(dataset); } private void setupLinePlot() { /* Note that once a LegendEntry acquires a plot line, it may * apply its own setup to it. */ linePlot = new LinearXYPlotLineWrapper(plot.getPlotView().getXAxis(), plot.getPlotView().getYAxis(), plot.getAxisOrientationSetting() == AxisOrientationSetting.X_AXIS_AS_TIME ? XYDimension.X : XYDimension.Y); LineMode lineMode = LineMode.STEP_XY; if (plot.getPlotAbstraction().getPlotLineConnectionType() == PlotLineConnectionType.DIRECT) { lineMode = LineMode.STRAIGHT; } linePlot.setLineMode(lineMode); linePlot.setForeground(color); if (plot.getPlotAbstraction().getPlotLineDraw().drawMarkers()) { for (int i = 0; i < PlotConstants.MAX_NUMBER_OF_DATA_ITEMS_ON_A_PLOT; i++) { if (PlotLineColorPalette.getColor(i).getRGB() == color.getRGB()) { //linePlot.setPointFill(PlotLineShapePalette.getShape(i)); linePlot.setPointIcon(new PlotMarkerIcon(PlotLineShapePalette.getShape(i))); } } } if (!plot.getPlotAbstraction().getPlotLineDraw().drawLine()) { linePlot.setStroke(EMPTY_STROKE); } plot.getPlotView().getContents().add(linePlot); if (legendEntry != null) { legendEntry.setPlot(linePlot); } if (legendEntry != null && legendEntry.hasRegressionLine()) { addRegressionLine(); } } /** * */ private void addRegressionLine() { regressionLine = new LinearXYPlotLine(plot.getPlotView().getXAxis(), plot.getPlotView().getYAxis(), plot.getAxisOrientationSetting() == AxisOrientationSetting.X_AXIS_AS_TIME ? XYDimension.X : XYDimension.Y); regressionLine.setLineMode(LineMode.STRAIGHT); regressionLine.setForeground(color); regressionLine.setStroke(new BasicStroke(PlotConstants.SLOPE_LINE_WIDTH, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, PlotConstants.dash1, 0.0f)); plot.getPlotView().getContents().add(regressionLine); legendEntry.setHasRegressionLine(true); legendEntry.setRegressionLine(regressionLine); } private void removeRegressionLine() { plot.getPlotView().getContents().remove(regressionLine); plot.getPlotView().getContents().validate(); plot.getPlotView().getContents().repaint(); regressionLine = null; } /** Get the regression line for this data series * @return regressionLine */ public LinearXYPlotLine getRegressionLine() { return regressionLine; } /** Set the regression line object for this data series * @param line a LinearXYPlotLine */ public void setRegressionLine(LinearXYPlotLine line) { regressionLine = line; } public String getDataSetName() { return dataSetName; } void setPlottingColor(Color theColor) { color = theColor; } void setLegend(LegendEntry legend) { legendEntry = legend; legendEntry.setPlotLabelingAlgorithm(plot.plotLabelingAlgorithm); } void setLinePlot(LinearXYPlotLine thePlot) { linePlot = thePlot; } LinearXYPlotLine getPlot() { return linePlot; } public LegendEntry getLegendEntry() { return legendEntry; } Color getColor() { return color; } CompressingXYDataset getData() { return dataset; } /** * Compress data in local plot buffer by fifty percent. */ public void compressByFiftyPercent() { // FIXME: Handle this in the dataset } /** * Search through the data set to identify the maximum value in the range * * @param maxTime * upper bound on time range to search * @param minTime * lower bound on the time range to search * @return minimum value in data set between max and max time (inclusive) */ double[] getMaxValue(long maxTime, long minTime) { double max = -Double.MAX_VALUE; double time = 0; DoubleData nonTimeData = getNonTimeData(dataset); DoubleData timeData = getTimeData(dataset); if (timeData != null) { int n = timeData.getLength(); for (int i = 0; i < n; i++) { double nonTime = nonTimeData.get(i); // System.out.println("min time " + minTime + " Max Time " + // maxTime + " timeData " + timeData[i] + " = " + // nonTimeData[i]); double time2 = timeData.get(i); if (time2 >= minTime && time2 <= maxTime) { if (nonTime > max) { max = nonTime; time = time2; } } } } double[] result = new double[2]; result[0] = max; result[1] = time; return result; } /** * Search through the data set to identify the minimum value in the range * * @param maxTime * upper bound on time range to search * @param minTime * lower bound on the time range to search * @return minimum value in data set between min and max time (inclusive) */ double[] getMinValue(long maxTime, long minTime) { double min = Double.MAX_VALUE; double time = 0; DoubleData nonTimeData = getNonTimeData(dataset); DoubleData timeData = getTimeData(dataset); if (timeData != null) { int n = timeData.getLength(); for (int i = 0; i < n; i++) { double time2 = timeData.get(i); if (time2 >= minTime && time2 <= maxTime) { double nonTime = nonTimeData.get(i); if (nonTime < min) { min = nonTime; time = time2; } } } } double[] result = new double[2]; result[0] = min; result[1] = time; return result; } DoubleData getNonTimeData(CompressingXYDataset dataSet) { if(plot.getAxisOrientationSetting() == AxisOrientationSetting.X_AXIS_AS_TIME) { return dataSet.getYData(); } else { return dataSet.getXData(); } } DoubleData getTimeData(CompressingXYDataset dataSet) { if(plot.getAxisOrientationSetting() == AxisOrientationSetting.X_AXIS_AS_TIME) { return dataSet.getXData(); } else { return dataSet.getYData(); } } @Override public void minMaxChanged(CompressingXYDataset dataset, XYDimension dimension) { plot.minMaxChanged(); } @Override public String toString() { int size = dataset.getPointCount(); if(size == 0) { return "Full Data: Number data points 0\n"; } else { return "Full Data: current value " + dataset.getYData().get(size - 1) + " Number data points " + size + "\n"; } } public void updateRegressionLine() { if (!updateRegressionLine) { return; } if (getLegendEntry() != null) { if (getLegendEntry().hasRegressionLine()) { if (regressionLine == null) { addRegressionLine(); } } else { if (regressionLine != null) { removeRegressionLine(); } return; } regressionLine.removeAllPoints(); // Not enough data for regression if (getData().getPointCount() < legendEntry.getNumberRegressionPoints()) { return; } double[] xRegData = new double[legendEntry.getNumberRegressionPoints()]; double[] yRegData = new double[legendEntry.getNumberRegressionPoints()]; int xDataLength = getData().getXData().getLength(); int yDataLength = getData().getYData().getLength(); int shift = 0; //Get last n x and y values that are valid values for (int i = legendEntry.getNumberRegressionPoints(); i > 0; i--) { if (((xDataLength - (legendEntry.getNumberRegressionPoints() - i) - 1 - shift) < 0) || ((yDataLength - (legendEntry.getNumberRegressionPoints() - i) - 1 - shift) < 0)) { // Not enough data for regression return; } while ( Double.isNaN(getData().getXData().get(xDataLength - (legendEntry.getNumberRegressionPoints() - i) - 1 - shift)) || Double.isNaN(getData().getYData().get(yDataLength - (legendEntry.getNumberRegressionPoints() - i) - 1 - shift))) { if ((yDataLength - (legendEntry.getNumberRegressionPoints() - i) - 1 - (shift + 1)) < 0 || (xDataLength - (legendEntry.getNumberRegressionPoints() - i) - 1 - (shift + 1)) < 0) { // Not enough data for regression return; } else { shift++; } } xRegData[i-1] = getData().getXData().get(xDataLength - (legendEntry.getNumberRegressionPoints() - i) - 1 - shift); yRegData[i-1] = getData().getYData().get(yDataLength - (legendEntry.getNumberRegressionPoints() - i) - 1 - shift); } double x = xRegData[legendEntry.getNumberRegressionPoints() - 1], y=yRegData[legendEntry.getNumberRegressionPoints() - 1]; HighPrecisionLinearRegression lr = new HighPrecisionLinearRegression(xRegData, yRegData); if (lr.getSlopeAsBigDecimal() != null && lr.getIntercept() != null && !Double.isNaN(lr.getSlope()) && !Double.isInfinite(lr.getSlope()) && !Double.isInfinite(lr.getIntercept().doubleValue()) && !Double.isNaN(lr.getIntercept().doubleValue()) ) { // Per Jira 3358 specs and customer discussions, translate regression line so that // origin is at last data point regressionLine.add(x, y); // Add origin of line if(linePlot.getIndependentDimension() == XYDimension.X) { regressionLine.add(Long.valueOf(plot.getMaxTime()).doubleValue(), lr.calculateY(Long.valueOf(plot.getMaxTime()).doubleValue()) + (y - lr.calculateY(x))); // Add terminus of line } else { regressionLine.add(lr.calculateX(Long.valueOf(plot.getMaxTime()).doubleValue()) + (x - lr.calculateX(y)),Long.valueOf(plot.getMaxTime()).doubleValue()); // Add terminus of line } } } } /** Get whether or not to update the prediction line (not updated if the * most recent data is not shown on a zoomed/panned plot. * @return updateRegressionLine */ public boolean isUpdateRegressionLine() { return updateRegressionLine; } /** Set whether or not to update the prediction line (not updated if the * most recent data is not shown on a zoomed/panned plot. * @param updateRegressionLine boolean indicator */ public void setUpdateRegressionLine(boolean updateRegressionLine) { this.updateRegressionLine = updateRegressionLine; } @Override public void setLegendEntry(AbstractLegendEntry entry) { // TODO Auto-generated method stub } }