org.esa.beam.visat.toolviews.spectrum.SpectrumToolView.java Source code

Java tutorial

Introduction

Here is the source code for org.esa.beam.visat.toolviews.spectrum.SpectrumToolView.java

Source

/*
 * Copyright (C) 2014 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.beam.visat.toolviews.spectrum;

import com.bc.ceres.glevel.MultiLevelModel;
import org.esa.beam.framework.datamodel.Band;
import org.esa.beam.framework.datamodel.DataNode;
import org.esa.beam.framework.datamodel.Placemark;
import org.esa.beam.framework.datamodel.PlacemarkGroup;
import org.esa.beam.framework.datamodel.Product;
import org.esa.beam.framework.datamodel.ProductManager;
import org.esa.beam.framework.datamodel.ProductNodeEvent;
import org.esa.beam.framework.datamodel.ProductNodeGroup;
import org.esa.beam.framework.datamodel.ProductNodeListenerAdapter;
import org.esa.beam.framework.help.HelpSys;
import org.esa.beam.framework.ui.GridBagUtils;
import org.esa.beam.framework.ui.ModalDialog;
import org.esa.beam.framework.ui.PixelPositionListener;
import org.esa.beam.framework.ui.UIUtils;
import org.esa.beam.framework.ui.application.support.AbstractToolView;
import org.esa.beam.framework.ui.product.ProductSceneView;
import org.esa.beam.framework.ui.product.spectrum.DisplayableSpectrum;
import org.esa.beam.framework.ui.product.spectrum.SpectrumBand;
import org.esa.beam.framework.ui.product.spectrum.SpectrumChooser;
import org.esa.beam.framework.ui.product.spectrum.SpectrumShapeProvider;
import org.esa.beam.framework.ui.product.spectrum.SpectrumStrokeProvider;
import org.esa.beam.framework.ui.tool.ToolButtonFactory;
import org.esa.beam.jai.ImageManager;
import org.esa.beam.util.Debug;
import org.esa.beam.util.ProductUtils;
import org.esa.beam.visat.VisatApp;
import org.esa.beam.visat.toolviews.nav.CursorSynchronizer;
import org.esa.beam.visat.toolviews.placemark.PlacemarkUtils;
import org.esa.beam.visat.toolviews.stat.XYPlotMarker;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.LegendItem;
import org.jfree.chart.LegendItemCollection;
import org.jfree.chart.LegendItemSource;
import org.jfree.chart.annotations.XYTitleAnnotation;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.block.LineBorder;
import org.jfree.chart.event.AxisChangeEvent;
import org.jfree.chart.event.AxisChangeListener;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.Range;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.HorizontalAlignment;
import org.jfree.ui.RectangleAnchor;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.BevelBorder;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * A window which displays product allSpectra.
 */
public class SpectrumToolView extends AbstractToolView {

    public static final String ID = SpectrumToolView.class.getName();
    public static final String CHART_TITLE = "Spectrum View";

    private static final String SUPPRESS_MESSAGE_KEY = "plugin.spectrum.tip";

    private final Map<Product, DisplayableSpectrum[]> productToAllSpectraMap;
    private final Map<Product, List<SpectrumBand>> productToBandsMap;

    private final ProductNodeListenerAdapter productNodeHandler;
    private final PinSelectionChangeListener pinSelectionChangeListener;
    private final PixelPositionListener pixelPositionListener;

    private AbstractButton filterButton;
    private AbstractButton showSpectrumForCursorButton;
    private AbstractButton showSpectraForSelectedPinsButton;
    private AbstractButton showSpectraForAllPinsButton;
    private AbstractButton showGridButton;
    // todo - not yet implemented for 4.1 but planned for 4.2 (mp - 31.10.2007)
    //    private AbstractButton showAveragePinSpectrumButton;
    //    private AbstractButton showGraphPointsButton;

    private String titleBase;
    private boolean tipShown;
    private ProductSceneView currentView;
    private Product currentProduct;
    private ChartPanel chartPanel;
    private ChartHandler chartHandler;
    private CursorSynchronizer cursorSynchronizer;

    private boolean domainAxisAdjustmentIsFrozen;
    private boolean rangeAxisAdjustmentIsFrozen;
    private boolean isCodeInducedAxisChange;
    private boolean isUserInducedAutomaticAdjustmentChosen;

    public SpectrumToolView() {
        productNodeHandler = new ProductNodeHandler();
        pinSelectionChangeListener = new PinSelectionChangeListener();
        productToAllSpectraMap = new HashMap<Product, DisplayableSpectrum[]>();
        productToBandsMap = new HashMap<Product, List<SpectrumBand>>();
        pixelPositionListener = new CursorSpectrumPixelPositionListener(this);
    }

    private ProductSceneView getCurrentView() {
        return currentView;
    }

    private void setCurrentView(ProductSceneView view) {
        ProductSceneView oldView = currentView;
        currentView = view;
        if (oldView != currentView) {
            if (oldView != null) {
                oldView.removePropertyChangeListener(ProductSceneView.PROPERTY_NAME_SELECTED_PIN,
                        pinSelectionChangeListener);
            }
            if (currentView != null) {
                currentView.addPropertyChangeListener(ProductSceneView.PROPERTY_NAME_SELECTED_PIN,
                        pinSelectionChangeListener);
                setCurrentProduct(currentView.getProduct());
            }
            updateUIState();
        }
    }

    private Product getCurrentProduct() {
        return currentProduct;
    }

    private void setCurrentProduct(Product product) {
        Product oldProduct = currentProduct;
        currentProduct = product;
        if (currentProduct != oldProduct) {
            if (oldProduct != null) {
                oldProduct.removeProductNodeListener(productNodeHandler);
            }
            if (currentProduct != null) {
                currentProduct.addProductNodeListener(productNodeHandler);
                if (!productToAllSpectraMap.containsKey(currentProduct)) {
                    setUpSpectra();
                }
                recreateChart();
            }
            if (currentProduct == null) {
                chartHandler.setEmptyPlot();
            }
            updateUIState();
            updateTitle();
        }
    }

    private void updateTitle() {
        if (currentProduct != null) {
            setTitle(titleBase + " - " + currentView.getProduct().getProductRefString());
        } else {
            setTitle(titleBase);
        }
    }

