/*******************************************************************************
* 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.utils.TimeFormatUtils;
import gov.nasa.arc.mct.fastplot.view.Pinnable;
import java.awt.Dimension;
import java.text.FieldPosition;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ResourceBundle;
import javax.swing.JComponent;
import javax.swing.SpringLayout;
import plotter.TimeSystemFormattedLabel;
import plotter.xy.SlopeLine;
import plotter.xy.SlopeLineDisplay;
import plotter.xy.XYLocationDisplay;
import plotter.xy.XYPlot;
import plotter.xy.XYPlotContents;
/**
* Manages the movement of the mouse pointer within a plot to show the X,Y data cursor and respond to mouse clicks
* to show the slope line. Includes the math functions to calculate the slope line.
*
* The X,Y data cursor runs off mouseMoved events.
*
* The slope line is initiated with a mousePress, moves with mouseDragged, and ends with a mouseReleased event.
*/
class PlotDataCursor {
private final static String HTML_WHITESPACES = " ";
@SuppressWarnings("serial")
private static final NumberFormat TIME_SPAN_FORMAT = new NumberFormat() {
@Override
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
toAppendTo.append(formatTime((long) number));
return toAppendTo;
}
@Override
public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
toAppendTo.append(formatTime(number));
return toAppendTo;
}
@Override
public Number parse(String source, ParsePosition parsePosition) {
throw new RuntimeException("Not implemented");
}
};
// Access bundle file where externalized strings are defined.
private static final ResourceBundle BUNDLE =
ResourceBundle.getBundle(PlotDataCursor.class.getName().substring(0,
PlotDataCursor.class.getName().lastIndexOf("."))+".Bundle");
private PlotterPlot parentPlot;
private SimpleDateFormat dateFormat;
private XYLocationDisplay pointerXYValueLabel = new XYLocationDisplay();
private TimeSystemFormattedLabel timeSystemFormattedLabel = new TimeSystemFormattedLabel();
private SlopeLineDisplay slopeLabel = new SlopeLineDisplay();
private SlopeLine slopeLine; // TODO: Use one SlopeLine across multiple plots
private boolean slopeLineEnabled = false;
private Pinnable pin;
PlotDataCursor (PlotterPlot plot) {
parentPlot = plot;
dateFormat = TimeFormatUtils.makeDataFormat(parentPlot.getTimeFormatSetting());
if (plot.getTimeSystemSetting() != null) {
timeSystemFormattedLabel.setTimeSystemAxisLabelName(parentPlot.getTimeSystemSetting());
}
setupTimeSystemLabel();
setupXYDisplay();
setupMarqueeZoom();
setupSlopeLineDisplay();
pin = plot.getPlotAbstraction().createPin();
}
private void setupMarqueeZoom() {
MarqueeZoomListener marqueeZoomListener = new MarqueeZoomListener(parentPlot.localControlsManager, parentPlot.getPlotView(), dateFormat, parentPlot.getTimeAxisFont());
parentPlot.getPlotView().getContents().addMouseListener(marqueeZoomListener);
parentPlot.getPlotView().getContents().addMouseMotionListener(marqueeZoomListener);
}
private void setupTimeSystemLabel() {
timeSystemFormattedLabel.setSize((int) timeSystemFormattedLabel.getPreferredSize().getWidth(),
(int) timeSystemFormattedLabel.getPreferredSize().getHeight());
timeSystemFormattedLabel.setFont(parentPlot.getTimeAxisFont());
timeSystemFormattedLabel.setForeground(PlotConstants.DATA_CURSOR_COLOR);
MessageFormat format = new MessageFormat("<html><body style=\"white-space:nowrap\"><B>" + HTML_WHITESPACES + timeSystemFormattedLabel.getTimeSystemAxisLabelName()
+ HTML_WHITESPACES
+ "</B></body></html>");
timeSystemFormattedLabel.setFormat(format);
}
/**
* Setup the mouse position x,y label that will be positioned at the top of the plot.
*/
private void setupXYDisplay() {
pointerXYValueLabel.setSize((int) pointerXYValueLabel.getPreferredSize().getWidth(),
(int) pointerXYValueLabel.getPreferredSize().getHeight());
pointerXYValueLabel.setFont(parentPlot.getTimeAxisFont());
pointerXYValueLabel.setForeground(PlotConstants.DATA_CURSOR_COLOR);
pointerXYValueLabel.attach(parentPlot.getPlotView());
if (parentPlot.getAxisOrientationSetting() == AxisOrientationSetting.X_AXIS_AS_TIME) {
MessageFormat format = new MessageFormat("<html><body style=\"white-space:nowrap\"><B>(X:</B> {0}" + HTML_WHITESPACES + "<B>Y:</B> {1})</body></html>");
format.setFormatByArgumentIndex(0, dateFormat);
format.setFormatByArgumentIndex(1, PlotConstants.NON_TIME_FORMAT);
pointerXYValueLabel.setFormat(format);
} else {
MessageFormat format = new MessageFormat("<html><body style=\"white-space:nowrap\"><B>(Y:</B> {1}" + HTML_WHITESPACES + "<B>X:</B> {0})</body></html>");
format.setFormatByArgumentIndex(0, PlotConstants.NON_TIME_FORMAT);
format.setFormatByArgumentIndex(1, dateFormat);
pointerXYValueLabel.setFormat(format);
}
// This sets the preferred height to the normal height so the component doesn't collapse to height 0 when the text is empty.
// Note that mimimumSize does not work for some reason.
pointerXYValueLabel.setText("Ag");
Dimension size = pointerXYValueLabel.getPreferredSize();
size.width = 100;
pointerXYValueLabel.setText("");
pointerXYValueLabel.setPreferredSize(size);
XYPlot plot = parentPlot.getPlotView();
XYPlotContents contents = plot.getContents();
plot.add(pointerXYValueLabel);
plot.add(timeSystemFormattedLabel);
SpringLayout layout2 = (SpringLayout) plot.getLayout();
layout2.putConstraint(SpringLayout.NORTH, pointerXYValueLabel, 0, SpringLayout.NORTH, plot);
layout2.putConstraint(SpringLayout.WEST, pointerXYValueLabel, 0, SpringLayout.WEST, contents);
layout2.putConstraint(SpringLayout.WEST, timeSystemFormattedLabel, 0, SpringLayout.WEST, contents);
layout2.putConstraint(SpringLayout.NORTH, timeSystemFormattedLabel, 0, SpringLayout.NORTH, plot);
layout2.putConstraint(SpringLayout.WEST, pointerXYValueLabel, 10, SpringLayout.EAST, timeSystemFormattedLabel);
// Pick the tallest label to lay out the plot contents against
JComponent top = (pointerXYValueLabel.getPreferredSize().height > timeSystemFormattedLabel.getPreferredSize().height) ?
pointerXYValueLabel : timeSystemFormattedLabel;
layout2.putConstraint(SpringLayout.NORTH, contents, 0, SpringLayout.SOUTH, top);
plot.getYAxis().setEndMargin(top.getPreferredSize().height);
}
/**
* Setup the slope dx, dy label that will be positioned at the top of the plot
*/
@SuppressWarnings("serial")
private void setupSlopeLineDisplay() {
slopeLine = new SlopeLine();
slopeLine.attach(parentPlot.getPlotView());
slopeLine.addListenerForPlot(parentPlot.getPlotView(), slopeLabel);
slopeLine.addListenerForPlot(parentPlot.getPlotView(), new SlopeLine.Listener() {
@Override
public void slopeLineUpdated(SlopeLine line, XYPlot plot, double arg2, double arg3, double arg4, double arg5) {
// ignore
}
@Override
public void slopeLineRemoved(SlopeLine line, XYPlot plot) {
pin.setPinned(false);
}
@Override
public void slopeLineAdded(SlopeLine line, XYPlot plot, double arg2, double arg3) {
pin.setPinned(true);
}
});
slopeLine.setForeground(PlotConstants.DATA_CURSOR_COLOR);
slopeLabel.setFont(parentPlot.getTimeAxisFont());
slopeLabel.setForeground(PlotConstants.DATA_CURSOR_COLOR);
if (parentPlot.getAxisOrientationSetting() == AxisOrientationSetting.X_AXIS_AS_TIME) {
MessageFormat format = new MessageFormat("<html><body style=\"white-space:nowrap\"><B>ΔX:</B> {0}" + HTML_WHITESPACES
+ "<B>ΔY:</B> {1}" + HTML_WHITESPACES + "<B>"
+ BUNDLE.getString("Slope.label") + ":</B> {2}" + PlotConstants.SLOPE_UNIT + "</body></html>");
format.setFormatByArgumentIndex(0, TIME_SPAN_FORMAT);
format.setFormatByArgumentIndex(1, PlotConstants.NON_TIME_FORMAT);
format.setFormatByArgumentIndex(2, new NumberFormat() {
@Override
public Number parse(String source, ParsePosition parsePosition) {
throw new RuntimeException("Not implemented");
}
@Override
public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
toAppendTo.append(PlotConstants.NON_TIME_FORMAT.format(number * PlotConstants.SLOPE_UNIT_DIVIDER_IN_MS));
return toAppendTo;
}
@Override
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
toAppendTo.append(PlotConstants.NON_TIME_FORMAT.format(number * PlotConstants.SLOPE_UNIT_DIVIDER_IN_MS));
return toAppendTo;
}
});
slopeLabel.setFormat(format);
} else {
MessageFormat format = new MessageFormat("<html><body style=\"white-space:nowrap\"><B>ΔY:</B> {0}"
+ HTML_WHITESPACES + "<B>ΔX:</B> {1}" + HTML_WHITESPACES + "<B>"
+ BUNDLE.getString("Slope.label") + ":</B> {2}" + PlotConstants.SLOPE_UNIT + "</body></html>");
format.setFormatByArgumentIndex(0, PlotConstants.NON_TIME_FORMAT);
format.setFormatByArgumentIndex(1, TIME_SPAN_FORMAT);
format.setFormatByArgumentIndex(2, new NumberFormat() {
@Override
public Number parse(String source, ParsePosition parsePosition) {
throw new RuntimeException("Not implemented");
}
@Override
public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
toAppendTo.append(PlotConstants.NON_TIME_FORMAT.format(PlotConstants.SLOPE_UNIT_DIVIDER_IN_MS / (double) number));
return toAppendTo;
}
@Override
public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
toAppendTo.append(PlotConstants.NON_TIME_FORMAT.format(PlotConstants.SLOPE_UNIT_DIVIDER_IN_MS / number));
return toAppendTo;
}
});
slopeLabel.setFormat(format);
}
// Sets the preferred height to the normal height so the component doesn't collapse to height 0 when the text is empty.
// Note that mimimumSize does not work for some reason.
slopeLabel.setText("Ag");
Dimension size = slopeLabel.getPreferredSize();
size.width = 100;
slopeLabel.setText("");
slopeLabel.setPreferredSize(size);
XYPlot plot = parentPlot.getPlotView();
plot.add(slopeLabel);
XYPlotContents contents = plot.getContents();
SpringLayout layout2 = (SpringLayout) plot.getLayout();
layout2.putConstraint(SpringLayout.NORTH, slopeLabel, 0, SpringLayout.NORTH, plot);
layout2.putConstraint(SpringLayout.EAST, slopeLabel, 0, SpringLayout.EAST, contents);
layout2.putConstraint(SpringLayout.WEST, timeSystemFormattedLabel, 0, SpringLayout.WEST, contents);
layout2.putConstraint(SpringLayout.NORTH, timeSystemFormattedLabel, 0, SpringLayout.NORTH, plot);
layout2.putConstraint(SpringLayout.WEST, pointerXYValueLabel, 10, SpringLayout.EAST, timeSystemFormattedLabel);
// Pick the tallest label to lay out the plot contents against
JComponent top = (pointerXYValueLabel.getPreferredSize().height > timeSystemFormattedLabel.getPreferredSize().height) ?
pointerXYValueLabel : timeSystemFormattedLabel;
layout2.putConstraint(SpringLayout.NORTH, contents, 0, SpringLayout.SOUTH, top);
plot.getYAxis().setEndMargin(top.getPreferredSize().height);
}
static String formatTime(long timeDelta) {
String sign = "+";
if (timeDelta < 0) {
sign = "-";
timeDelta = timeDelta * -1;
}
// build the time string
StringBuilder timeString = new StringBuilder();
long days = timeDelta / PlotConstants.MILLISECONDS_IN_DAY;
long remainder = timeDelta % PlotConstants.MILLISECONDS_IN_DAY;
long hours = remainder / PlotConstants.MILLISECONDS_IN_HOUR;
remainder = remainder % PlotConstants.MILLISECONDS_IN_HOUR;
long mins = remainder / PlotConstants.MILLISECONDS_IN_MIN;
remainder = remainder % PlotConstants.MILLISECONDS_IN_MIN;
long seconds = remainder / PlotConstants.MILLISECONDS_IN_SECOND;
remainder = remainder % PlotConstants.MILLISECONDS_IN_SECOND;
timeString.append(sign);
timeString.append(days);
timeString.append("/");
if (hours < 10) {
timeString.append("0");
}
timeString.append(hours);
timeString.append(":");
if (mins < 10) {
timeString.append("0");
}
timeString.append(mins);
timeString.append(":");
if (seconds < 10) {
timeString.append("0");
}
timeString.append(seconds);
return timeString.toString();
}
public boolean isSlopeLineEnabled() {
return slopeLineEnabled;
}
}