org.esa.snap.rcp.statistics.StatisticsPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.esa.snap.rcp.statistics.StatisticsPanel.java

Source

/*
 * Copyright (C) 2012 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.rcp.statistics;

import com.bc.ceres.binding.PropertyContainer;
import com.bc.ceres.binding.PropertyDescriptor;
import com.bc.ceres.binding.ValueRange;
import com.bc.ceres.binding.validators.IntervalValidator;
import com.bc.ceres.core.ProgressMonitor;
import com.bc.ceres.core.SubProgressMonitor;
import com.bc.ceres.swing.binding.BindingContext;
import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker;
import org.esa.snap.core.datamodel.Mask;
import org.esa.snap.core.datamodel.ProductNodeGroup;
import org.esa.snap.core.datamodel.RasterDataNode;
import org.esa.snap.core.datamodel.Stx;
import org.esa.snap.core.datamodel.StxFactory;
import org.esa.snap.core.datamodel.VectorDataNode;
import org.esa.snap.core.util.StringUtils;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.statistics.output.Util;
import org.esa.snap.ui.GridBagUtils;
import org.esa.snap.ui.UIUtils;
import org.esa.snap.ui.tool.ToolButtonFactory;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYBarPainter;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.xy.XIntervalSeries;
import org.jfree.data.xy.XIntervalSeriesCollection;
import org.jfree.ui.RectangleInsets;
import org.openide.windows.TopComponent;

import javax.media.jai.Histogram;
import javax.swing.AbstractButton;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.JTable;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingWorker;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.DecimalFormat;
import java.util.List;

/**
 * A general pane within the statistics window.
 *
 * @author Norman Fomferra
 * @author Marco Peters
 */
class StatisticsPanel extends PagePanel implements MultipleRoiComputePanel.ComputeMasks, StatisticsDataProvider {

    private static final String DEFAULT_STATISTICS_TEXT = "No statistics computed yet."; /*I18N*/
    private static final String TITLE_PREFIX = "Statistics";

    private MultipleRoiComputePanel computePanel;
    private JPanel backgroundPanel;
    private AbstractButton hideAndShowButton;
    private AbstractButton exportButton;
    private JPanel contentPanel;

    private final StatisticsPanel.PopupHandler popupHandler;
    private final StringBuilder resultText;
    private boolean init;
    private Histogram[] histograms;
    private ExportStatisticsAsCsvAction exportAsCsvAction;
    private PutStatisticsIntoVectorDataAction putStatisticsIntoVectorDataAction;
    private AccuracyModel accuracyModel;

    public StatisticsPanel(final TopComponent parentDialog, String helpID) {
        super(parentDialog, helpID, TITLE_PREFIX);
        setMinimumSize(new Dimension(1000, 390));
        resultText = new StringBuilder();
        popupHandler = new PopupHandler();
    }