    private void updateUIState() {
        boolean hasView = getCurrentView() != null;
        boolean hasProduct = getCurrentProduct() != null;
        boolean hasSelectedPins = hasView && getCurrentView().getSelectedPins().length > 0;
        boolean hasPins = hasProduct && getCurrentProduct().getPinGroup().getNodeCount() > 0;
        filterButton.setEnabled(hasProduct);
        showSpectrumForCursorButton.setEnabled(hasView);
        showSpectraForSelectedPinsButton.setEnabled(hasSelectedPins);
        showSpectraForAllPinsButton.setEnabled(hasPins);
        showGridButton.setEnabled(hasView);
        // todo - not yet implemented for 4.1 but planned for 4.2 (mp - 31.10.2007)
        //        showAveragePinSpectrumButton.setEnabled(hasPins); // todo - hasSpectraGraphs
        //        showGraphPointsButton.setEnabled(chartHandlerHasDiagram);
        chartPanel.setEnabled(hasProduct); // todo - hasSpectraGraphs
        showGridButton.setSelected(hasView);
        chartHandler.setGridVisible(showGridButton.isSelected());
    }

    @Override
    public void componentShown() {
        super.componentShown();

    }

    void setPrepareForUpdateMessage() {
        chartHandler.setCollectingSpectralInformationMessage();
    }

    void updateData(int pixelX, int pixelY, int level) {
        chartHandler.setPosition(pixelX, pixelY);
        chartHandler.setLevel(level);
        chartHandler.updateData();
    }

    void updateChart(boolean adjustAxes) {
        chartHandler.setAutomaticRangeAdjustments(adjustAxes);
        updateChart();
    }

    void updateChart() {
        maybeShowTip();
        chartHandler.updateChart();
        chartPanel.repaint();
    }

    private void maybeShowTip() {
        if (!tipShown) {
            final String message = "Tip: If you press the SHIFT key while moving the mouse cursor over \n"
                    + "an image, " + VisatApp.getApp().getAppName() + " adjusts the diagram axes "
                    + "to the local values at the\n"
                    + "current pixel position, if you release the SHIFT key again, then the\n"
                    + "min/max are accumulated again.";
            VisatApp.getApp().showInfoDialog("Spectrum Tip", message, SUPPRESS_MESSAGE_KEY);
            tipShown = true;
        }
    }

    private SpectrumBand[] getAvailableSpectralBands() {
        Debug.assertNotNull(currentProduct);
        if (!productToBandsMap.containsKey(currentProduct)) {
            productToBandsMap.put(currentProduct, new ArrayList<SpectrumBand>());
        }
        List<SpectrumBand> spectrumBands = productToBandsMap.get(currentProduct);
        Band[] bands = currentProduct.getBands();
        for (Band band : bands) {
            if (isSpectralBand(band)) {
                if (!band.isFlagBand()) {
                    boolean isAlreadyIncluded = false;
                    for (SpectrumBand spectrumBand : spectrumBands) {
                        if (spectrumBand.getOriginalBand() == band) {
                            isAlreadyIncluded = true;
                            break;
                        }
                    }
                    if (!isAlreadyIncluded) {
                        spectrumBands.add(new SpectrumBand(band, true));
                    }
                }
            }
        }
        return spectrumBands.toArray(new SpectrumBand[spectrumBands.size()]);
    }

    private boolean isSpectralBand(Band band) {
        return band.getSpectralWavelength() > 0.0;
    }

