/*******************************************************************************
* 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.components.FeedProvider;
import gov.nasa.arc.mct.fastplot.bridge.PlotConstants.AxisOrientationSetting;
import gov.nasa.arc.mct.fastplot.bridge.PlotConstants.LimitAlarmState;
import gov.nasa.arc.mct.fastplot.bridge.PlotConstants.TimeAxisSubsequentBoundsSetting;
import gov.nasa.arc.mct.fastplot.bridge.PlotConstants.XAxisMaximumLocationSetting;
import gov.nasa.arc.mct.fastplot.bridge.PlotConstants.YAxisMaximumLocationSetting;
import gov.nasa.arc.mct.fastplot.bridge.controls.BoundaryArrow;
import gov.nasa.arc.mct.fastplot.bridge.controls.ControllableAxis;
import gov.nasa.arc.mct.fastplot.bridge.controls.CornerResetButton;
import gov.nasa.arc.mct.fastplot.bridge.controls.LocalControlKeyEventDispatcher;
import gov.nasa.arc.mct.fastplot.bridge.controls.PanControls;
import gov.nasa.arc.mct.fastplot.bridge.controls.ZoomControls;
import gov.nasa.arc.mct.fastplot.scatter.NonTimeFixedBoundManager;
import gov.nasa.arc.mct.fastplot.settings.LineSettings;
import gov.nasa.arc.mct.fastplot.settings.PlotConfiguration;
import gov.nasa.arc.mct.fastplot.settings.PlotConfigurationDelegator;
import gov.nasa.arc.mct.fastplot.settings.PlotSettings;
import gov.nasa.arc.mct.fastplot.utils.AbbreviatingPlotLabelingAlgorithm;
import gov.nasa.arc.mct.fastplot.view.Axis;
import gov.nasa.arc.mct.fastplot.view.LegendEntryPopupMenuFactory;
import gov.nasa.arc.mct.fastplot.view.PinSupport;
import gov.nasa.arc.mct.fastplot.view.Pinnable;
import gov.nasa.arc.mct.fastplot.view.PlotViewManifestation;
import gov.nasa.arc.mct.fastplot.view.legend.AbstractLegendEntry;
import gov.nasa.arc.mct.gui.FeedView.SynchronizationControl;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.Timer;
import java.util.TimerTask;
import java.util.TreeMap;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of the general plot interface.
*
* Construct using the builder pattern.
*
* Example:
* <code>Plot.Builder(new <GeneralPlottingPackageInterface).plotName("Your Plot Name").build();</code>
*
* RefinedAbstraction in bridge pattern.
*/
public class PlotView extends PlotConfigurationDelegator implements PlotAbstraction {
private final static Logger logger = LoggerFactory.getLogger(PlotView.class);
private static final Timer timer = new Timer();
// Manifestation holding this plot
private PlotViewManifestation plotUser;
// Used in time synchronization line mode.
private SynchronizationControl synControl;
private Class <? extends AbstractPlottingPackage> plotPackage;
private String plotName;
/* Appearance Constants */
// - Fonts
private Font timeAxisFont;
// Thickness of the plotted lines.
private int plotLineThickness;
private Color plotBackgroundFrameColor;
private Color plotAreaBackgroundColor;
// - Axis
// -- x-axis
// Point where x-axis intercepts y axis
private int timeAxisIntercept;
//color for drawing x-axis
private Color timeAxisColor;
// x-axis labels
private Color timeAxisLabelColor;
private Color timeAxisLabelTextColor;
// format of the date when shown on the x-axis
private String timeAxisDataFormat;
// -- y-axis
private Color nonTimeAxisColor;
// - Gridlines
private Color gridLineColor;
/* Scrolling and scaling behaviors */
// Number of sample to accumulate before autoscaling the y-axis. This
// prevents rapid changing of the y axis.
private int minSamplesForAutoScale;
private boolean compressionEnabled;
private boolean localControlsEnabled;
private int numberOfSubPlots;
/** The list of sub plots. */
public List<AbstractPlottingPackage> subPlots;
/** The plot panel. */
JPanel plotPanel;
/** Map for containing the data set name to sub-group. */
public Map<String, Set<AbstractPlottingPackage>> dataSetNameToSubGroupMap = new HashMap<String, Set<AbstractPlottingPackage>>();
/** Map for containing the data set name to display map. */
public Map<String, String> dataSetNameToDisplayMap = new HashMap<String, String>();
private AbbreviatingPlotLabelingAlgorithm plotLabelingAlgorithm = new AbbreviatingPlotLabelingAlgorithm();;
/** List of plot subjects. */
List<PlotSubject> subPlotsToIgnoreNextUpdateFrom = new ArrayList<PlotSubject>();
/** Time axis at start of update cycle. */
GregorianCalendar timeAxisMaxAtStartOfDataUpdateCycle = new GregorianCalendar();
/** Lock updates flag. */
boolean lockUpdates = false;
private PinSupport pinSupport = new PinSupport() {
protected void informPinned(boolean pinned) {
if(pinned) {
pause();
} else {
unpause();
}
}
};
private Pinnable timeSyncLinePin = createPin();
private Axis timeAxis = new Axis();
private Pinnable timeAxisUserPin = timeAxis.createPin();
private TimerTask updateTimeBoundsTask;
private AbstractAxis plotTimeAxis;
/**
* Sets the plot view manifestation.
* @param theManifestation - the plot view manifestation.
*/
public void setManifestation(PlotViewManifestation theManifestation) {
if (theManifestation == null ) {
throw new IllegalArgumentException("Plot must not have a null user");
}
plotUser = theManifestation;
for (AbstractPlottingPackage p: subPlots) {
p.setPlotAbstraction(this);
}
}
@Override
public JPanel getPlotPanel() {
return plotPanel;
}
@Override
public void addDataSet(String dataSetName) {
addDataSet(dataSetName.toLowerCase(), getNextColor(subPlots.size()-1));
}
@Override
public void addDataSet(String dataSetName, Color plottingColor) {
throwIllegalArgumentExcpetionIfWeHaveNoPlots();
getLastPlot().addDataSet(dataSetName.toLowerCase(), plottingColor);
String name = dataSetName.toLowerCase();
Set<AbstractPlottingPackage> set = dataSetNameToSubGroupMap.get(name);
if(set == null) {
set = new HashSet<AbstractPlottingPackage>();
dataSetNameToSubGroupMap.put(name, set);
}
set.add(getLastPlot());
dataSetNameToDisplayMap.put(dataSetName.toLowerCase(), dataSetName);
}
@Override
public void addDataSet(String dataSetName, String displayName) {
throwIllegalArgumentExcpetionIfWeHaveNoPlots();
getLastPlot().addDataSet(dataSetName.toLowerCase(), getNextColor(subPlots.size()-1), displayName);
String name = dataSetName.toLowerCase();
Set<AbstractPlottingPackage> set = dataSetNameToSubGroupMap.get(name);
if(set == null) {
set = new HashSet<AbstractPlottingPackage>();
dataSetNameToSubGroupMap.put(name, set);
}
set.add(getLastPlot());
dataSetNameToDisplayMap.put(dataSetName.toLowerCase(), displayName);
}
/**
* Adds the data set per subgroup index, data set name and display name.
* @param subGroupIndex - the subgroup index.
* @param dataSetName - data set name.
* @param displayName - base display name.
*/
public void addDataSet(int subGroupIndex, String dataSetName, String displayName) {
throwIllegalArgumentExcpetionIfIndexIsNotInSubPlots(subGroupIndex);
String lowerCaseDataSetName = dataSetName.toLowerCase();
int actualIndex = subGroupIndex;
subPlots.get(actualIndex).addDataSet(lowerCaseDataSetName,
getNextColor(subGroupIndex), displayName);
Set<AbstractPlottingPackage> set = dataSetNameToSubGroupMap
.get(lowerCaseDataSetName);
if (set == null) {
set = new HashSet<AbstractPlottingPackage>();
dataSetNameToSubGroupMap.put(lowerCaseDataSetName, set);
}
set.add(subPlots.get(actualIndex));
dataSetNameToDisplayMap.put(lowerCaseDataSetName, displayName);
}
/**
* Adds the data set per subgroup index, data set name and display name.
* @param subGroupIndex - the subgroup index.
* @param dataSetName - data set name.
* @param displayName - base display name.
*/
public void addDataSet(int subGroupIndex, String dataSetName, AbstractLegendEntry legendEntry) {
throwIllegalArgumentExcpetionIfIndexIsNotInSubPlots(subGroupIndex);
String lowerCaseDataSetName = dataSetName.toLowerCase();
int actualIndex = subGroupIndex;
subPlots.get(actualIndex).getLegendManager().addLegendEntry(legendEntry);
subPlots.get(actualIndex).addDataSet(lowerCaseDataSetName,
getNextColor(subGroupIndex), legendEntry);
Set<AbstractPlottingPackage> set = dataSetNameToSubGroupMap
.get(lowerCaseDataSetName);
if (set == null) {
set = new HashSet<AbstractPlottingPackage>();
dataSetNameToSubGroupMap.put(lowerCaseDataSetName, set);
}
set.add(subPlots.get(actualIndex));
dataSetNameToDisplayMap.put(lowerCaseDataSetName, legendEntry.getDisplayedName());
}
/**
* Adds the popup menus to plot legend entry.
*/
public void addPopupMenus() {
LegendEntryPopupMenuFactory popupManager = new LegendEntryPopupMenuFactory(plotUser);
for (int index = 0; index < subPlots.size(); index++) {
AbstractPlottingPackage plot = subPlots.get(index);
for (LegendEntry entry : plot.getLegendManager().getLegendEntryList()) {
entry.setPopup(popupManager);
}
}
}
/**
* Get per-line settings currently in use for this stack of plots.
* Each element of the returned list corresponds,
* in order, to the sub-plots displayed, and maps subscription ID to a
* LineSettings object describing how its plot line should be drawn.
* @return a list of subscription->setting mappings for this plot
*/
public List<Map<String, LineSettings>> getLineSettings() {
List<Map<String,LineSettings>> settingsAssignments = new ArrayList<Map<String,LineSettings>>();
for (int subPlotIndex = 0; subPlotIndex < subPlots.size(); subPlotIndex++) {
Map<String, LineSettings> settingsMap = new HashMap<String, LineSettings>();
settingsAssignments.add(settingsMap);
AbstractPlottingPackage plot = subPlots.get(subPlotIndex);
for (LegendEntry entry : plot.getLegendManager().getLegendEntryList()) {
settingsMap.put(entry.getDataSetName(), entry.getLineSettings());
}
}
return settingsAssignments;
}
/**
* Set line settings for use in this stack of plots.
* Each element corresponds, in order, to the sub-plots displayed, and maps
* subscription ID to the line settings described by a LineSettings object
* @param lineSettings a list of subscription->line setting mappings for this plot
*/
public void setLineSettings(
List<Map<String, LineSettings>> lineSettings) {
if (lineSettings != null) {
for (int subPlotIndex = 0; subPlotIndex < lineSettings.size() && subPlotIndex < subPlots.size(); subPlotIndex++) {
AbstractPlottingPackage plot = subPlots.get(subPlotIndex);
for (Entry<String, LineSettings> entry : lineSettings.get(subPlotIndex).entrySet()) {
AbstractPlotDataSeries series = plot.getPlotDataManager().getNamedDataSeries(entry.getKey());
if (series != null) {
AbstractLegendEntry legendEntry = series.getLegendEntry();
if (legendEntry instanceof LegendEntry) {
((LegendEntry) legendEntry).setLineSettings(entry.getValue());
}
}
}
}
}
}
@Override
public boolean isKnownDataSet(String setName) {
assert setName != null : "data set is null";
for (AbstractPlottingPackage p : subPlots) {
if (p.isKnownDataSet(setName.toLowerCase())) return true;
}
return false;
//return dataSetNameToSubGroupMap.containsKey(setName.toLowerCase());
}
@Override
public void refreshDisplay() {
for (AbstractPlottingPackage p: subPlots) {
p.refreshDisplay();
}
}
public void initialDataRequest() {
plotUser.requestDataRefresh(getLastPlot().getCurrentTimeAxisMin(), getLastPlot().getCurrentTimeAxisMax());
}
@Override
public void updateLegend(String dataSetName, FeedProvider.RenderingInfo info) {
String dataSetNameLower = dataSetName.toLowerCase();
if (dataSetNameToSubGroupMap.containsKey(dataSetNameLower)) {
for(AbstractPlottingPackage plot : dataSetNameToSubGroupMap.get(dataSetNameLower)) {
plot.updateLegend(dataSetNameLower, info);
}
}
}
@Override
public LimitAlarmState getNonTimeMaxAlarmState(int subGroupIndex) {
throwIllegalArgumentExcpetionIfIndexIsNotInSubPlots(subGroupIndex);
return subPlots.get(subGroupIndex).getDependentMaxAlarmState();
}
@Override
public LimitAlarmState getNonTimeMinAlarmState(int subGroupIndex) {
throwIllegalArgumentExcpetionIfIndexIsNotInSubPlots(subGroupIndex);
return subPlots.get(subGroupIndex).getDependentMinAlarmState();
}
@Override
public void showTimeSyncLine(GregorianCalendar time) {
assert time != null;
timeSyncLinePin.setPinned(true);
for (AbstractPlottingPackage p: subPlots) {
p.showTimeSyncLine(time);
}
}
@Override
public void removeTimeSyncLine() {
timeSyncLinePin.setPinned(false);
for (AbstractPlottingPackage p: subPlots) {
p.removeTimeSyncLine();
}
}
@Override
public boolean isTimeSyncLineVisible() {
return getLastPlot().isTimeSyncLineVisible();
}
@Override
public void initiateGlobalTimeSync(GregorianCalendar time) {
synControl = plotUser.synchronizeTime(time.getTimeInMillis());
}
@Override
public void updateGlobalTimeSync(GregorianCalendar time) {
if(synControl == null) {
synControl = plotUser.synchronizeTime(time.getTimeInMillis());
} else {
synControl.update(time.getTimeInMillis());
}
}
@Override
public void notifyGlobalTimeSyncFinished() {
if (synControl!=null) {
synControl.synchronizationDone();
}
removeTimeSyncLine();
}
@Override
public boolean inTimeSyncMode() {
return getLastPlot().inTimeSyncMode();
}
private Color getNextColor(int subGroupIndex) {
throwIllegalArgumentExcpetionIfIndexIsNotInSubPlots(subGroupIndex);
if (subPlots.get(subGroupIndex).getDataSetSize() < PlotLineColorPalette.getColorCount()) {
return PlotLineColorPalette.getColor(subPlots.get(subGroupIndex).getDataSetSize());
} else {
// Exceeded the number of colors in the pallet.
return PlotConstants.ROLL_OVER_PLOT_LINE_COLOR;
}
}
@Override
public double getNonTimeMaxCurrentlyDisplayed() {
return getLastPlot().getNonTimeMaxDataValueCurrentlyDisplayed();
}
@Override
public double getNonTimeMinCurrentlyDisplayed() {
return getLastPlot().getNonTimeMinDataValueCurrentlyDisplayed();
}
@Override
public String toString() {
assert plotPackage != null : "Plot package not initalized";
return "Plot: + " + plotName + "\n" + plotPackage.toString();
}
/**
* Construct plots using the builder pattern.
*
*/
public static class Builder {
// Required parameters
private Class<? extends AbstractPlottingPackage> plotPackage;
//Optional parameters
// default values give a "traditional" chart with time on the x-axis etc.
private String plotName = "Plot Name Undefined";
private PlotConfiguration settings = new PlotSettings();
// initial settings
private Font timeAxisFont = PlotConstants.DEFAULT_TIME_AXIS_FONT;
private int plotLineThickness = PlotConstants.DEFAULT_PLOTLINE_THICKNESS ;
private Color plotBackgroundFrameColor = PlotConstants.DEFAULT_PLOT_FRAME_BACKGROUND_COLOR;
private Color plotAreaBackgroundColor = PlotConstants.DEFAULT_PLOT_AREA_BACKGROUND_COLOR;
private int timeAxisIntercept = PlotConstants.DEFAULT_TIME_AXIS_INTERCEPT;
private Color timeAxisColor = PlotConstants.DEFAULT_TIME_AXIS_COLOR;
private Color timeAxisLabelColor = PlotConstants.DEFAULT_TIME_AXIS_LABEL_COLOR;
private String timeAxisDateFormat = PlotConstants.DEFAULT_TIME_AXIS_DATA_FORMAT;
private Color nonTimeAxisColor = PlotConstants.DEFAULT_NON_TIME_AXIS_COLOR;
private Color gridLineColor = PlotConstants.DEFAULT_GRID_LINE_COLOR;
private int minSamplesForAutoScale = PlotConstants.DEFAULT_MIN_SAMPLES_FOR_AUTO_SCALE;
private boolean compressionEnabled = PlotConstants.COMPRESSION_ENABLED_BY_DEFAULT;
private int numberOfSubPlots = PlotConstants.DEFAULT_NUMBER_OF_SUBPLOTS;
private boolean localControlsEnabled = PlotConstants.LOCAL_CONTROLS_ENABLED_BY_DEFAULT;
private AbbreviatingPlotLabelingAlgorithm plotLabelingAlgorithm = new AbbreviatingPlotLabelingAlgorithm();
/**
* Specifies the required parameters for constructing a plot.
* @param selectedPlotPackage plotting package to render the plot
*/
public Builder(Class<? extends AbstractPlottingPackage>selectedPlotPackage) {
this.plotPackage = selectedPlotPackage;
}
/**
* Specify the plot's user readable name.
* @param initPlotName the initial plot name.
* @return builder the plot view.
*/
public Builder plotName(String initPlotName) {
plotName = initPlotName;
return this;
}
/**
* Specify the grouped plot settings for this chart
* @param plotSettings
* @return
*/
public Builder plotSettings(PlotConfiguration plotSettings) {
settings = plotSettings;
return this;
}
/**
* Specify the size of the font of the labels on the time axis.
* @param theTimeAxisFontSize font size.
* @return the builder the plot view.
*/
public Builder timeAxisFontSize(int theTimeAxisFontSize) {
timeAxisFont = new Font(timeAxisFont.getFontName(), Font.PLAIN, theTimeAxisFontSize);
return this;
}
/**
* Specify the font that will be used to draw the labels on the axis axis.
* This parameter overrides the time axis font size parameter when specified.
* @param theTimeAxisFont the font size.
* @return the builder the plot view.
*/
public Builder timeAxisFont(Font theTimeAxisFont) {
timeAxisFont = theTimeAxisFont;
return this;
}
/**
* Specify the thickness of the line used to plot data on the plot.
* @param theThickness the thickness.
* @return the builder the plot view.
*/
public Builder plotLineThickness(int theThickness) {
plotLineThickness = theThickness;
return this;
}
/**
* Specify the color of the frame surrounding the plot area.
* @param theBackgroundColor the color.
* @return the builder the plot view.
*/
public Builder plotBackgroundFrameColor(Color theBackgroundColor) {
plotBackgroundFrameColor = theBackgroundColor;
return this;
}
/**
* Specify the background color of the plot area.
* @param thePlotAreaColor the color.
* @return the builder the plot view.
*/
public Builder plotAreaBackgroundColor (Color thePlotAreaColor) {
plotAreaBackgroundColor = thePlotAreaColor;
return this;
}
/**
* Specify the point at which the time axis intercepts the non time axis.
* @param theIntercept the intercept point.
* @return the builder the plot view.
*/
public Builder timeAxisIntercept(int theIntercept) {
timeAxisIntercept = theIntercept;
return this;
}
/**
* Specify the color of the time axis.
* @param theTimeAxisColor the color.
* @return the builder the plot view.
*/
public Builder timeAxisColor(Color theTimeAxisColor) {
timeAxisColor = theTimeAxisColor;
return this;
}
/**
* Specify color of text on the time axis.
* @param theTimeAxisTextColor the color.
* @return the builder the plot view.
*/
public Builder timeAxisTextColor(Color theTimeAxisTextColor) {
timeAxisLabelColor = theTimeAxisTextColor;
return this;
}
/**
* Set the format of how time information is printed on time axis labels.
* @param theTimeAxisDateFormat the format.
* @return the builder the plot view.
*/
public Builder timeAxisDateFormat(String theTimeAxisDateFormat) {
timeAxisDateFormat = theTimeAxisDateFormat;
return this;
}
/**
* Set the color of the non time axis.
* @param theNonTimeAxisColor the color.
* @return the builder the plot view.
*/
public Builder nonTimeAxisColor(Color theNonTimeAxisColor) {
nonTimeAxisColor = theNonTimeAxisColor;
return this;
}
/**
* Set the color of the plot gridlines.
* @param theGridLineColor the color.
* @return the builder the plot view.
*/
public Builder gridLineColor(Color theGridLineColor) {
gridLineColor = theGridLineColor;
return this;
}
/**
* The minimum number of samples to accumulate out of range before an autoscale occurs. This
* prevents rapid autoscaling on every plot action.
* @param theMinSamplesForAutoScale the number of samples.
* @return the plot view.
*/
public Builder minSamplesForAutoScale(int theMinSamplesForAutoScale) {
minSamplesForAutoScale = theMinSamplesForAutoScale;
return this;
}
/**
* Specify if the plot is to compress its data to match the screen resolution.
* @param state true to compress, false otherwise.
* @return the builder the plot view.
*/
public Builder isCompressionEnabled(boolean state) {
compressionEnabled = state;
return this;
}
/**
* Specify the number of subplots in this plotview.
* @param theNumberOfSubPlots the number of sub-plots.
* @return the builder the plot view.
*/
public Builder numberOfSubPlots(int theNumberOfSubPlots) {
numberOfSubPlots = theNumberOfSubPlots;
return this;
}
/**
* Turn the plot local controls on and off.
* @param theIsEnabled true enabled; otherwise false.
* @return builder the plot view.
*/
public Builder localControlsEnabled(boolean theIsEnabled) {
localControlsEnabled = theIsEnabled;
return this;
}
/**
* Specify the plot abbreviation labeling algorithm.
* @param thePlotLabelingAlgorithm the plot labeling algorithm.
* @return builder the plot view.
*/
public Builder plotLabelingAlgorithm(AbbreviatingPlotLabelingAlgorithm thePlotLabelingAlgorithm) {
plotLabelingAlgorithm = thePlotLabelingAlgorithm;
assert plotLabelingAlgorithm != null : "Plot labeling algorithm should NOT be NULL at this point.";
return this;
}
/**
* Build a new plot instance and return it.
* @return the new plot instance.
*/
public PlotView build() {
return new PlotView(this);
}
}
// Private constructor. Construct using builder pattern.
private PlotView(Builder builder) {
super(builder.settings);
plotPackage = builder.plotPackage;
plotName = builder.plotName;
timeAxisFont = builder.timeAxisFont;
plotLineThickness = builder.plotLineThickness;
plotBackgroundFrameColor = builder.plotBackgroundFrameColor;
plotAreaBackgroundColor = builder.plotAreaBackgroundColor;
timeAxisIntercept = builder.timeAxisIntercept;
timeAxisColor = builder.timeAxisColor;
timeAxisLabelTextColor = builder.timeAxisLabelColor;
timeAxisDataFormat = builder.timeAxisDateFormat;
nonTimeAxisColor = builder.nonTimeAxisColor;
gridLineColor = builder.gridLineColor;
minSamplesForAutoScale = builder.minSamplesForAutoScale;
compressionEnabled = builder.compressionEnabled;
numberOfSubPlots = builder.numberOfSubPlots;
localControlsEnabled = builder.localControlsEnabled;
plotLabelingAlgorithm = builder.plotLabelingAlgorithm;
plotPanel = new JPanel();
plotPanel.addAncestorListener(new AncestorListener() {
@Override
public synchronized void ancestorRemoved(AncestorEvent event) {
if(updateTimeBoundsTask != null) {
updateTimeBoundsTask.cancel();
updateTimeBoundsTask = null;
}
}
@Override
public void ancestorMoved(AncestorEvent event) {
}
@Override
public synchronized void ancestorAdded(AncestorEvent event) {
for(AbstractPlottingPackage p : subPlots) {
p.updateCompressionRatio();
}
updateTimeBoundsTask = new TimerTask() {
@Override
public void run() {
try {
timeReachedEnd();
} catch(Exception e) {
// We need to catch exceptions because they can kill the timer.
logger.error(e.toString(), e);
}
}
};
timer.schedule(updateTimeBoundsTask, 0, 1000);
}
});
/* Make sure key events get dispatched to local controls (which enable for Alt/Ctrl/etc...) */
plotPanel.addAncestorListener(new LocalControlKeyEventDispatcher(this));
GridBagLayout layout = new StackPlotLayout(this);
plotPanel.setLayout(layout);
subPlots = new ArrayList<AbstractPlottingPackage>(numberOfSubPlots);
// create the specified number of subplots
for (int i=0; i< numberOfSubPlots; i++) {
AbstractPlottingPackage newPlot;
try {
newPlot = plotPackage.newInstance();
boolean isTimeLabelEnabled = i == (numberOfSubPlots -1);
newPlot.createChart(timeAxisFont,
plotLineThickness,
plotBackgroundFrameColor,
plotAreaBackgroundColor,
timeAxisIntercept,
timeAxisColor,
timeAxisLabelColor,
timeAxisLabelTextColor,
timeAxisDataFormat,
nonTimeAxisColor,
gridLineColor,
minSamplesForAutoScale,
compressionEnabled,
isTimeLabelEnabled,
localControlsEnabled,
this, plotLabelingAlgorithm);
// TODO: Move control attachment to its own class?
attachLocalControls(newPlot);
newPlot.setPlotLabelingAlgorithm(plotLabelingAlgorithm);
subPlots.add(newPlot);
newPlot.registerObservor(this);
logger.debug("plotLabelingAlgorithm.getPanelContextTitleList().size()="
+ plotLabelingAlgorithm.getPanelContextTitleList().size()
+ ", plotLabelingAlgorithm.getCanvasContextTitleList().size()="
+ plotLabelingAlgorithm.getCanvasContextTitleList().size());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
if (getAxisOrientationSetting() == AxisOrientationSetting.Y_AXIS_AS_TIME) {
Collections.reverse(subPlots);
}
for (AbstractPlottingPackage subPlot: subPlots) {
JComponent subPanel = subPlot.getPlotComponent();
plotPanel.add(subPanel);
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.BOTH;
c.weightx = 1;
c.weighty = 1;
if(getAxisOrientationSetting() == AxisOrientationSetting.X_AXIS_AS_TIME) {
c.gridwidth = GridBagConstraints.REMAINDER;
}
layout.setConstraints(subPanel, c);
}
if (builder.settings.getPinTimeAxis()) {
timeAxisUserPin.setPinned(true);
// update the corner reset buttons after the plot is visible
SwingUtilities.invokeLater(new Runnable() {
public void run() {
for(AbstractPlottingPackage subPlot : subPlots) {
subPlot.updateResetButtons();
}
}
});
}
}
private void attachLocalControls(AbstractPlottingPackage newPlot) {
List<ControllableAxis> observableAxes = new ArrayList<ControllableAxis>();
for (AbstractAxis axis : newPlot.getAxes()) {
if (axis != null && axis.getVisibleOrientation() != null) {
ControllableAxis a = new ControllableAxis(newPlot, axis);
observableAxes.add(a);
newPlot.attachLocalControl(new PanControls(a));
newPlot.attachLocalControl(new ZoomControls(a));
newPlot.attachLocalControl(new CornerResetButton(a));
for (AbstractAxisBoundManager mgr : newPlot.getBoundManagers(a.getVisibleOrientation())) {
newPlot.attachLocalControl(new BoundaryArrow(mgr));
}
}
}
newPlot.attachLocalControl(new CornerResetButton(observableAxes.toArray(new ControllableAxis[observableAxes.size()])));
}
/**
* Gets the pinnable time axis by user.
* @return pinnable time axis.
*/
public Pinnable getTimeAxisUserPin() {
return timeAxisUserPin;
}
/**
* Gets the plot labeling algorithm.
* @return abbreviating plot labeling algorithm.
*/
public AbbreviatingPlotLabelingAlgorithm getPlotLabelingAlgorithm() {
return plotLabelingAlgorithm;
}
/**
* Sets the plot labeling algorithm.
* @param thePlotLabelingAlgorithm the plot labeling algorithm.
*/
public void setPlotLabelingAlgorithm(AbbreviatingPlotLabelingAlgorithm thePlotLabelingAlgorithm) {
plotLabelingAlgorithm = thePlotLabelingAlgorithm;
}
@Override
public AbstractPlottingPackage returnPlottingPackage() {
return getLastPlot();
}
@Override
public void setCompressionEnabled(boolean compression) {
for (AbstractPlottingPackage p: subPlots) {
p.setCompressionEnabled(compression);
}
}
@Override
public boolean isCompressionEnabled() {
return getLastPlot().isCompressionEnabled();
}
@Override
public void requestPlotData(GregorianCalendar startTime, GregorianCalendar endTime) {
plotUser.requestDataRefresh(startTime, endTime);
}
private void requestPredictivePlotData(GregorianCalendar startTime, GregorianCalendar endTime) {
plotUser.requestPredictiveData(startTime, endTime);
}
@Override
public void informUpdateDataEventStarted() {
timeAxisMaxAtStartOfDataUpdateCycle.setTimeInMillis(this.getLastPlot().getCurrentTimeAxisMax().getTimeInMillis());
for (AbstractPlottingPackage p: subPlots) {
p.informUpdateCachedDataStreamStarted();
}
}
@Override
public void informUpdateFromFeedEventStarted() {
timeAxisMaxAtStartOfDataUpdateCycle.setTimeInMillis(this.getLastPlot().getCurrentTimeAxisMax().getTimeInMillis());
for (AbstractPlottingPackage p: subPlots) {
p.informUpdateFromLiveDataStreamStarted();
}
}
@Override
public void informUpdateDataEventCompleted() {
for (AbstractPlottingPackage p: subPlots) {
p.informUpdateCacheDataStreamCompleted();
}
syncTimeAxisAcrossPlots();
}
@Override
public void informUpdateFromFeedEventCompleted() {
for (AbstractPlottingPackage p: subPlots) {
p.informUpdateFromLiveDataStreamCompleted();
}
syncTimeAxisAcrossPlots();
}
/**
* Synchronizes the time axis across all plots.
*/
void syncTimeAxisAcrossPlots() {
long maxAtStart = timeAxisMaxAtStartOfDataUpdateCycle.getTimeInMillis();
long currentMaxTime = maxAtStart;
long currentMinTime = maxAtStart;
for (AbstractPlottingPackage p: subPlots) {
long max = p.getMaxTime();
if (max > currentMaxTime) {
currentMaxTime = max;
currentMinTime = p.getMinTime();
}
}
if (currentMaxTime > maxAtStart) {
boolean inverted;
if(getAxisOrientationSetting() == AxisOrientationSetting.X_AXIS_AS_TIME) {
inverted = getXAxisMaximumLocation() == XAxisMaximumLocationSetting.MAXIMUM_AT_LEFT;
} else {
inverted = getYAxisMaximumLocation() == YAxisMaximumLocationSetting.MAXIMUM_AT_BOTTOM;
}
long start;
long end;
if(inverted) {
start = currentMaxTime;
end = currentMinTime;
} else {
start = currentMinTime;
end = currentMaxTime;
}
for (AbstractPlottingPackage p: subPlots) {
p.setTimeAxisStartAndStop(start, end);
}
}
}
@Override
public long getCurrentMCTTime() {
return plotUser.getCurrentMCTTime();
}
/**
* Gets the plot user view manifestation.
* @return plotUser the plot user view manifestation.
*/
public PlotViewManifestation getPlotUser() {
return plotUser;
}
/**
* Gets the last plot.
* @return abstract plotting package.
*/
AbstractPlottingPackage getLastPlot() {
throwIllegalArgumentExcpetionIfWeHaveNoPlots();
return subPlots.get(subPlots.size() - 1);
}
private void throwIllegalArgumentExcpetionIfWeHaveNoPlots() {
if (subPlots.size() < 1) {
throw new IllegalArgumentException("Plot contains no sub plots");
}
}
private void throwIllegalArgumentExcpetionIfIndexIsNotInSubPlots(int subGroupIndex) {
if ((subPlots.size() -1) < subGroupIndex) {
throw new IllegalArgumentException("subgroup is out of range" + subGroupIndex + " > " + (subPlots.size() -1));
}
}
@Override
public boolean plotMatchesSetting(PlotConfiguration settings) {
if (settings.getOrdinalPositionForStackedPlots() != this.getOrdinalPositionForStackedPlots())
return false;
if (settings.getPinTimeAxis() != getPinTimeAxis())
return false;
if (settings.getAxisOrientationSetting() != this.getAxisOrientationSetting())
return false;
if (settings.getMaxDependent() != getMaxDependent())
return false;
if (settings.getMaxNonTime() != getMaxNonTime())
return false;
if (settings.getMaxTime() != getMaxTime())
return false;
if (settings.getMinDependent() != getMinDependent())
return false;
if (settings.getMinNonTime() != getMinNonTime())
return false;
if (settings.getMinTime() != getMinTime())
return false;
if (settings.getNonTimeAxisSubsequentMaxSetting() != getNonTimeAxisSubsequentMaxSetting())
return false;
if (settings.getNonTimeAxisSubsequentMinSetting() != getNonTimeAxisSubsequentMinSetting())
return false;
if (settings.getNonTimeMaxPadding() != getNonTimeMaxPadding())
return false;
if (settings.getNonTimeMinPadding() != getNonTimeMinPadding())
return false;
if (!settings.getPlotLineConnectionType().equals(getPlotLineConnectionType()))
return false;
if (!settings.getTimeAxisSubsequentSetting().equals(getTimeAxisSubsequentSetting()))
return false;
if (settings.getTimePadding() != getTimePadding())
return false;
if (settings.getXAxisMaximumLocation() != getXAxisMaximumLocation())
return false;
if (settings.getYAxisMaximumLocation() != getYAxisMaximumLocation())
return false;
if (settings.getPlotLineDraw().drawLine() != getPlotLineDraw().drawLine())
return false;
if (settings.getPlotLineDraw().drawMarkers() != getPlotLineDraw().drawMarkers())
return false;
return true;
}
@Override
public void updateTimeAxis(PlotSubject subject, long startTime, long endTime) {
for (AbstractPlottingPackage plot: subPlots) {
if (plot!= subject) {
plot.setTimeAxisStartAndStop(startTime, endTime);
}
}
}
@Override
public void updateResetButtons() {
for(AbstractPlottingPackage p : subPlots) {
p.updateResetButtons();
}
}
@Override
public void clearAllDataFromPlot() {
for (AbstractPlottingPackage plot: subPlots) {
plot.clearAllDataFromPlot();
}
}
@Override
public Pinnable createPin() {
return pinSupport.createPin();
}
private void pause() {
for(AbstractPlottingPackage plot : subPlots) {
plot.pause(true);
}
}
private void unpause() {
// Request data from buffer to fill in what was missed while paused.
plotUser.updateFromFeed(null);
for(AbstractPlottingPackage plot : subPlots) {
plot.pause(false);
}
}
@Override
public boolean isPinned() {
return pinSupport.isPinned();
}
@Override
public List<AbstractPlottingPackage> getSubPlots() {
return Collections.unmodifiableList(subPlots);
}
@Override
public Axis getTimeAxis() {
return timeAxis;
}
/**
* Adds data set per map.
* @param dataForPlot data map.
*/
public void addData(Map<String, SortedMap<Long, Double>> dataForPlot) {
for(Entry<String, SortedMap<Long, Double>> feedData : dataForPlot.entrySet()) {
String feedID = feedData.getKey();
String dataSetNameLower = feedID.toLowerCase();
if (!isKnownDataSet(dataSetNameLower)) {
throw new IllegalArgumentException("Attempting to set value for an unknown data set " + feedID);
}
if (getAxisOrientationSetting() != AxisOrientationSetting.Z_AXIS_AS_TIME) {
Set<AbstractPlottingPackage> feedPlots = dataSetNameToSubGroupMap.get(dataSetNameLower);
SortedMap<Long, Double> points = feedData.getValue();
for(AbstractPlottingPackage plot : feedPlots) {
plot.addData(dataSetNameLower, points);
}
} else {
for(AbstractPlottingPackage plot : subPlots) {
if (plot.isKnownDataSet(feedID)) {
plot.addData(dataSetNameLower, feedData.getValue());
}
}
}
}
}
/**
* Adds data set per feed Id, timestamp, and telemetry value.
* @param feedID the feed Id.
* @param time timestamp in millisecs.
* @param value telemetry value in double.
*/
public void addData(String feedID, long time, double value) {
SortedMap<Long, Double> points = new TreeMap<Long, Double>();
points.put(time, value);
addData(Collections.singletonMap(feedID, points));
}
/**
* Sets the plot X-Y time axis.
* @param axis the X-Y time axis.
*/
public void setPlotTimeAxis(AbstractAxis axis) {
this.plotTimeAxis = axis;
}
/**
* Gets the plot X-Y time axis.
* @return X-Y time axis.
*/
public AbstractAxis getPlotTimeAxis() {
return plotTimeAxis;
}
private void timeReachedEnd() {
long maxTime = getCurrentMCTTime();
double plotMax = Math.max(plotTimeAxis.getStart(), plotTimeAxis.getEnd());
double lag = maxTime - plotMax;
double scrollRescaleTimeMargin = this.getTimePadding();
if (scrollRescaleTimeMargin == 0) {
scrollRescaleTimeMargin = (maxTime - plotMax) / Math.abs(plotTimeAxis.getEnd() - plotTimeAxis.getStart());
}
if(lag > 0 && !timeAxis.isPinned()) {
if(getTimeAxisSubsequentSetting() == TimeAxisSubsequentBoundsSetting.JUMP) {
double increment = Math.abs(scrollRescaleTimeMargin * (plotTimeAxis.getEnd() - plotTimeAxis.getStart()));
plotTimeAxis.shift(Math.ceil(lag / increment) * increment);
for(AbstractPlottingPackage subPlot : subPlots) {
subPlot.setTimeAxisStartAndStop(plotTimeAxis.getStartAsLong(), plotTimeAxis.getEndAsLong());
}
} else if(getTimeAxisSubsequentSetting() == TimeAxisSubsequentBoundsSetting.SCRUNCH) {
double max = plotTimeAxis.getEnd();
double min = plotTimeAxis.getStart();
double diff = max - min;
assert diff != 0 : "min = max = " + min;
double scrunchFactor = 1 + scrollRescaleTimeMargin;
if((max < min)) {
min = max + (maxTime - max)*(scrunchFactor);
} else {
max = min + (maxTime - min)*(scrunchFactor);
}
plotTimeAxis.setStart(min);
plotTimeAxis.setEnd(max);
for(AbstractPlottingPackage subPlot : subPlots) {
subPlot.setTimeAxisStartAndStop(plotTimeAxis.getStartAsLong(), plotTimeAxis.getEndAsLong());
subPlot.updateCompressionRatio();
}
} else {
assert false : "Unrecognized timeAxisSubsequentSetting: " + getTimeAxisSubsequentSetting().name();
}
double newPlotMax = Math.max(plotTimeAxis.getStart(), plotTimeAxis.getEnd());
if(newPlotMax != plotMax) {
GregorianCalendar start = new GregorianCalendar();
GregorianCalendar end = new GregorianCalendar();
start.setTimeInMillis((long) plotMax);
end.setTimeInMillis((long) newPlotMax);
requestPredictivePlotData(start, end);
}
}
}
@Override
public void plotAxisChanged(PlotSubject subject, AbstractAxis axis) {
}
@Override
public void dataPlotted() {
}
}