    @Override
    protected void initComponents() {
        init = true;

        computePanel = new MultipleRoiComputePanel(this, getRaster());
        exportButton = getExportButton();

        final JPanel exportAndHelpPanel = GridBagUtils.createPanel();
        GridBagConstraints helpPanelConstraints = GridBagUtils
                .createConstraints("anchor=NORTHWEST,fill=HORIZONTAL,insets.top=2,weightx=1,ipadx=0");
        GridBagUtils.addToPanel(exportAndHelpPanel, new JSeparator(), helpPanelConstraints,
                "fill=HORIZONTAL,gridwidth=2,insets.left=5,insets.right=5");
        GridBagUtils.addToPanel(exportAndHelpPanel, exportButton, helpPanelConstraints,
                "gridy=1,anchor=WEST,fill=NONE");
        GridBagUtils.addToPanel(exportAndHelpPanel, getHelpButton(), helpPanelConstraints,
                "gridx=1,gridy=1,anchor=EAST,fill=NONE");

        final JPanel rightPanel = GridBagUtils.createPanel();
        GridBagConstraints extendedOptionsPanelConstraints = GridBagUtils
                .createConstraints("anchor=NORTHWEST,fill=HORIZONTAL,insets.top=2,weightx=1,insets.right=-2");
        GridBagUtils.addToPanel(rightPanel, computePanel, extendedOptionsPanelConstraints,
                "gridy=0,fill=BOTH,weighty=1");
        GridBagUtils.addToPanel(rightPanel, createAccuracyPanel(), extendedOptionsPanelConstraints,
                "gridy=1,fill=BOTH,weighty=1");
        GridBagUtils.addToPanel(rightPanel, exportAndHelpPanel, extendedOptionsPanelConstraints,
                "gridy=2,anchor=SOUTHWEST,fill=HORIZONTAL,weighty=0");

        final ImageIcon collapseIcon = UIUtils.loadImageIcon("icons/PanelRight12.png");
        final ImageIcon collapseRolloverIcon = ToolButtonFactory.createRolloverIcon(collapseIcon);
        final ImageIcon expandIcon = UIUtils.loadImageIcon("icons/PanelLeft12.png");
        final ImageIcon expandRolloverIcon = ToolButtonFactory.createRolloverIcon(expandIcon);

        hideAndShowButton = ToolButtonFactory.createButton(collapseIcon, false);
        hideAndShowButton.setToolTipText("Collapse Options Panel");
        hideAndShowButton.setName("switchToChartButton");
        hideAndShowButton.addActionListener(new ActionListener() {

            public boolean rightPanelShown;

            @Override
            public void actionPerformed(ActionEvent e) {
                rightPanel.setVisible(rightPanelShown);
                if (rightPanelShown) {
                    hideAndShowButton.setIcon(collapseIcon);
                    hideAndShowButton.setRolloverIcon(collapseRolloverIcon);
                    hideAndShowButton.setToolTipText("Collapse Options Panel");
                } else {
                    hideAndShowButton.setIcon(expandIcon);
                    hideAndShowButton.setRolloverIcon(expandRolloverIcon);
                    hideAndShowButton.setToolTipText("Expand Options Panel");
                }
                rightPanelShown = !rightPanelShown;
            }
        });

        contentPanel = new JPanel(new GridLayout(-1, 1));
        contentPanel.setBackground(Color.WHITE);
        contentPanel.addMouseListener(popupHandler);

        final JScrollPane contentScrollPane = new JScrollPane(contentPanel);
        contentScrollPane.setBorder(null);
        contentScrollPane.setBackground(Color.WHITE);

        backgroundPanel = new JPanel(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        GridBagUtils.addToPanel(backgroundPanel, contentScrollPane, gbc,
                "fill=BOTH, weightx=1.0, weighty=1.0, anchor=NORTH");
        GridBagUtils.addToPanel(backgroundPanel, rightPanel, gbc, "gridx=1, fill=VERTICAL, weightx=0.0");

        JLayeredPane layeredPane = new JLayeredPane();
        layeredPane.add(backgroundPanel);
        layeredPane.add(hideAndShowButton);
        add(layeredPane);
    }

    private JPanel createAccuracyPanel() {
        final JPanel accuracyPanel = new JPanel(new GridBagLayout());
        final GridBagConstraints gbc = new GridBagConstraints();
        final JLabel label = new JLabel("Histogram accuracy:");

        accuracyModel = new AccuracyModel();
        final BindingContext bindingContext = new BindingContext(
                PropertyContainer.createObjectBacked(accuracyModel));
        final SpinnerNumberModel accuracyNumberModel = new SpinnerNumberModel(accuracyModel.accuracy, 0,
                Util.MAX_ACCURACY, 1);
        final JSpinner accuracySpinner = new JSpinner(accuracyNumberModel);
        ((JSpinner.DefaultEditor) accuracySpinner.getEditor()).getTextField().setEditable(false);
        bindingContext.bind("accuracy", accuracySpinner);
        final JCheckBox checkBox = new JCheckBox("Auto accuracy");
        bindingContext.bind("useAutoAccuracy", checkBox);

        final IntervalValidator rangeValidator = new IntervalValidator(new ValueRange(0, Util.MAX_ACCURACY));
        final PropertyDescriptor accuracyDescriptor = bindingContext.getPropertySet().getDescriptor("accuracy");
        accuracyDescriptor.setValidator(rangeValidator);

        checkBox.setSelected(accuracyModel.useAutoAccuracy);

        bindingContext.getPropertySet().getProperty("useAutoAccuracy")
                .addPropertyChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent evt) {
                        label.setEnabled(!checkBox.isSelected());
                        accuracySpinner.setEnabled(!checkBox.isSelected());
                        if (checkBox.isSelected()) {
                            bindingContext.getBinding("accuracy").setPropertyValue(3);
                        }
                        computePanel.updateEnablement();
                    }
                });

        label.setEnabled(false);
        accuracySpinner.setEnabled(false);
        accuracySpinner.setToolTipText("Specify the number of histogram bins (#bins: 10^accuracy).");
        accuracySpinner.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                computePanel.updateEnablement();
            }
        });

        GridBagUtils.addToPanel(accuracyPanel, new TitledSeparator("Histogram accuracy"), gbc,
                "fill=HORIZONTAL, weightx=1.0,anchor=NORTH,gridwidth=2");
        GridBagUtils.addToPanel(accuracyPanel, checkBox, gbc, "gridy=1,insets.left=5,insets.top=2");
        GridBagUtils.addToPanel(accuracyPanel, label, gbc,
                "gridy=2, insets.left=26,weightx=0.0,fill=NONE,anchor=WEST,gridwidth=1");
        GridBagUtils.addToPanel(accuracyPanel, accuracySpinner, gbc,
                "gridx=1,weightx=1.0,fill=HORIZONTAL,insets.right=5,insets.left=5");
        return accuracyPanel;
    }

    @Override
    protected void updateComponents() {
        if (!init) {
            initComponents();
        }

        final RasterDataNode raster = getRaster();
        computePanel.setRaster(raster);
        contentPanel.removeAll();
        resultText.setLength(0);

        if (raster != null && raster.isStxSet() && raster.getStx().getResolutionLevel() == 0) {
            resultText.append(createText(raster.getStx(), null));
            contentPanel.add(createStatPanel(raster.getStx(), null));
            histograms = new Histogram[] { raster.getStx().getHistogram() };
            exportAsCsvAction = new ExportStatisticsAsCsvAction(this);
            putStatisticsIntoVectorDataAction = new PutStatisticsIntoVectorDataAction(this);
            exportButton.setEnabled(true);
        } else {
            contentPanel.add(new JLabel(DEFAULT_STATISTICS_TEXT));
            exportButton.setEnabled(false);
        }

        contentPanel.revalidate();
        contentPanel.repaint();
    }

    @Override
    public Histogram[] getHistograms() {
        return histograms;
    }

    private static class ComputeResult {

        final Stx stx;
        final Mask mask;

        ComputeResult(Stx stx, Mask mask) {
            this.stx = stx;
            this.mask = mask;
        }
    }

    @Override
    public void compute(final Mask[] selectedMasks) {
        this.histograms = new Histogram[selectedMasks.length];
        final String title = "Computing Statistics";
        SwingWorker<Object, ComputeResult> swingWorker = new ProgressMonitorSwingWorker<Object, ComputeResult>(this,
                title) {

            @Override
            protected Object doInBackground(ProgressMonitor pm) {
                pm.beginTask(title, selectedMasks.length);
                try {
                    final int binCount = Util.computeBinCount(accuracyModel.accuracy);
                    for (int i = 0; i < selectedMasks.length; i++) {
                        final Mask mask = selectedMasks[i];
                        final Stx stx;
                        ProgressMonitor subPm = SubProgressMonitor.create(pm, 1);
                        if (mask == null) {
                            stx = new StxFactory().withHistogramBinCount(binCount).create(getRaster(), subPm);
                            getRaster().setStx(stx);
                        } else {
                            stx = new StxFactory().withHistogramBinCount(binCount).withRoiMask(mask)
                                    .create(getRaster(), subPm);
                        }
                        histograms[i] = stx.getHistogram();
                        publish(new ComputeResult(stx, mask));
                    }
                } finally {
                    pm.done();
                }
                return null;
            }

            @Override
            protected void process(List<ComputeResult> chunks) {

                for (ComputeResult result : chunks) {

                    final Stx stx = result.stx;
                    final Mask mask = result.mask;

                    if (resultText.length() > 0) {
                        resultText.append("\n");
                    }
                    resultText.append(createText(stx, mask));

                    JPanel statPanel = createStatPanel(stx, mask);
                    contentPanel.add(statPanel);
                    contentPanel.revalidate();
                    contentPanel.repaint();
                }
            }

            @Override
            protected void done() {
                try {
                    get();
                    if (exportAsCsvAction == null) {
                        exportAsCsvAction = new ExportStatisticsAsCsvAction(StatisticsPanel.this);
                    }
                    exportAsCsvAction.setSelectedMasks(selectedMasks);
                    if (putStatisticsIntoVectorDataAction == null) {
                        putStatisticsIntoVectorDataAction = new PutStatisticsIntoVectorDataAction(
                                StatisticsPanel.this);
                    }
                    putStatisticsIntoVectorDataAction.setSelectedMasks(selectedMasks);
                    exportButton.setEnabled(true);
                } catch (Exception e) {
                    e.printStackTrace();
                    Dialogs.showMessage("<html>Statistics",
                            "Failed to compute statistics.<br/>An error occurred:" + e.getMessage() + "</html>",
                            JOptionPane.ERROR_MESSAGE, null);
                }
            }
        };

        resultText.setLength(0);
        contentPanel.removeAll();

        swingWorker.execute();
    }

    private JPanel createStatPanel(Stx stx, final Mask mask) {
        final Histogram histogram = stx.getHistogram();

        XIntervalSeries histogramSeries = new XIntervalSeries("Histogram");
        int[] bins = histogram.getBins(0);
        for (int j = 0; j < bins.length; j++) {
            histogramSeries.add(histogram.getBinLowValue(0, j), histogram.getBinLowValue(0, j),
                    j < bins.length - 1 ? histogram.getBinLowValue(0, j + 1) : histogram.getHighValue(0), bins[j]);
        }
        ChartPanel histogramPanel = createChartPanel(histogramSeries, "Value", "#Pixels", new Color(0, 0, 127));

        XIntervalSeries percentileSeries = new XIntervalSeries("Percentile");
        percentileSeries.add(0, 0, 1, histogram.getLowValue(0));
        for (int j = 1; j < 99; j++) {
            percentileSeries.add(j, j, j + 1, histogram.getPTileThreshold(j / 100.0)[0]);
        }
        percentileSeries.add(99, 99, 100, histogram.getHighValue(0));

        ChartPanel percentilePanel = createChartPanel(percentileSeries, "Percentile (%)", "Value Threshold",
                new Color(127, 0, 0));

        Object[][] tableData = new Object[][] { new Object[] { "#Pixels total:", histogram.getTotals()[0] },
                new Object[] { "Minimum:", stx.getMinimum() }, new Object[] { "Maximum:", stx.getMaximum() },
                new Object[] { "Mean:", stx.getMean() }, new Object[] { "Sigma:", stx.getStandardDeviation() },
                new Object[] { "Median:", stx.getMedian() },
                new Object[] { "Coef Variation:", stx.getCoefficientOfVariation() },
                new Object[] { "ENL:", stx.getEquivalentNumberOfLooks() },
                new Object[] { "P75 threshold:", histogram.getPTileThreshold(0.75)[0] },
                new Object[] { "P80 threshold:", histogram.getPTileThreshold(0.80)[0] },
                new Object[] { "P85 threshold:", histogram.getPTileThreshold(0.85)[0] },
                new Object[] { "P90 threshold:", histogram.getPTileThreshold(0.90)[0] },
                new Object[] { "Max error:", getBinSize(histogram) }, };

        JPanel plotContainerPanel = new JPanel(new GridLayout(1, 2));
        plotContainerPanel.add(histogramPanel);
        plotContainerPanel.add(percentilePanel);

        TableModel tableModel = new DefaultTableModel(tableData, new String[] { "Name", "Value" }) {
            @Override
            public Class<?> getColumnClass(int columnIndex) {
                return columnIndex == 0 ? String.class : Number.class;
            }

            @Override
            public boolean isCellEditable(int row, int column) {
                return false;
            }
        };

        final JTable table = new JTable(tableModel);
        table.setDefaultRenderer(Number.class, new DefaultTableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                    boolean hasFocus, int row, int column) {
                final Component label = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row,
                        column);
                if (value instanceof Float || value instanceof Double) {
                    setHorizontalTextPosition(RIGHT);
                    setText(getFormattedValue((Number) value));
                }
                return label;
            }

            private String getFormattedValue(Number value) {
                if (value.doubleValue() < 0.001 && value.doubleValue() > -0.001 && value.doubleValue() != 0.0) {
                    return new DecimalFormat("0.####E0").format(value.doubleValue());
                }
                return String.format("%.4f", value.doubleValue());
            }
        });
        table.addMouseListener(popupHandler);

        JPanel textContainerPanel = new JPanel(new BorderLayout(2, 2));
        textContainerPanel.setBackground(Color.WHITE);
        textContainerPanel.add(table, BorderLayout.CENTER);

        JPanel statPanel = new JPanel(new BorderLayout(4, 4));
        statPanel.setBorder(new EmptyBorder(10, 2, 10, 2));
        statPanel.setBackground(Color.WHITE);
        statPanel.add(new JLabel(getSubPanelTitle(mask)), BorderLayout.NORTH);
        statPanel.add(textContainerPanel, BorderLayout.WEST);
        statPanel.add(plotContainerPanel, BorderLayout.CENTER);

        return statPanel;
    }

    static double getBinSize(Histogram histogram) {
        return (histogram.getHighValue(0) - histogram.getLowValue(0)) / histogram.getNumBins(0);
    }

    private String getSubPanelTitle(Mask mask) {
        final String title;
        if (mask != null) {
            title = String.format("<html><b>%s</b> with ROI-mask <b>%s</b></html>", getRaster().getName(),
                    mask.getName());
        } else {
            title = String.format("<html><b>%s</b></html>", getRaster().getName());
        }
        return title;
    }

    @Override
    protected String getDataAsText() {
        return resultText.toString();
    }

    private String createText(final Stx stx, final Mask mask) {

        if (stx.getSampleCount() == 0) {
            if (mask != null) {
                return "The ROI-Mask '" + mask.getName() + "' is empty.";
            } else {
                return "The scene contains no valid pixels.";
            }
        }

        RasterDataNode raster = getRaster();
        boolean maskUsed = mask != null;
        final String unit = (StringUtils.isNotNullAndNotEmpty(raster.getUnit()) ? raster.getUnit() : "1");
        final long numPixelTotal = (long) raster.getRasterWidth() * (long) raster.getRasterHeight();
        final StringBuilder sb = new StringBuilder(1024);

        sb.append("Only ROI-mask pixels considered:\t");
        sb.append(maskUsed ? "Yes" : "No");
        sb.append("\n");

        if (maskUsed) {
            sb.append("ROI-mask name:\t");
            sb.append(mask.getName());
            sb.append("\n");
        }

        sb.append("Number of pixels total:\t");
        sb.append(numPixelTotal);
        sb.append("\n");

        sb.append("Number of considered pixels:\t");
        sb.append(stx.getSampleCount());
        sb.append("\n");

        sb.append("Ratio of considered pixels:\t");
        sb.append(100.0 * stx.getSampleCount() / numPixelTotal);
        sb.append("\t");
        sb.append("%");
        sb.append("\n");

        sb.append("Minimum:\t");
        sb.append(stx.getMinimum());
        sb.append("\t");
        sb.append(unit);
        sb.append("\n");

        sb.append("Maximum:\t");
        sb.append(stx.getMaximum());
        sb.append("\t");
        sb.append(unit);
        sb.append("\n");

        sb.append("Mean:\t");
        sb.append(stx.getMean());
        sb.append("\t");
        sb.append(unit);
        sb.append("\n");

        sb.append("Standard deviation:\t");
        sb.append(stx.getStandardDeviation());
        sb.append("\t");
        sb.append(unit);
        sb.append("\n");

        sb.append("Coefficient of variation:\t");
        sb.append(getCoefficientOfVariation(stx));
        sb.append("\t");
        sb.append("");
        sb.append("\n");

        sb.append("Median:\t");
        sb.append(stx.getMedian());
        sb.append("\t ");
        sb.append(unit);
        sb.append("\n");

        for (int percentile = 5; percentile <= 95; percentile += 5) {
            sb.append("P").append(percentile).append(" threshold:\t");
            sb.append(stx.getHistogram().getPTileThreshold(percentile / 100.0)[0]);
            sb.append("\t");
            sb.append(unit);
            sb.append("\n");
        }

        sb.append("Threshold max error:\t");
        sb.append(getBinSize(stx.getHistogram()));
        sb.append("\t");
        sb.append(unit);
        sb.append("\n");

        return sb.toString();
    }

    private double getCoefficientOfVariation(Stx stx) {
        return stx.getStandardDeviation() / stx.getMean();
    }

    @Override
    public void doLayout() {
        super.doLayout();
        backgroundPanel.setBounds(0, 0, getWidth() - 8, getHeight() - 8);
        hideAndShowButton.setBounds(getWidth() - hideAndShowButton.getWidth() - 12, 6, 24, 24);
    }

    private static ChartPanel createChartPanel(XIntervalSeries percentileSeries, String xAxisLabel,
            String yAxisLabel, Color color) {
        XIntervalSeriesCollection percentileDataset = new XIntervalSeriesCollection();
        percentileDataset.addSeries(percentileSeries);
        return getHistogramPlotPanel(percentileDataset, xAxisLabel, yAxisLabel, color);
    }

    private static ChartPanel getHistogramPlotPanel(XIntervalSeriesCollection dataset, String xAxisLabel,
            String yAxisLabel, Color color) {
        JFreeChart chart = ChartFactory.createHistogram(null, xAxisLabel, yAxisLabel, dataset,
                PlotOrientation.VERTICAL, false, // Legend?
                true, // tooltips
                false // url
        );
        final XYPlot xyPlot = chart.getXYPlot();
        //xyPlot.setForegroundAlpha(0.85f);
        xyPlot.setNoDataMessage("No data");
        xyPlot.setAxisOffset(new RectangleInsets(5, 5, 5, 5));

        final XYBarRenderer renderer = (XYBarRenderer) xyPlot.getRenderer();
        renderer.setDrawBarOutline(false);
        renderer.setShadowVisible(false);
        renderer.setSeriesPaint(0, color);
        StandardXYBarPainter painter = new StandardXYBarPainter();
        renderer.setBarPainter(painter);

        ChartPanel chartPanel = new ChartPanel(chart);
        chartPanel.setPreferredSize(new Dimension(300, 200));
        //        chartPanel.getPopupMenu().add(createCopyDataToClipboardMenuItem());
        return chartPanel;
    }

    private AbstractButton getExportButton() {
        final AbstractButton export = ToolButtonFactory.createButton(UIUtils.loadImageIcon("icons/Export24.gif"),
                false);
        export.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JPopupMenu viewPopup = new JPopupMenu("Export");
                viewPopup.add(exportAsCsvAction);
                viewPopup.add(putStatisticsIntoVectorDataAction);
                final Rectangle buttonBounds = export.getBounds();
                viewPopup.show(export, 1, buttonBounds.height + 1);
            }
        });
        export.setEnabled(false);
        return export;
    }

    @Override
    public RasterDataNode getRasterDataNode() {
        return getRaster();
    }

    @Override
    public ProductNodeGroup<VectorDataNode> getVectorDataNodeGroup() {
        return getRasterDataNode().getProduct().getVectorDataGroup();
    }

    private class PopupHandler extends MouseAdapter {

        @Override
        public void mouseReleased(MouseEvent e) {
            if (e.getButton() == 2 || e.isPopupTrigger()) {
                final JPopupMenu menu = new JPopupMenu();
                menu.add(createCopyDataToClipboardMenuItem());
                menu.show(e.getComponent(), e.getX(), e.getY());
            }
        }
    }

    // The fields of this class are used by the binding framework
    @SuppressWarnings("UnusedDeclaration")
    static class AccuracyModel {

        private int accuracy = 3;
        private boolean useAutoAccuracy = true;
    }
}