/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 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 General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package org.esa.snap.timeseries.ui.graph;
import com.bc.ceres.glayer.support.ImageLayer;
import org.esa.snap.core.datamodel.Placemark;
import org.esa.snap.core.datamodel.Product;
import org.esa.snap.core.datamodel.ProductNode;
import org.esa.snap.core.datamodel.ProductNodeEvent;
import org.esa.snap.core.datamodel.RasterDataNode;
import org.esa.snap.core.ui.PixelPositionListener;
import org.esa.snap.core.ui.product.ProductSceneView;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.timeseries.core.TimeSeriesMapper;
import org.esa.snap.timeseries.core.TimeSeriesModule;
import org.esa.snap.timeseries.core.timeseries.datamodel.AbstractTimeSeries;
import org.esa.snap.timeseries.core.timeseries.datamodel.TimeSeriesChangeEvent;
import org.esa.snap.timeseries.core.timeseries.datamodel.TimeSeriesListener;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.XYPlot;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import static org.esa.snap.timeseries.core.timeseries.datamodel.AbstractTimeSeries.*;
/**
* Main class for the graph tool.
*
* @author Marco Peters
* @author Thomas Storm
* @author Sabine Embacher
*/
@TopComponent.Description(
preferredID = "TimeSeriesGraphTopComponent",
iconBase = "org/esa/snap/timeseries/ui/icons/timeseries-plot.png",
persistenceType = TopComponent.PERSISTENCE_ALWAYS
)
@TopComponent.Registration(
mode = "navigator",
openAtStartup = false,
position = 2
)
@TopComponent.OpenActionRegistration(
displayName = "#CTL_TimeSeriesGraphTopComponentName",
preferredID = "TimeSeriesGraphTopComponent"
)
@ActionID(category = "Window", id = "org.esa.snap.timeseries.ui.graph.TimeSeriesGraphTopComponent")
@ActionReferences({
@ActionReference(path = "Menu/View/Tool Windows/Time Series", position = 1220),
@ActionReference(path = "Toolbars/Time Series", position = 20)
})
@NbBundle.Messages({"CTL_TimeSeriesGraphTopComponentName=Time Series Graph"})
public class TimeSeriesGraphTopComponent extends TopComponent {
private static final String HELP_ID = "timeSeriesGraph";
private static final String DEFAULT_RANGE_LABEL = "Value";
private static final String DEFAULT_DOMAIN_LABEL = "Time";
private final TimeSeriesPPL pixelPosListener;
private final PropertyChangeListener pinSelectionListener;
private final PropertyChangeListener sliderListener;
private final TimeSeriesListener timeSeriesGraphTSL;
private JFreeChart chart;
private TimeSeriesGraphForm graphForm;
private TimeSeriesGraphModel graphModel;
private ProductSceneView currentView;
private final TimeSeriesValidator validator;
public TimeSeriesGraphTopComponent() {
pixelPosListener = new TimeSeriesPPL();
pinSelectionListener = new PinSelectionListener();
sliderListener = new SliderListener();
timeSeriesGraphTSL = new TimeSeriesGraphTSL();
validator = new TimeSeriesValidator();
initUI();
}
private void initUI() {
final boolean displayLegend = true;
final boolean showTooltips = true;
final boolean showUrls = false;
chart = ChartFactory.createTimeSeriesChart(null,
DEFAULT_DOMAIN_LABEL,
DEFAULT_RANGE_LABEL,
null, displayLegend, showTooltips, showUrls);
graphModel = new TimeSeriesGraphModel(chart.getXYPlot(), validator);
graphForm = new TimeSeriesGraphForm(graphModel, chart, validator, HELP_ID);
setDisplayName(Bundle.CTL_TimeSeriesGraphTopComponentName());
setLayout(new BorderLayout());
add(graphForm.getControl(), BorderLayout.CENTER);
ProductSceneView selectedView = SnapApp.getDefault().getSelectedProductSceneView();
if (selectedView != null) {
maySetCurrentView(selectedView);
}
SnapApp.getDefault().getSelectionSupport(ProductSceneView.class).addHandler((oldValue, newValue) -> {
if (oldValue != null) {
maySetCurrentView(null);
}
if (newValue != null) {
maySetCurrentView(newValue);
}
});
}
@Override
public HelpCtx getHelpCtx() {
return new HelpCtx(HELP_ID);
}
/*
* Checks if the view displays a timeseries product.
* If so it is set as the current view.
*/
private void maySetCurrentView(ProductSceneView view) {
final String viewProductType = view.getProduct().getProductType();
if (view != currentView &&
!view.isRGB() &&
viewProductType.equals(AbstractTimeSeries.TIME_SERIES_PRODUCT_TYPE) &&
TimeSeriesMapper.getInstance().getTimeSeries(view.getProduct()) != null) {
setCurrentView(view);
}
}
private void setCurrentView(ProductSceneView newView) {
if (currentView == newView) {
return;
}
if (currentView != null) {
final AbstractTimeSeries timeSeries = TimeSeriesMapper.getInstance().getTimeSeries(currentView.getProduct());
timeSeries.removeTimeSeriesListener(timeSeriesGraphTSL);
currentView.removePixelPositionListener(pixelPosListener);
currentView.removePropertyChangeListener(ProductSceneView.PROPERTY_NAME_SELECTED_PIN, pinSelectionListener);
currentView.removePropertyChangeListener(TimeSeriesModule.TIME_PROPERTY, sliderListener);
}
currentView = newView;
graphForm.setButtonsEnabled(currentView != null);
if (currentView != null) {
final Product currentProduct = currentView.getProduct();
final AbstractTimeSeries timeSeries = TimeSeriesMapper.getInstance().getTimeSeries(currentProduct);
timeSeries.addTimeSeriesListener(timeSeriesGraphTSL);
currentView.addPixelPositionListener(pixelPosListener);
currentView.addPropertyChangeListener(ProductSceneView.PROPERTY_NAME_SELECTED_PIN, pinSelectionListener);
currentView.addPropertyChangeListener(TimeSeriesModule.TIME_PROPERTY, sliderListener);
final RasterDataNode raster = currentView.getRaster();
graphModel.adaptToTimeSeries(timeSeries);
graphModel.updateAnnotation(raster);
graphModel.updateTimeSeries(null, TimeSeriesType.INSITU);
graphModel.updateTimeSeries(null, TimeSeriesType.PIN);
String variableName = rasterToVariableName(raster.getName());
setDisplayName(String.format("%s - %s", Bundle.CTL_TimeSeriesGraphTopComponentName(), variableName));
} else {
graphModel.removeAnnotation();
graphModel.adaptToTimeSeries(null);
graphModel.updateTimeSeries(null, TimeSeriesType.INSITU);
graphModel.updateTimeSeries(null, TimeSeriesType.PIN);
setDisplayName(Bundle.CTL_TimeSeriesGraphTopComponentName());
}
}
private void updateTimeSeries(AbstractTimeSeries timeSeries) {
graphModel.adaptToTimeSeries(timeSeries);
graphModel.updateTimeSeries(null, TimeSeriesType.INSITU);
graphModel.updateTimeSeries(null, TimeSeriesType.PIN);
}
private class TimeSeriesPPL implements PixelPositionListener {
@Override
public void pixelPosChanged(ImageLayer imageLayer, int pixelX, int pixelY,
int currentLevel, boolean pixelPosValid, MouseEvent e) {
if (!graphModel.isShowCursorTimeSeries()) {
return;
}
if (pixelPosValid && isVisible() && currentView != null) {
final TimeSeriesGraphUpdater.Position position = new TimeSeriesGraphUpdater.Position(pixelX, pixelY, currentLevel);
graphModel.updateTimeSeries(position, TimeSeriesType.CURSOR);
}
final boolean autorange = e.isShiftDown();
final XYPlot xyPlot = chart.getXYPlot();
for (int i = 0; i < xyPlot.getRangeAxisCount(); i++) {
xyPlot.getRangeAxis(i).setAutoRange(autorange);
}
if (currentView != null) {
graphModel.updateAnnotation(currentView.getRaster());
}
}
@Override
public void pixelPosNotAvailable() {
if (!graphModel.isShowCursorTimeSeries()) {
return;
}
graphModel.updateTimeSeries(null, TimeSeriesType.CURSOR);
}
}
private class PinSelectionListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (graphModel.isShowingSelectedPins()) {
graphModel.updateTimeSeries(null, TimeSeriesType.PIN);
graphModel.updateTimeSeries(null, TimeSeriesType.INSITU);
}
}
}
private class TimeSeriesGraphTSL extends TimeSeriesListener {
@Override
public void timeSeriesChanged(TimeSeriesChangeEvent event) {
int type = event.getType();
if (type == TimeSeriesChangeEvent.PROPERTY_PRODUCT_LOCATIONS ||
type == TimeSeriesChangeEvent.PROPERTY_EO_VARIABLE_SELECTION) {
graphModel.updateAnnotation(currentView.getRaster());
updateTimeSeries(event.getTimeSeries());
} else if (type == TimeSeriesChangeEvent.PROPERTY_INSITU_VARIABLE_SELECTION ||
type == TimeSeriesChangeEvent.PROPERTY_AXIS_MAPPING_CHANGED ||
type == TimeSeriesChangeEvent.START_TIME_PROPERTY_NAME ||
type == TimeSeriesChangeEvent.END_TIME_PROPERTY_NAME) {
updateTimeSeries(event.getTimeSeries());
}
}
@Override
public void nodeChanged(ProductNodeEvent event) {
String propertyName = event.getPropertyName();
if (propertyName.equals(Placemark.PROPERTY_NAME_PIXELPOS)
|| propertyName.equals(Placemark.PROPERTY_NAME_LABEL)) {
graphModel.updateTimeSeries(null, TimeSeriesType.PIN);
graphModel.updateTimeSeries(null, TimeSeriesType.INSITU);
}
}
@Override
public void nodeAdded(ProductNodeEvent event) {
final ProductNode node = event.getSourceNode();
if (node instanceof Placemark) {
handlePlacemarkChanged();
} else if (node instanceof RasterDataNode && currentView != null) {
graphModel.adaptToTimeSeries(getTimeSeries());
graphModel.updateTimeSeries(null, TimeSeriesType.INSITU);
graphModel.updateTimeSeries(null, TimeSeriesType.PIN);
}
}
@Override
public void nodeRemoved(ProductNodeEvent event) {
final ProductNode node = event.getSourceNode();
if (node instanceof Placemark) {
handlePlacemarkChanged();
} else if (node instanceof RasterDataNode && currentView != null) {
graphModel.adaptToTimeSeries(getTimeSeries());
graphModel.updateTimeSeries(null, TimeSeriesType.INSITU);
graphModel.updateTimeSeries(null, TimeSeriesType.PIN);
}
}
private AbstractTimeSeries getTimeSeries() {
final Product product = currentView.getProduct();
return TimeSeriesMapper.getInstance().getTimeSeries(product);
}
private void handlePlacemarkChanged() {
final boolean placemarksSet = currentView.getProduct().getPinGroup().getNodeCount() > 0;
graphForm.setExportEnabled(placemarksSet);
updateTimeSeries(getTimeSeries());
}
}
private class SliderListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
graphModel.updateAnnotation(currentView.getRaster());
}
}
}