/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2001-2004, by :
* Corporate:
* EADS Astrium SAS
* EADS CRC
* Individual:
* Claude Cazenave
*
* $Id: TimePlot.java,v 1.34 2008/11/27 10:53:48 ogor Exp $
*
* Changes
* -------
* 11 jan. 2005 : Initial public release (CC);
*
*/
package jsynoptic.plugins.async;
import java.awt.Rectangle;
import java.util.LinkedHashMap;
import java.util.Vector;
import javax.swing.undo.CompoundEdit;
import jsynoptic.base.DataSourceConsumer;
import jsynoptic.plugins.async.ui.TimePlotPropertiesPanel;
import jsynoptic.ui.JSynoptic;
import jsynoptic.ui.LongAction;
import simtools.data.DataException;
import simtools.data.DataInfo;
import simtools.data.DataSource;
import simtools.data.async.StreamingTSDataSource;
import simtools.data.async.TimeStampedDataSource;
import simtools.diagram.DiagramSelection;
import simtools.shapes.CurveShape;
import simtools.ui.JPropertiesPanel;
import simtools.ui.MenuResourceBundle;
import simtools.ui.ResourceFinder;
/**
* A plot dedicated to time stamped data source
* The X axis (primary and secondary) display the time source
* values of TimeStampedDataSource. Assuming the time values
* are based on the same time zone, the data sources do not need
* to belong to the same collection (they are all asynchronous).
*
* @see TimeStampedDataSource
*
* @author Claude Cazenave
*
*/
public class TimePlot extends jsynoptic.builtin.TimePlot implements DataSourceConsumer {
public static MenuResourceBundle addedResources = ResourceFinder.getMenu(TimePlot.class);
static final long serialVersionUID = 4978966488916450849L;
/**
* @deprecated see pyformat
*/
//protected ChoiceFormat y1format;
/**
* @deprecated see syformat
*/
//protected ChoiceFormat y2format;
/**
* diferences between time and time2
*/
protected double time2Offset = 0 ;
/**
* boolean to enabled the diplay of the secondary axis
*/
protected boolean displaySecondaryAxis = false;
/**
* @param ox
* @param oy
* @param width
* @param height
*/
public TimePlot(int ox, int oy, int width, int height) {
super(ox, oy, width, height);
}
protected CurveShape doAddYAction(Object[] obar){
TimeStampedDataSource ds = (TimeStampedDataSource)obar[0];
boolean primary = ((Boolean)obar[1]).booleanValue();
boolean usePrimaryX = ((Boolean)obar[2]).booleanValue();
Rectangle bounds = getBounds();
CurveShape cs;
if (usePrimaryX) {
cs = createCurveShape(ds.getTime(), ds);
if (isPrimaryBounded) cs.setSlice(primaryStartIndex, primaryEndIndex);
} else {
cs = createCurveShape(ds.getTime(), ds);
if (isSecondaryBounded) cs.setSlice(secondaryStartIndex, secondaryEndIndex);
}
String label = DEFAULT_DISPLAY_DATA_SOURCE_ID? DataInfo.getAliasOrIdwithUnit(ds) : DataInfo.getAliasOrLabelwithUnit(ds);
if (primary) {
if (primaryCurves==null) primaryCurves = new Vector();
primaryCurves.add(cs);
insertCurve(cs, !usePrimaryX, false);
} else {
setSecondaryAxis(false, true);
if (secondaryCurves==null) secondaryCurves = new Vector();
secondaryCurves.add(cs);
insertCurve(cs, !usePrimaryX, true);
}
if (_curves.size()>0) setLegendVisible(true);
setCurveLabel(cs, label);
setCurveColor(cs, defaultPalette[paletteIndex++]);
if (paletteIndex >= defaultPalette.length) paletteIndex = 0;
repaintDiagram(bounds);
return cs;
}
protected boolean add(TimeStampedDataSource ds, boolean primary, boolean background) {
if (ds==null) return false;
new LongAction(LongAction.LONG_ACTION_SHAPE | LongAction.LONG_ACTION_SOURCE,
new Object[]{ds, new Boolean(primary), new Boolean(background)},
new Object[]{ds, this} ) {
protected void doAction() {
doAddYAction((Object[])param);
}
}.start(background);
return !background;
}
/* (non-Javadoc)
* @see jsynoptic.builtin.Plot#repaintDiagram(java.awt.Rectangle)
*/
public void repaintDiagram(Rectangle oldBounds) {
// Update PX axis and optionally SX axis
computeHorizontalBoundAndStep(_curves);
if (axesLimitsArray[PX].validity) {
setX(axesLimitsArray[PX].min, axesLimitsArray[PX].max, axesLimitsArray[PX].step);
}
setLabel(axesLimitsArray[PX].label,true,false);
if (axesLimitsArray[SX].validity) {
setSecondaryX(axesLimitsArray[SX].min, axesLimitsArray[SX].max, axesLimitsArray[SX].step);
}
setLabel(axesLimitsArray[SX].label,true,true);
// Update PY axis
computeVerticalBoundAndStep(axesLimitsArray[PY], primaryCurves);
if (axesLimitsArray[PY].validity) {
setY(axesLimitsArray[PY].min, axesLimitsArray[PY].max, axesLimitsArray[PY].step);
}
setLabel(axesLimitsArray[PY].label,false,false);
// Update SY axis
computeVerticalBoundAndStep(axesLimitsArray[SY], secondaryCurves);
if (axesLimitsArray[SY].validity) {
setSecondaryY(axesLimitsArray[SY].min, axesLimitsArray[SY].max, axesLimitsArray[SY].step);
}
setLabel(axesLimitsArray[SY].label,false,true);
//Store bounds.
if (oldBounds!=null){
oldBounds.add(getBounds());
}
notifyChange(oldBounds);
}
/**
*
* This method compute the bounds and the step of horizontal time axes.
* @param curves
*/
private void computeHorizontalBoundAndStep(Vector curves) {
// update PX axe
if ((!axesLimitsArray[PX].validity) && (curves!=null) && (curves.size()>0)) {
axesLimitsArray[PX].min = getStartEndTime(curves, true, false);
axesLimitsArray[PX].max = getStartEndTime(curves, false, false);
if ( axesLimitsArray[PX].floatingAxe) {
if (axesLimitsArray[PX].max - axesLimitsArray[PX].min > axesLimitsArray[PX].floatingAxeRange) {
axesLimitsArray[PX].min = axesLimitsArray[PX].max - axesLimitsArray[PX].floatingAxeRange;
} else {
axesLimitsArray[PX].max = axesLimitsArray[PX].min + axesLimitsArray[PX].floatingAxeRange;
}
}
if (axesLimitsArray[PX].min == axesLimitsArray[PX].max) {
axesLimitsArray[PX].max = axesLimitsArray[PX].min + 1;
axesLimitsArray[PX].step = 1;
} else {
axesLimitsArray[PX].computeStep(nbXGraduation);
}
axesLimitsArray[PX].validity=true;
}
// Get first TimeStamped data source and compute offset between time1 and time2 values
if (!curves.isEmpty()){
try {
TimeStampedDataSource fisrtTsDs =(TimeStampedDataSource) ((Curve)curves.get(0)).shape.getYSource();
if (fisrtTsDs instanceof StreamingTSDataSource){
if (((StreamingTSDataSource)fisrtTsDs).getTime2()!=null){
if (displaySecondaryAxis) {
if(_asx2 == null){
setSecondaryAxis(true,true);
setTimeFormat(_asx1.getMin(), _asx1.getMax(), _asx1.getStep(), true); // set format using ax1 format
}
// Compute offset between time1 and tim2
long currentIndex = fisrtTsDs.getLastIndex();
time2Offset =
((StreamingTSDataSource)fisrtTsDs).getTime2().getDoubleValue(currentIndex)-
fisrtTsDs.getTime().getDoubleValue(currentIndex);
// update SX axe
if(!axesLimitsArray[SX].validity){
axesLimitsArray[SX].min = axesLimitsArray[PX].min + time2Offset ;
axesLimitsArray[SX].max = axesLimitsArray[PX].max + time2Offset ;
axesLimitsArray[SX].step = axesLimitsArray[PX].step;
axesLimitsArray[SX].validity = true;
}
} else { // remove the secondary axis
if (_asx2 != null) {
super.setSecondaryAxis(true, false);
setDateTime(null, true); // remove the secondary date
}
}
}
}
} catch (DataException e) {
}
}
}
/* (non-Javadoc)
* @see simtools.data.EndNotificationListener#notificationEnd(java.lang.Object)
*/
public void notificationEnd(Object referer) {
if(referer instanceof TimeStampedDataSource){
// time sources notification are filtered
super.notificationEnd(referer);
}
}
/* (non-Javadoc)
* @see jsynoptic.base.SelectionContextualActionProvider#getCollectiveActions()
*/
public LinkedHashMap getCollectiveActions(DiagramSelection sel, double x, double y, Object o, int context) {
LinkedHashMap res=new LinkedHashMap();
if (((primaryCurves!=null) && (primaryCurves.size()!=0))
|| ((secondaryCurves!=null) && (secondaryCurves.size()!=0))) { // do both primary and secondary
res.put(resources.getStringValue("autoscale"), null);
res.put(resources.getStringValue("autoscaleOnY"), null);
res.put(resources.getStringValue("adjustOnX") + ";" + resources.getStringValue("union"), null);
res.put(resources.getStringValue("adjustOnX") + ";" + resources.getStringValue("intersection"), null);
res.put(resources.getStringValue("adjustOnX") + ";" + resources.getStringValue("firstPlotSelected"), null);
}
return res;
}
public String[] getActions(double x, double y, Object o, int context) {
if ((context==MOUSE_OVER_CONTEXT) ||
(context==MOUSE_OUT_CONTEXT) ||
(context==MOUSE_PRESSED_CONTEXT)){
return super.getActions(x,y,o,context);
}
Vector v = new Vector();
if (o instanceof TimeStampedDataSource) {
v.add(addedResources.getStringValue("add"));
if (primaryCurves!=null && primaryCurves.size()!=0) {
v.add(addedResources.getStringValue("addSec"));
}
} else {
JSynoptic.setStatus(resources.getStringValue("noSource"));
}
if (context==EDITOR_CONTEXT) {
v.add(0,resources.getStringValue("properties"));
if (_curves.size()!=0){
v.add(0,resources.getStringValue("information"));
if (_magnetizedCurve!=null) {
v.add(resources.getStringValue("selectPoint"));
v.add(resources.getStringValue("disableMagneticCoordinates") + _magnetizedCurve.getLabel());
}
for(int i=0;i<_curves.size();i++){
// magnetize a curve action
if (!((_magnetizedCurve!=null) && (((Curve)_curves.get(i)).equals(_magnetizedCurve)) ))
v.add(resources.getStringValue("enableMagneticCoordinates")+ ";" + ((Curve)_curves.get(i)).getLabel() );
}
for(int i=0;i<_curves.size();i++){
// Delete curve action
v.add(resources.getStringValue("deleteCurve")+ ";" + ((Curve)_curves.get(i)).getLabel() );
}
}
}
return (String[])v.toArray(new String[v.size()]);
}
public boolean doAction(double x, double y, Object o, String action, CompoundEdit undoableEdit) {
if (action==null) return false;
if (action.equals("mouseOver")
|| action.equals("mouseOut")
|| action.equals("zoomTopLeft")
|| action.equals("zoomBottomRight")
|| action.equals(resources.getStringValue("zoomBox"))
|| action.equals(resources.getStringValue("zoomOut"))
|| action.equals(resources.getStringValue("autoscale"))
|| action.equals(resources.getStringValue("information"))
|| action.equals(resources.getStringValue("autoscaleOnY"))
|| action.equals(resources.getStringValue("properties"))
|| action.startsWith(resources.getStringValue("enableMagneticCoordinates"))
|| action.startsWith(resources.getStringValue("disableMagneticCoordinates"))
|| action.startsWith(resources.getStringValue("deleteCurve"))
|| action.startsWith(resources.getStringValue("adjustOnX"))
|| action.startsWith(resources.getStringValue("selectPoint"))
){
return super.doAction(x,y,o,action, undoableEdit);
}
if (o instanceof TimeStampedDataSource) {
TimeStampedDataSource ds = (TimeStampedDataSource)o;
if (action.equals(addedResources.getStringValue("add"))) {
return add(ds, true, true);
}
if (action.equals(addedResources.getStringValue("addSec"))) {
return add(ds, false, true);
}
}
return false;
}
/* (non-Javadoc)
* @see jsynoptic.builtin.TimePlot#secondaryAxisBoundComputation(java.awt.Rectangle)
*/
protected void secondaryAxisBoundComputation(Rectangle ret){
if(_asx2 != null){
//Because _asx2 is not associated with a CurveShape, it is not
//automatically taken into account in the shape Bound computing.
Rectangle r = _asx2.getBounds();
//add _asx2 in the shape Bounds computing
ret.add(r);
}
}
/* (non-Javadoc)
* @see jsynoptic.builtin.Plot#createPanel()
*/
public JPropertiesPanel createPanel() {
int curvesSize = ((primaryCurves!=null ? primaryCurves.size() : 0) + (secondaryCurves!=null ? secondaryCurves.size() : 0));
return new TimePlotPropertiesPanel(curvesSize, AsyncPlugin.resources.getString("TimePlot"));
}
/* (non-Javadoc)
* @see simtools.shapes.AbstractShape#getShapeName()
*/
public String getShapeName(){
return AsyncPlugin.resources.getString("TimePlot");
}
/* (non-Javadoc)
* @see jsynoptic.base.DataSourceConsumer#canAddDataSource(simtools.data.DataSource)
*/
public boolean canAddDataSource(DataSource d) {
return d instanceof TimeStampedDataSource;
}
/* (non-Javadoc)
* @see jsynoptic.base.DataSourceConsumer#addDataSource(simtools.data.DataSource)
*/
public boolean addDataSource(DataSource d) {
return add((TimeStampedDataSource)d, true, true);
}
/**
* Get start or end time related to a list of curves
* @param curves
* @param start - if true get start time, otherwise get end time
* @param secondaryXaxis - if true look on all curves whose X is defined on X secondaryXaxis
* otherwise look on all curves whose X is defined on X primary
* @return
*/
protected double getStartEndTime(Vector curves, boolean start, boolean secondaryXaxis){
double res = start? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
try{
for (int i=0; i<curves.size(); ++i) {
Curve curve = ((Curve) curves.get(i));
if (curve.secondaryXaxis == secondaryXaxis) {
TimeStampedDataSource ySource = (TimeStampedDataSource)curve.shape.getYSource();
double t = (start ? ySource.getStart(): ySource.getEnd());
if (start && t < res){
res = t;
}
if (!start && t > res){
res = t;
}
}
}
} catch (DataException e){
res = 0.0;
}
return res;
}
private void readObject(java.io.ObjectInputStream in) throws java.lang.ClassNotFoundException, java.io.IOException {
in.defaultReadObject();
// primaryX and secondaryX shall be null in Asynchronous Plots,
// but old plots can still contain those data source values -> update them.
if (primaryX != null){
primaryX = null;
axesLimitsArray[PX].label = null;
}
if (secondaryX != null){
secondaryX = null;
axesLimitsArray[SX].label = null;
}
}
/*
* (non-Javadoc)
*
* @see jsynoptic.builtin.ui.PlotPropertiesPanel#getPropertyNames()
*/
public String[] getPropertyNames() {
return createPanel().getPropertyNames();
}
/*
* (non-Javadoc)
*
* @see jsynoptic.builtin.ui.PlotPropertiesPanel#getPropertyValue(java.lang.String)
*/
public Object getPropertyValue(String name) {
Object res = super.getPropertyValue(name);
if (name.equalsIgnoreCase("DISPLAY_SEC_AXIS")) {
res = new Boolean(displaySecondaryAxis);
}
return res;
}
/* (non-Javadoc)
* @see jsynoptic.builtin.ui.PlotPropertiesPanel#setPropertyValue(java.lang.String, java.lang.Object)
*/
public void setPropertyValue(String name, Object value) {
if (name.equalsIgnoreCase("DISPLAY_SEC_AXIS")) {
displaySecondaryAxis = ((Boolean) value).booleanValue();
}
super.setPropertyValue(name, value);
}
}