    @Override
    public JComponent createControl() {
        final JFreeChart chart = ChartFactory.createXYLineChart(CHART_TITLE, "Wavelength (nm)", "", null,
                PlotOrientation.VERTICAL, true, true, false);
        chart.getXYPlot().getRangeAxis().addChangeListener(new AxisChangeListener() {
            @Override
            public void axisChanged(AxisChangeEvent axisChangeEvent) {
                if (!isCodeInducedAxisChange) {
                    rangeAxisAdjustmentIsFrozen = !((ValueAxis) axisChangeEvent.getAxis()).isAutoRange();
                }
            }
        });
        chart.getXYPlot().getDomainAxis().addChangeListener(new AxisChangeListener() {
            @Override
            public void axisChanged(AxisChangeEvent axisChangeEvent) {
                if (!isCodeInducedAxisChange) {
                    domainAxisAdjustmentIsFrozen = !((ValueAxis) axisChangeEvent.getAxis()).isAutoRange();
                }
            }
        });
        chart.getXYPlot().getRangeAxis().setAutoRange(false);
        rangeAxisAdjustmentIsFrozen = false;
        chart.getXYPlot().getDomainAxis().setAutoRange(false);
        domainAxisAdjustmentIsFrozen = false;
        chartPanel = new ChartPanel(chart);
        chartHandler = new ChartHandler(chart);
        final XYPlotMarker plotMarker = new XYPlotMarker(chartPanel, new XYPlotMarker.Listener() {
            @Override
            public void pointSelected(XYDataset xyDataset, int seriesIndex, Point2D dataPoint) {
                if (hasDiagram()) {
                    if (cursorSynchronizer == null) {
                        cursorSynchronizer = new CursorSynchronizer(VisatApp.getApp());
                    }
                    if (!cursorSynchronizer.isEnabled()) {
                        cursorSynchronizer.setEnabled(true);
                    }
                }
            }

            @Override
            public void pointDeselected() {
                cursorSynchronizer.setEnabled(false);
            }
        });

        titleBase = getDescriptor().getTitle();
        filterButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/Filter24.gif"), false);
        filterButton.setName("filterButton");
        filterButton.setEnabled(false);
        filterButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                selectSpectralBands();
                recreateChart();
            }
        });

        showSpectrumForCursorButton = ToolButtonFactory
                .createButton(UIUtils.loadImageIcon("icons/CursorSpectrum24.gif"), true);
        showSpectrumForCursorButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                recreateChart();
            }
        });
        showSpectrumForCursorButton.setName("showSpectrumForCursorButton");
        showSpectrumForCursorButton.setSelected(true);
        showSpectrumForCursorButton.setToolTipText("Show spectrum at cursor position.");

        showSpectraForSelectedPinsButton = ToolButtonFactory
                .createButton(UIUtils.loadImageIcon("icons/SelectedPinSpectra24.gif"), true);
        showSpectraForSelectedPinsButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (isShowingSpectraForAllPins()) {
                    showSpectraForAllPinsButton.setSelected(false);
                } else if (!isShowingSpectraForSelectedPins()) {
                    plotMarker.setInvisible();
                }
                recreateChart();
            }
        });
        showSpectraForSelectedPinsButton.setName("showSpectraForSelectedPinsButton");
        showSpectraForSelectedPinsButton.setToolTipText("Show spectra for selected pins.");

        showSpectraForAllPinsButton = ToolButtonFactory
                .createButton(UIUtils.loadImageIcon("icons/PinSpectra24.gif"), true);
        showSpectraForAllPinsButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (isShowingSpectraForSelectedPins()) {
                    showSpectraForSelectedPinsButton.setSelected(false);
                } else if (!isShowingSpectraForAllPins()) {
                    plotMarker.setInvisible();
                }
                recreateChart();
            }
        });
        showSpectraForAllPinsButton.setName("showSpectraForAllPinsButton");
        showSpectraForAllPinsButton.setToolTipText("Show spectra for all pins.");

        // todo - not yet implemented for 4.1 but planned for 4.2 (mp - 31.10.2007)
        //        showAveragePinSpectrumButton = ToolButtonFactory.createButton(
        //                UIUtils.loadImageIcon("icons/AverageSpectrum24.gif"), true);
        //        showAveragePinSpectrumButton.addActionListener(new ActionListener() {
        //            public void actionPerformed(ActionEvent e) {
        //                // todo - implement
        //            }
        //        });
        //        showAveragePinSpectrumButton.setName("showAveragePinSpectrumButton");
        //        showAveragePinSpectrumButton.setToolTipText("Show average spectrum of all pin spectra.");

        showGridButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/SpectrumGrid24.gif"), true);
        showGridButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                chartHandler.setGridVisible(showGridButton.isSelected());
            }
        });
        showGridButton.setName("showGridButton");
        showGridButton.setToolTipText("Show diagram grid.");

        // todo - not yet implemented for 4.1 but planned for 4.2 (mp - 31.10.2007)
        //        showGraphPointsButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/GraphPoints24.gif"), true);
        //        showGraphPointsButton.addActionListener(new ActionListener() {
        //            public void actionPerformed(ActionEvent e) {
        //                // todo - implement
        //                JOptionPane.showMessageDialog(null, "Not implemented");
        //            }
        //        });
        //        showGraphPointsButton.setName("showGraphPointsButton");
        //        showGraphPointsButton.setToolTipText("Show graph points grid.");

        AbstractButton exportSpectraButton = ToolButtonFactory
                .createButton(UIUtils.loadImageIcon("icons/Export24.gif"), false);
        exportSpectraButton.addActionListener(new SpectraExportAction(this));
        exportSpectraButton.setToolTipText("Export spectra to text file.");
        exportSpectraButton.setName("exportSpectraButton");

        AbstractButton helpButton = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/Help22.png"),
                false);
        helpButton.setName("helpButton");
        helpButton.setToolTipText("Help."); /*I18N*/

        final JPanel buttonPane = GridBagUtils.createPanel();
        final GridBagConstraints gbc = new GridBagConstraints();
        gbc.anchor = GridBagConstraints.CENTER;
        gbc.fill = GridBagConstraints.NONE;
        gbc.insets.top = 2;
        gbc.gridy = 0;
        buttonPane.add(filterButton, gbc);
        gbc.gridy++;
        buttonPane.add(showSpectrumForCursorButton, gbc);
        gbc.gridy++;
        buttonPane.add(showSpectraForSelectedPinsButton, gbc);
        gbc.gridy++;
        buttonPane.add(showSpectraForAllPinsButton, gbc);
        gbc.gridy++;
        // todo - not yet implemented for 4.1 but planned for 4.2 (mp - 31.10.2007)
        //        buttonPane.add(showAveragePinSpectrumButton, gbc);
        //        gbc.gridy++;
        buttonPane.add(showGridButton, gbc);
        gbc.gridy++;
        // todo - not yet implemented for 4.1 but planned for 4.2 (mp - 31.10.2007)
        //        buttonPane.add(showGraphPointsButton, gbc);
        //        gbc.gridy++;
        buttonPane.add(exportSpectraButton, gbc);

        gbc.gridy++;
        gbc.insets.bottom = 0;
        gbc.fill = GridBagConstraints.VERTICAL;
        gbc.weighty = 1.0;
        gbc.gridwidth = 2;
        buttonPane.add(new JLabel(" "), gbc); // filler
        gbc.fill = GridBagConstraints.NONE;
        gbc.weighty = 0.0;
        gbc.gridy = 10;
        gbc.anchor = GridBagConstraints.EAST;
        buttonPane.add(helpButton, gbc);

        chartPanel.setPreferredSize(new Dimension(300, 200));
        chartPanel.setBackground(Color.white);
        chartPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createBevelBorder(BevelBorder.LOWERED), BorderFactory.createEmptyBorder(2, 2, 2, 2)));
        chartPanel.addChartMouseListener(plotMarker);

        JPanel mainPane = new JPanel(new BorderLayout(4, 4));
        mainPane.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
        mainPane.add(BorderLayout.CENTER, chartPanel);
        mainPane.add(BorderLayout.EAST, buttonPane);
        mainPane.setPreferredSize(new Dimension(320, 200));

        if (getDescriptor().getHelpId() != null) {
            HelpSys.enableHelpOnButton(helpButton, getDescriptor().getHelpId());
            HelpSys.enableHelpKey(mainPane, getDescriptor().getHelpId());
        }

        // Add an internal frame listener to VISAT so that we can update our
        // spectrum dialog with the information of the currently activated
        // product scene view.
        //
        VisatApp.getApp().addInternalFrameListener(new SpectrumIFL());

        VisatApp.getApp().getProductManager().addListener(new ProductManager.Listener() {
            @Override
            public void productAdded(ProductManager.Event event) {
                // ignored
            }

            @Override
            public void productRemoved(ProductManager.Event event) {
                final Product product = event.getProduct();
                if (getCurrentProduct() == product) {
                    chartPanel.getChart().getXYPlot().setDataset(null);
                    setCurrentView(null);
                    setCurrentProduct(null);
                }
                if (productToAllSpectraMap.containsKey(product)) {
                    productToAllSpectraMap.remove(product);
                }
                if (productToBandsMap.containsKey(product)) {
                    productToBandsMap.remove(product);
                }
                PlacemarkGroup pinGroup = product.getPinGroup();
                for (int i = 0; i < pinGroup.getNodeCount(); i++) {
                    chartHandler.removePinInformation(pinGroup.get(i));
                }
            }
        });

        final ProductSceneView view = VisatApp.getApp().getSelectedProductSceneView();
        if (view != null) {
            handleViewActivated(view);
        } else {
            setCurrentView(view);
        }
        updateUIState();
        return mainPane;
    }

    private void selectSpectralBands() {
        final DisplayableSpectrum[] allSpectra = productToAllSpectraMap.get(getCurrentProduct());
        final SpectrumChooser spectrumChooser = new SpectrumChooser(getPaneWindow(), allSpectra);
        if (spectrumChooser.show() == ModalDialog.ID_OK) {
            final DisplayableSpectrum[] spectra = spectrumChooser.getSpectra();
            productToAllSpectraMap.put(currentProduct, spectra);
        }
    }

    boolean isShowingCursorSpectrum() {
        return showSpectrumForCursorButton.isSelected();
    }

    private boolean isShowingPinSpectra() {
        return isShowingSpectraForSelectedPins() || isShowingSpectraForAllPins();
    }

    private boolean isShowingSpectraForAllPins() {
        return showSpectraForAllPinsButton.isSelected();
    }

    private void recreateChart() {
        chartHandler.updateData();
        chartHandler.updateChart();
        chartPanel.repaint();
        updateUIState();
    }

    Placemark[] getDisplayedPins() {
        if (isShowingSpectraForSelectedPins() && getCurrentView() != null) {
            return getCurrentView().getSelectedPins();
        } else if (isShowingSpectraForAllPins() && getCurrentProduct() != null) {
            ProductNodeGroup<Placemark> pinGroup = getCurrentProduct().getPinGroup();
            return pinGroup.toArray(new Placemark[pinGroup.getNodeCount()]);
        } else {
            return new Placemark[0];
        }
    }

    private void setUpSpectra() {
        DisplayableSpectrum[] spectra;
        if (!areSpectralBandsAvailable()) {
            spectra = new DisplayableSpectrum[] {};
        } else {
            final Product.AutoGrouping autoGrouping = currentProduct.getAutoGrouping();
            if (autoGrouping != null) {
                final int selectedSpectrumIndex = autoGrouping.indexOf(getCurrentView().getRaster().getName());
                spectra = new DisplayableSpectrum[autoGrouping.size() + 1];
                final Iterator<String[]> iterator = autoGrouping.iterator();
                int i = 0;
                while (iterator.hasNext()) {
                    final String spectrumName = iterator.next()[0];
                    int symbolIndex = SpectrumShapeProvider.getValidIndex(i, false);
                    DisplayableSpectrum spectrum = new DisplayableSpectrum(spectrumName, symbolIndex);
                    spectrum.setSelected(i == selectedSpectrumIndex);
                    spectrum.setLineStyle(SpectrumStrokeProvider.getStroke(i));
                    spectra[i++] = spectrum;
                }
                int symbolIndex = SpectrumShapeProvider.getValidIndex(i, false);
                DisplayableSpectrum defaultSpectrum = new DisplayableSpectrum(
                        DisplayableSpectrum.REMAINING_BANDS_NAME, symbolIndex);
                defaultSpectrum.setSelected(selectedSpectrumIndex == -1);
                spectra[spectra.length - 1] = defaultSpectrum;
                final SpectrumBand[] availableSpectralBands = getAvailableSpectralBands();
                for (SpectrumBand availableSpectralBand : availableSpectralBands) {
                    final String bandName = availableSpectralBand.getName();
                    final int spectrumIndex = autoGrouping.indexOf(bandName);
                    if (spectrumIndex != -1) {
                        spectra[spectrumIndex].addBand(availableSpectralBand);
                    } else {
                        spectra[spectra.length - 1].addBand(availableSpectralBand);
                    }
                }
            } else {
                spectra = new DisplayableSpectrum[1];
                spectra[0] = new DisplayableSpectrum(DisplayableSpectrum.DEFAULT_SPECTRUM_NAME,
                        getAvailableSpectralBands(), 1);
                spectra[0].setLineStyle(SpectrumStrokeProvider.getStroke(0));
            }
        }
        productToAllSpectraMap.put(currentProduct, spectra);
    }

    private DisplayableSpectrum[] getAllSpectra() {
        return productToAllSpectraMap.get(currentProduct);
    }

    private boolean areSpectralBandsAvailable() {
        return getAvailableSpectralBands().length > 0;
    }

    private boolean isShowingSpectraForSelectedPins() {
        return showSpectraForSelectedPinsButton.isSelected();
    }

    // todo - not yet implemented for 4.1 but planned for 5.0 (tf - 5.3.2014)
    //    private boolean isShowingAveragePinSpectrum() {
    //        return showAveragePinSpectrumButton.isSelected();
    //    }

    List<DisplayableSpectrum> getSelectedSpectra() {
        List<DisplayableSpectrum> selectedSpectra = new ArrayList<DisplayableSpectrum>();
        if (currentProduct != null && productToAllSpectraMap.containsKey(currentProduct)) {
            DisplayableSpectrum[] allSpectra = productToAllSpectraMap.get(currentProduct);
            for (DisplayableSpectrum displayableSpectrum : allSpectra) {
                if (displayableSpectrum.isSelected()) {
                    selectedSpectra.add(displayableSpectrum);
                }
            }
        }
        return selectedSpectra;
    }

    private void addBandToSpectra(Band band) {
        DisplayableSpectrum[] allSpectra = productToAllSpectraMap.get(getCurrentProduct());
        Product.AutoGrouping autoGrouping = currentProduct.getAutoGrouping();
        if (autoGrouping != null) {
            final int bandIndex = autoGrouping.indexOf(band.getName());
            final DisplayableSpectrum spectrum;
            if (bandIndex != -1) {
                spectrum = allSpectra[bandIndex];
            } else {
                spectrum = allSpectra[allSpectra.length - 1];
            }
            spectrum.addBand(new SpectrumBand(band, spectrum.isSelected()));
        } else {
            allSpectra[0].addBand(new SpectrumBand(band, true));
        }
    }

    private void removeBandFromSpectra(Band band) {
        DisplayableSpectrum[] allSpectra = productToAllSpectraMap.get(currentProduct);
        for (DisplayableSpectrum displayableSpectrum : allSpectra) {
            Band[] spectralBands = displayableSpectrum.getSpectralBands();
            for (int j = 0; j < spectralBands.length; j++) {
                Band spectralBand = spectralBands[j];
                if (spectralBand == band) {
                    displayableSpectrum.remove(j);
                    if (displayableSpectrum.getSelectedBands().length == 0) {
                        displayableSpectrum.setSelected(false);
                    }
                    return;
                }
            }
        }
    }

    private void updateSpectraUnits() {
        for (DisplayableSpectrum spectrum : getAllSpectra()) {
            spectrum.updateUnit();
        }
    }

    void removeCursorSpectraFromDataset() {
        chartHandler.removeCursorSpectraFromDataset();
    }

    boolean hasDiagram() {
        return chartHandler.hasDiagram();
    }

    private void handleViewActivated(final ProductSceneView view) {
        view.addPixelPositionListener(pixelPositionListener);
        setCurrentView(view);
    }

    private void handleViewDeactivated(final ProductSceneView view) {
        view.removePixelPositionListener(pixelPositionListener);
        setCurrentView(null);
    }

    public boolean hasValidCursorPosition() {
        return chartHandler.hasValidCursorPosition();
    }

    private class ChartHandler {

        private static final String MESSAGE_NO_SPECTRAL_BANDS = "No spectral bands available"; /*I18N*/
        private static final String MESSAGE_NO_PRODUCT_SELECTED = "No product selected";
        private static final String MESSAGE_NO_SPECTRA_SELECTED = "No spectra selected";
        private static final String MESSAGE_COLLECTING_SPECTRAL_INFORMATION = "Collecting spectral information...";

        private final JFreeChart chart;
        private final ChartUpdater chartUpdater;

        private ChartHandler(JFreeChart chart) {
            chartUpdater = new ChartUpdater();
            this.chart = chart;
            setLegend(chart);
            setAutomaticRangeAdjustments(false);
            final XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) chart.getXYPlot().getRenderer();
            renderer.setBaseShapesVisible(true);
            renderer.setBaseShapesFilled(false);
            setPlotMessage(MESSAGE_NO_PRODUCT_SELECTED);
        }

        private void setAutomaticRangeAdjustments(boolean userInducesAutomaticAdjustment) {
            final XYPlot plot = chart.getXYPlot();
            boolean adjustmentHasChanged = false;
            if (userInducesAutomaticAdjustment) {
                if (!isUserInducedAutomaticAdjustmentChosen) {
                    isUserInducedAutomaticAdjustmentChosen = true;
                    if (!isAutomaticDomainAdjustmentSet()) {
                        plot.getDomainAxis().setAutoRange(true);
                        domainAxisAdjustmentIsFrozen = false;
                        adjustmentHasChanged = true;
                    }
                    if (!isAutomaticRangeAdjustmentSet()) {
                        plot.getRangeAxis().setAutoRange(true);
                        rangeAxisAdjustmentIsFrozen = false;
                        adjustmentHasChanged = true;
                    }
                }
            } else {
                if (isUserInducedAutomaticAdjustmentChosen) {
                    isUserInducedAutomaticAdjustmentChosen = false;
                    if (isAutomaticDomainAdjustmentSet()) {
                        plot.getDomainAxis().setAutoRange(false);
                        domainAxisAdjustmentIsFrozen = false;
                        adjustmentHasChanged = true;
                    }
                    if (isAutomaticRangeAdjustmentSet()) {
                        plot.getRangeAxis().setAutoRange(false);
                        rangeAxisAdjustmentIsFrozen = false;
                        adjustmentHasChanged = true;
                    }
                }
            }
            if (adjustmentHasChanged) {
                chartUpdater.invalidatePlotBounds();
            }
        }

        private boolean isAutomaticDomainAdjustmentSet() {
            return chart.getXYPlot().getDomainAxis().isAutoRange();
        }

        private boolean isAutomaticRangeAdjustmentSet() {
            return chart.getXYPlot().getRangeAxis().isAutoRange();
        }

        private void setLegend(JFreeChart chart) {
            chart.removeLegend();
            final LegendTitle legend = new LegendTitle(new SpectrumLegendItemSource());
            legend.setPosition(RectangleEdge.BOTTOM);
            LineBorder border = new LineBorder(Color.BLACK, new BasicStroke(), new RectangleInsets(2, 2, 2, 2));
            legend.setFrame(border);
            chart.addLegend(legend);
        }

        private void setPosition(int pixelX, int pixelY) {
            chartUpdater.setPosition(pixelX, pixelY);
        }

        private void setLevel(int level) {
            chartUpdater.setLevel(level);
        }

        private void updateChart() {
            if (chartUpdater.isDatasetEmpty()) {
                setEmptyPlot();
                return;
            }
            List<DisplayableSpectrum> spectra = getSelectedSpectra();
            chartUpdater.updateChart(chart, spectra);
            chart.getXYPlot().clearAnnotations();
        }

        private void updateData() {
            List<DisplayableSpectrum> spectra = getSelectedSpectra();
            chartUpdater.updateData(chart, spectra);
        }

        private void setEmptyPlot() {
            chart.getXYPlot().setDataset(null);
            if (getCurrentProduct() == null) {
                setPlotMessage(MESSAGE_NO_PRODUCT_SELECTED);
            } else if (!chartUpdater.hasValidCursorPosition()) {
                return;
            } else if (getAllSpectra().length == 0) {
                setPlotMessage(MESSAGE_NO_SPECTRA_SELECTED);
            } else {
                setPlotMessage(MESSAGE_NO_SPECTRAL_BANDS);
            }
        }

        private void setGridVisible(boolean visible) {
            chart.getXYPlot().setDomainGridlinesVisible(visible);
            chart.getXYPlot().setRangeGridlinesVisible(visible);
        }

        private boolean hasDiagram() {
            return chart.getXYPlot().getDataset() != null;
        }

        private void removePinInformation(Placemark pin) {
            chartUpdater.removePinInformation(pin);
        }

        private void removeBandInformation(Band band) {
            chartUpdater.removeBandinformation(band);
        }

        private void setPlotMessage(String messageText) {
            chart.getXYPlot().clearAnnotations();
            TextTitle tt = new TextTitle(messageText);
            tt.setTextAlignment(HorizontalAlignment.RIGHT);
            tt.setFont(chart.getLegend().getItemFont());
            tt.setBackgroundPaint(new Color(200, 200, 255, 50));
            tt.setFrame(new BlockBorder(Color.white));
            tt.setPosition(RectangleEdge.BOTTOM);
            XYTitleAnnotation message = new XYTitleAnnotation(0.5, 0.5, tt, RectangleAnchor.CENTER);
            chart.getXYPlot().addAnnotation(message);
        }

        public boolean hasValidCursorPosition() {
            return chartUpdater.hasValidCursorPosition();
        }

        public void removeCursorSpectraFromDataset() {
            chartUpdater.removeCursorSpectraFromDataset();
        }

        public void setCollectingSpectralInformationMessage() {
            setPlotMessage(MESSAGE_COLLECTING_SPECTRAL_INFORMATION);
        }
    }

    private class ChartUpdater {

        private final static int domain_axis_index = 0;
        private final static int range_axis_index = 1;
        private final static double relativePlotInset = 0.05;

        private final Map<Placemark, Map<Band, Double>> pinToEnergies;
        private int pixelX;
        private int pixelY;
        private int level;
        private Range[] plotBounds;
        private XYSeriesCollection dataset;

        private ChartUpdater() {
            pinToEnergies = new HashMap<Placemark, Map<Band, Double>>();
            plotBounds = new Range[2];
            invalidatePlotBounds();
        }

        void invalidatePlotBounds() {
            plotBounds[domain_axis_index] = null;
            plotBounds[range_axis_index] = null;
        }

        private void setLevel(int level) {
            this.level = level;
        }

        private void setPosition(int pixelX, int pixelY) {
            this.pixelX = pixelX;
            this.pixelY = pixelY;
        }

        private void updateData(JFreeChart chart, List<DisplayableSpectrum> spectra) {
            dataset = new XYSeriesCollection();
            if (level >= 0) {
                fillDatasetWithPinSeries(spectra, dataset, chart);
                // todo - not yet implemented for 4.1 but planned for 5.0 (tf - 5.3.2014)
                //                fillDatasetWithAveragePinSpectrum(spectra, dataset, chart);
                if (hasValidCursorPosition()) {
                    fillDatasetWithCursorSeries(spectra, dataset, chart);
                }
            }
        }

        private void updateChart(JFreeChart chart, List<DisplayableSpectrum> spectra) {
            final XYPlot plot = chart.getXYPlot();
            if (!chartHandler.isAutomaticDomainAdjustmentSet() && !domainAxisAdjustmentIsFrozen) {
                isCodeInducedAxisChange = true;
                updatePlotBounds(dataset.getDomainBounds(true), plot.getDomainAxis(), domain_axis_index);
                isCodeInducedAxisChange = false;
            }
            if (!chartHandler.isAutomaticRangeAdjustmentSet() && !rangeAxisAdjustmentIsFrozen) {
                isCodeInducedAxisChange = true;
                updatePlotBounds(dataset.getRangeBounds(true), plot.getRangeAxis(), range_axis_index);
                isCodeInducedAxisChange = false;
            }
            plot.setDataset(dataset);
            setPlotUnit(spectra, plot);
        }

        private void setPlotUnit(List<DisplayableSpectrum> spectra, XYPlot plot) {
            String unitToBeDisplayed = spectra.get(0).getUnit();
            int i = 1;
            while (i < spectra.size() && !unitToBeDisplayed.equals(DisplayableSpectrum.MIXED_UNITS)) {
                DisplayableSpectrum displayableSpectrum = spectra.get(i++);
                if (displayableSpectrum.hasSelectedBands()
                        && !unitToBeDisplayed.equals(displayableSpectrum.getUnit())) {
                    unitToBeDisplayed = DisplayableSpectrum.MIXED_UNITS;
                }
            }
            isCodeInducedAxisChange = true;
            plot.getRangeAxis().setLabel(unitToBeDisplayed);
            isCodeInducedAxisChange = false;
        }

        private void updatePlotBounds(Range newBounds, ValueAxis axis, int index) {
            if (newBounds != null) {
                final Range axisBounds = axis.getRange();
                final Range oldBounds = this.plotBounds[index];
                this.plotBounds[index] = getNewRange(newBounds, this.plotBounds[index], axisBounds);
                if (oldBounds != this.plotBounds[index]) {
                    axis.setRange(getNewPlotBounds(this.plotBounds[index]));
                }
            }
        }

        private Range getNewRange(Range newBounds, Range currentBounds, Range plotBounds) {
            if (currentBounds == null) {
                currentBounds = newBounds;
            } else {
                if (plotBounds.getLowerBound() > 0 && newBounds.getLowerBound() < currentBounds.getLowerBound()
                        || newBounds.getUpperBound() > currentBounds.getUpperBound()) {
                    currentBounds = new Range(Math.min(currentBounds.getLowerBound(), newBounds.getLowerBound()),
                            Math.max(currentBounds.getUpperBound(), newBounds.getUpperBound()));
                }
            }
            return currentBounds;
        }

        private Range getNewPlotBounds(Range bounds) {
            double range = bounds.getLength();
            double delta = range * relativePlotInset;
            return new Range(Math.max(0, bounds.getLowerBound() - delta), bounds.getUpperBound() + delta);
        }

        private void fillDatasetWithCursorSeries(List<DisplayableSpectrum> spectra, XYSeriesCollection dataset,
                JFreeChart chart) {
            if (isShowingCursorSpectrum() && getCurrentView().isCurrentPixelPosValid()) {
                for (DisplayableSpectrum spectrum : spectra) {
                    XYSeries series = new XYSeries(spectrum.getName());
                    final Band[] spectralBands = spectrum.getSelectedBands();
                    for (Band spectralBand : spectralBands) {
                        final float wavelength = spectralBand.getSpectralWavelength();
                        final double energy = ProductUtils.getGeophysicalSampleDouble(spectralBand, pixelX, pixelY,
                                level);
                        if (energy != spectralBand.getGeophysicalNoDataValue()) {
                            series.add(wavelength, energy);
                        }
                    }
                    updateRenderer(dataset.getSeriesCount(), Color.BLACK, spectrum, chart);
                    dataset.addSeries(series);
                }
            }
        }

        private void fillDatasetWithPinSeries(List<DisplayableSpectrum> spectra, XYSeriesCollection dataset,
                JFreeChart chart) {
            Placemark[] pins = getDisplayedPins();
            for (Placemark pin : pins) {
                List<XYSeries> pinSeries = createXYSeriesFromPin(pin, dataset.getSeriesCount(), spectra, chart);
                for (XYSeries series : pinSeries) {
                    dataset.addSeries(series);
                }
            }
        }

        private List<XYSeries> createXYSeriesFromPin(Placemark pin, int seriesIndex,
                List<DisplayableSpectrum> spectra, JFreeChart chart) {
            List<XYSeries> pinSeries = new ArrayList<XYSeries>();
            Color pinColor = PlacemarkUtils.getPlacemarkColor(pin, getCurrentView());
            for (DisplayableSpectrum spectrum : spectra) {
                XYSeries series = new XYSeries(spectrum.getName() + "_" + pin.getLabel());
                final Band[] spectralBands = spectrum.getSelectedBands();
                Map<Band, Double> bandToEnergy;
                if (pinToEnergies.containsKey(pin)) {
                    bandToEnergy = pinToEnergies.get(pin);
                } else {
                    bandToEnergy = new HashMap<Band, Double>();
                    pinToEnergies.put(pin, bandToEnergy);
                }
                for (Band spectralBand : spectralBands) {
                    double energy;
                    if (bandToEnergy.containsKey(spectralBand)) {
                        energy = bandToEnergy.get(spectralBand);
                    } else {
                        energy = readEnergy(pin, spectralBand);
                        bandToEnergy.put(spectralBand, energy);
                    }
                    final float wavelength = spectralBand.getSpectralWavelength();
                    if (energy != spectralBand.getGeophysicalNoDataValue()) {
                        series.add(wavelength, energy);
                    }
                }
                updateRenderer(seriesIndex++, pinColor, spectrum, chart);
                pinSeries.add(series);
            }
            return pinSeries;
        }

        // todo - not yet implemented for 4.1 but planned for 5.0 (tf - 5.3.2014)
        //        private void fillDatasetWithAveragePinSpectrum(List<DisplayableSpectrum> spectra, XYSeriesCollection dataset, JFreeChart chart) {
        //            if(!isShowingAveragePinSpectrum()) {
        //                return;
        //            }
        //            ProductNodeGroup<Placemark> pinGroup = getCurrentProduct().getPinGroup();
        //            for(int i = 0; i < pinGroup.getNodeCount(); i++) {
        //                Placemark pin = pinGroup.get(i);
        //                for (DisplayableSpectrum spectrum : spectra) {
        ////                    XYSeries series = new XYSeries(spectrum.getName() + "_" + pin.getLabel());
        //                    final Band[] spectralBands = spectrum.getSelectedBands();
        //                    Map<Band, Double> bandToEnergy;
        //                    if (pinToEnergies.containsKey(pin)) {
        //                        bandToEnergy = pinToEnergies.get(pin);
        //                    } else {
        //                        bandToEnergy = new HashMap<Band, Double>();
        //                        pinToEnergies.put(pin, bandToEnergy);
        //                    }
        //                    for (Band spectralBand : spectralBands) {
        //                        double energy;
        //                        if (bandToEnergy.containsKey(spectralBand)) {
        //                            energy = bandToEnergy.get(spectralBand);
        //                        } else {
        //                            energy = readEnergy(pin, spectralBand);
        //                            bandToEnergy.put(spectralBand, energy);
        //                        }
        //                        final float wavelength = spectralBand.getSpectralWavelength();
        //                        if (energy != spectralBand.getGeophysicalNoDataValue()) {
        //                            series.add(wavelength, energy);
        //                        }
        //                    }
        ////                    updateRenderer(seriesIndex++, pinColor, spectrum, chart);
        ////                    pinSeries.add(series);
        //                }
        //            }
        //
        //        }

        private void updateRenderer(int seriesIndex, Color seriesColor, DisplayableSpectrum spectrum,
                JFreeChart chart) {
            final XYItemRenderer renderer = chart.getXYPlot().getRenderer();
            renderer.setSeriesPaint(seriesIndex, seriesColor);
            final Stroke lineStyle = spectrum.getLineStyle();
            renderer.setSeriesStroke(seriesIndex, lineStyle);
            Shape symbol = spectrum.getScaledShape();
            renderer.setSeriesShape(seriesIndex, symbol);
        }

        private double readEnergy(Placemark pin, Band spectralBand) {
            final MultiLevelModel multiLevelModel = ImageManager.getMultiLevelModel(spectralBand);
            final AffineTransform i2mTransform = multiLevelModel.getImageToModelTransform(0);
            final AffineTransform m2iTransform = multiLevelModel.getModelToImageTransform(level);
            final Point2D modelPixel = i2mTransform.transform(pin.getPixelPos(), null);
            final Point2D imagePixel = m2iTransform.transform(modelPixel, null);
            int pinPixelX = (int) Math.floor(imagePixel.getX());
            int pinPixelY = (int) Math.floor(imagePixel.getY());
            return ProductUtils.getGeophysicalSampleDouble(spectralBand, pinPixelX, pinPixelY, level);
        }

        private void removePinInformation(Placemark pin) {
            pinToEnergies.remove(pin);
        }

        private void removeBandinformation(Band band) {
            for (Placemark pin : pinToEnergies.keySet()) {
                Map<Band, Double> bandToEnergiesMap = pinToEnergies.get(pin);
                if (bandToEnergiesMap.containsKey(band)) {
                    bandToEnergiesMap.remove(band);
                }
            }
        }

        public boolean hasValidCursorPosition() {
            return pixelX > Integer.MIN_VALUE && pixelY > Integer.MIN_VALUE;
        }

        void removeCursorSpectraFromDataset() {
            if (hasValidCursorPosition()) {
                pixelX = Integer.MIN_VALUE;
                pixelY = Integer.MIN_VALUE;
                int numberOfSelectedSpectra = getSelectedSpectra().size();
                int numberOfPins = getDisplayedPins().length;
                int numberOfDisplayedGraphs = numberOfPins * numberOfSelectedSpectra;
                while (dataset.getSeriesCount() > numberOfDisplayedGraphs) {
                    dataset.removeSeries(dataset.getSeriesCount() - 1);
                }
            }
        }

        public boolean isDatasetEmpty() {
            return dataset == null || dataset.getSeriesCount() == 0;
        }

    }

    private class SpectrumLegendItemSource implements LegendItemSource {

        @Override
        public LegendItemCollection getLegendItems() {
            LegendItemCollection itemCollection = new LegendItemCollection();
            final Placemark[] displayedPins = getDisplayedPins();
            final List<DisplayableSpectrum> spectra = getSelectedSpectra();
            for (Placemark pin : displayedPins) {
                Paint pinPaint = PlacemarkUtils.getPlacemarkColor(pin, getCurrentView());
                for (DisplayableSpectrum spectrum : spectra) {
                    if (spectrum.hasSelectedBands()) {
                        String legendLabel = pin.getLabel() + "_" + spectrum.getName();
                        LegendItem item = createLegendItem(spectrum, pinPaint, legendLabel);
                        itemCollection.add(item);
                    }
                }
            }
            if (isShowingCursorSpectrum() && hasValidCursorPosition()) {
                for (DisplayableSpectrum spectrum : spectra) {
                    if (spectrum.hasSelectedBands()) {
                        Paint defaultPaint = Color.BLACK;
                        LegendItem item = createLegendItem(spectrum, defaultPaint, spectrum.getName());
                        itemCollection.add(item);
                    }
                }
            }
            return itemCollection;
        }

        private LegendItem createLegendItem(DisplayableSpectrum spectrum, Paint paint, String legendLabel) {
            Stroke outlineStroke = new BasicStroke();
            Line2D lineShape = new Line2D.Double(0, 5, 40, 5);
            Stroke lineStyle = spectrum.getLineStyle();
            Shape symbol = spectrum.getScaledShape();
            return new LegendItem(legendLabel, legendLabel, legendLabel, legendLabel, true, symbol, false, paint,
                    true, paint, outlineStroke, true, lineShape, lineStyle, paint);
        }

    }

    /////////////////////////////////////////////////////////////////////////
    // View change handling

    private class SpectrumIFL extends InternalFrameAdapter {

        @Override
        public void internalFrameActivated(InternalFrameEvent e) {
            final Container contentPane = e.getInternalFrame().getContentPane();
            if (contentPane instanceof ProductSceneView) {
                handleViewActivated((ProductSceneView) contentPane);
            }
        }

        @Override
        public void internalFrameDeactivated(InternalFrameEvent e) {
            final Container contentPane = e.getInternalFrame().getContentPane();
            if (contentPane instanceof ProductSceneView) {
                handleViewDeactivated((ProductSceneView) contentPane);
            }
        }
    }

    /////////////////////////////////////////////////////////////////////////
    // Product change handling

    private class ProductNodeHandler extends ProductNodeListenerAdapter {

        @Override
        public void nodeChanged(final ProductNodeEvent event) {
            boolean chartHasChanged = false;
            if (event.getSourceNode() instanceof Band) {
                final String propertyName = event.getPropertyName();
                if (propertyName.equals(DataNode.PROPERTY_NAME_UNIT)) {
                    updateSpectraUnits();
                    chartHasChanged = true;
                } else if (propertyName.equals(Band.PROPERTY_NAME_SPECTRAL_WAVELENGTH)) {
                    setUpSpectra();
                    chartHasChanged = true;
                }
            } else if (event.getSourceNode() instanceof Placemark) {
                if (event.getPropertyName().equals("geoPos") || event.getPropertyName().equals("pixelPos")) {
                    chartHandler.removePinInformation((Placemark) event.getSourceNode());
                }
                if (isShowingPinSpectra()) {
                    chartHasChanged = true;
                }
            } else if (event.getSourceNode() instanceof Product) {
                if (event.getPropertyName().equals("autoGrouping")) {
                    setUpSpectra();
                    chartHasChanged = true;
                }
            }
            if (isActive() && chartHasChanged) {
                recreateChart();
            }
        }

        @Override
        public void nodeAdded(final ProductNodeEvent event) {
            if (!isActive()) {
                return;
            }
            if (event.getSourceNode() instanceof Band) {
                Band newBand = (Band) event.getSourceNode();
                if (isSpectralBand(newBand)) {
                    addBandToSpectra((Band) event.getSourceNode());
                    recreateChart();
                }
            } else if (event.getSourceNode() instanceof Placemark) {
                if (isShowingPinSpectra()) {
                    recreateChart();
                } else {
                    updateUIState();
                }
            }
        }

        @Override
        public void nodeRemoved(final ProductNodeEvent event) {
            if (!isActive()) {
                return;
            }
            if (event.getSourceNode() instanceof Band) {
                Band band = (Band) event.getSourceNode();
                removeBandFromSpectra(band);
                chartHandler.removeBandInformation(band);
                recreateChart();
            } else if (event.getSourceNode() instanceof Placemark) {
                if (isShowingPinSpectra()) {
                    recreateChart();
                }
            }
        }

        private boolean isActive() {
            return isVisible() && getCurrentProduct() != null;
        }
    }

    private class PinSelectionChangeListener implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            recreateChart();
        }

    }

}