/* * 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.Property; import com.bc.ceres.binding.PropertyContainer; import com.bc.ceres.binding.ValidationException; import com.bc.ceres.binding.ValueSet; 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.Band; import org.esa.snap.core.datamodel.Mask; 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.datamodel.Stx; import org.esa.snap.core.datamodel.StxFactory; import org.esa.snap.core.dataop.barithm.BandArithmetic; import org.esa.snap.core.util.Debug; import org.esa.snap.core.util.ProductUtils; import org.esa.snap.core.util.math.MathUtils; import org.esa.snap.rcp.SnapApp; import org.esa.snap.rcp.util.Dialogs; import org.esa.snap.ui.GridBagUtils; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.NumberAxis; import org.jfree.ui.RectangleInsets; import org.openide.windows.TopComponent; import javax.swing.DefaultListCellRenderer; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSeparator; import javax.swing.ListCellRenderer; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.Shape; import java.awt.event.ItemEvent; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.DataBufferByte; import java.awt.image.IndexColorModel; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; /** * The density plot pane within the statistics window. */ class DensityPlotPanel extends ChartPagePanel { private static final String NO_DATA_MESSAGE = "No scatter plot computed yet.\n" + "To create a scatter plot, select bands in both combo boxes.\n" + "The plot will be computed when you click the 'Refresh View' button.\n" + HELP_TIP_MESSAGE + "\n" + ZOOM_TIP_MESSAGE; private static final String CHART_TITLE = "Scatter Plot"; private final static String PROPERTY_NAME_AUTO_MIN_MAX = "autoMinMax"; private final static String PROPERTY_NAME_MIN = "min"; private final static String PROPERTY_NAME_MAX = "max"; private final static String PROPERTY_NAME_USE_ROI_MASK = "useRoiMask"; private final static String PROPERTY_NAME_ROI_MASK = "roiMask"; private final static String PROPERTY_NAME_X_PRODUCT = "xProduct"; private final static String PROPERTY_NAME_Y_PRODUCT = "yProduct"; private final static String PROPERTY_NAME_X_BAND = "xBand"; private final static String PROPERTY_NAME_Y_BAND = "yBand"; private static final int X_VAR = 0; private static final int Y_VAR = 1; private static final int NUM_DECIMALS = 2; private BindingContext bindingContext; private DataSourceConfig dataSourceConfig; private Property xProductProperty; private Property yProductProperty; private Property xBandProperty; private Property yBandProperty; private JComboBox<ListCellRenderer> xProductList; private JComboBox<ListCellRenderer> yProductList; private JComboBox<ListCellRenderer> xBandList; private JComboBox<ListCellRenderer> yBandList; //todo instead using referenceSize, use referenceSceneRasterTransform private Dimension referenceSize; private static AxisRangeControl[] axisRangeControls = new AxisRangeControl[2]; private IndexColorModel toggledColorModel; private IndexColorModel untoggledColorModel; private ChartPanel densityPlotDisplay; private XYImagePlot plot; private static final Color backgroundColor = new Color(255, 255, 255, 0); private boolean plotColorsInverted; private JCheckBox toggleColorCheckBox; DensityPlotPanel(TopComponent parentComponent, String helpId) { super(parentComponent, helpId, CHART_TITLE, true); } @Override protected void initComponents() { initParameters(); createUI(); initActionEnablers(); } private void initActionEnablers() { RefreshActionEnabler roiMaskActionEnabler = new RefreshActionEnabler(refreshButton, PROPERTY_NAME_USE_ROI_MASK, PROPERTY_NAME_ROI_MASK, PROPERTY_NAME_X_PRODUCT, PROPERTY_NAME_Y_PRODUCT, PROPERTY_NAME_X_BAND, PROPERTY_NAME_Y_BAND); bindingContext.addPropertyChangeListener(roiMaskActionEnabler); RefreshActionEnabler rangeControlActionEnabler = new RefreshActionEnabler(refreshButton, PROPERTY_NAME_MIN, PROPERTY_NAME_AUTO_MIN_MAX, PROPERTY_NAME_MAX); axisRangeControls[X_VAR].getBindingContext().addPropertyChangeListener(rangeControlActionEnabler); axisRangeControls[Y_VAR].getBindingContext().addPropertyChangeListener(rangeControlActionEnabler); } @Override public void nodeDataChanged(ProductNodeEvent event) { super.nodeDataChanged(event); if (!dataSourceConfig.useRoiMask) { return; } final Mask roiMask = dataSourceConfig.roiMask; if (roiMask == null) { return; } final ProductNode sourceNode = event.getSourceNode(); if (!(sourceNode instanceof Mask)) { return; } final String maskName = sourceNode.getName(); if (roiMask.getName().equals(maskName)) { updateComponents(); } } @Override protected void updateComponents() { super.updateComponents(); if (isRasterChanged() || isProductChanged()) { plot.setImage(null); plot.setDataset(null); if (isProductChanged()) { plot.getDomainAxis().setLabel("X"); plot.getRangeAxis().setLabel("Y"); } final ValueSet productValueSet = new ValueSet(createAvailableProductList()); xProductProperty.getDescriptor().setValueSet(productValueSet); yProductProperty.getDescriptor().setValueSet(productValueSet); if (productValueSet.getItems().length > 0) { Product currentProduct = getProduct(); try { xProductProperty.setValue(currentProduct); yProductProperty.setValue(currentProduct); } catch (ValidationException ignored) { Debug.trace(ignored); } } updateBandList(getProduct(), xBandProperty, false); updateBandList(getProduct(), yBandProperty, true); toggleColorCheckBox.setEnabled(false); } refreshButton.setEnabled(xBandProperty.getValue() != null && yBandProperty.getValue() != null); } private void updateBandList(final Product product, final Property bandProperty, boolean considerReferenceSize) { if (product == null) { return; } final ValueSet bandValueSet = new ValueSet(createAvailableBandList(product, considerReferenceSize)); bandProperty.getDescriptor().setValueSet(bandValueSet); if (bandValueSet.getItems().length > 0) { RasterDataNode currentRaster = getRaster(); if (bandValueSet.contains(getRaster())) { currentRaster = getRaster(); } try { bandProperty.setValue(currentRaster); } catch (ValidationException ignored) { Debug.trace(ignored); } } } private void initParameters() { axisRangeControls[X_VAR] = new AxisRangeControl("X-Axis"); axisRangeControls[Y_VAR] = new AxisRangeControl("Y-Axis"); initColorModels(); plotColorsInverted = false; dataSourceConfig = new DataSourceConfig(); bindingContext = new BindingContext(PropertyContainer.createObjectBacked(dataSourceConfig)); xProductList = new JComboBox<>(); xProductList.addItemListener(event -> { if (event.getStateChange() == ItemEvent.SELECTED) { final Product selectedProduct = (Product) event.getItem(); updateBandList(selectedProduct, xBandProperty, false); } }); xProductList.setRenderer(new ProductListCellRenderer()); bindingContext.bind(PROPERTY_NAME_X_PRODUCT, xProductList); xProductProperty = bindingContext.getPropertySet().getProperty(PROPERTY_NAME_X_PRODUCT); yProductList = new JComboBox<>(); yProductList.addItemListener(event -> { if (event.getStateChange() == ItemEvent.SELECTED) { final Product selectedProduct = (Product) event.getItem(); updateBandList(selectedProduct, yBandProperty, true); } }); yProductList.setRenderer(new ProductListCellRenderer()); bindingContext.bind(PROPERTY_NAME_Y_PRODUCT, yProductList); yProductProperty = bindingContext.getPropertySet().getProperty(PROPERTY_NAME_Y_PRODUCT); xBandList = new JComboBox<>(); xBandList.setRenderer(new BandListCellRenderer()); bindingContext.bind(PROPERTY_NAME_X_BAND, xBandList); xBandList.addActionListener(e -> { final Object value = xBandList.getSelectedItem(); if (value != null) { final Dimension rasterSize = ((RasterDataNode) value).getRasterSize(); if (rasterSize != referenceSize) { referenceSize = rasterSize; updateBandList(getProduct(), yBandProperty, true); } } }); xBandProperty = bindingContext.getPropertySet().getProperty(PROPERTY_NAME_X_BAND); yBandList = new JComboBox<>(); yBandList.setRenderer(new BandListCellRenderer()); bindingContext.bind(PROPERTY_NAME_Y_BAND, yBandList); yBandProperty = bindingContext.getPropertySet().getProperty(PROPERTY_NAME_Y_BAND); } private static String formatProductName(final Product product) { String name = product.getName().substring(0, Math.min(10, product.getName().length())); if (product.getName().length() > 10) { name += "..."; } return product.getProductRefString() + name; } private void initColorModels() { for (int j = 0; j <= 1; j++) { final int palSize = 256; final byte[] r = new byte[palSize]; final byte[] g = new byte[palSize]; final byte[] b = new byte[palSize]; final byte[] a = new byte[palSize]; r[0] = (byte) backgroundColor.getRed(); g[0] = (byte) backgroundColor.getGreen(); b[0] = (byte) backgroundColor.getBlue(); a[0] = (byte) backgroundColor.getAlpha(); for (int i = 1; i < 128; i++) { if (j == 0) { r[i] = (byte) (2 * i); g[i] = (byte) 0; } else { r[i] = (byte) 255; g[i] = (byte) (255 - (2 * (i - 128))); } b[i] = (byte) 0; a[i] = (byte) 255; } for (int i = 128; i < 256; i++) { if (j == 0) { r[i] = (byte) 255; g[i] = (byte) (2 * (i - 128)); } else { r[i] = (byte) (255 - (2 * i)); g[i] = (byte) 0; } b[i] = (byte) 0; a[i] = (byte) 255; } if (j == 0) { toggledColorModel = new IndexColorModel(8, palSize, r, g, b, a); } else { untoggledColorModel = new IndexColorModel(8, palSize, r, g, b, a); } } } private void createUI() { plot = new XYImagePlot(); plot.setAxisOffset(new RectangleInsets(5, 5, 5, 5)); NumberAxis domainAxis = (NumberAxis) plot.getDomainAxis(); NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis(); domainAxis.setAutoRangeIncludesZero(false); rangeAxis.setAutoRangeIncludesZero(false); domainAxis.setUpperMargin(0); domainAxis.setLowerMargin(0); rangeAxis.setUpperMargin(0); rangeAxis.setLowerMargin(0); plot.setNoDataMessage(NO_DATA_MESSAGE); plot.getRenderer().setBaseToolTipGenerator(new XYPlotToolTipGenerator()); JFreeChart chart = new JFreeChart(CHART_TITLE, plot); ChartFactory.getChartTheme().apply(chart); chart.removeLegend(); createUI(createChartPanel(chart), createOptionsPanel(), bindingContext); updateUIState(); } private void toggleColor() { BufferedImage image = plot.getImage(); if (image != null) { if (!plotColorsInverted) { image = new BufferedImage(untoggledColorModel, image.getRaster(), image.isAlphaPremultiplied(), null); } else { image = new BufferedImage(toggledColorModel, image.getRaster(), image.isAlphaPremultiplied(), null); } plot.setImage(image); densityPlotDisplay.getChart().setNotify(true); plotColorsInverted = !plotColorsInverted; } } private JPanel createOptionsPanel() { toggleColorCheckBox = new JCheckBox("Invert plot colors"); toggleColorCheckBox.addActionListener(e -> toggleColor()); toggleColorCheckBox.setEnabled(false); final JPanel optionsPanel = GridBagUtils.createPanel(); final GridBagConstraints gbc = GridBagUtils.createConstraints("anchor=NORTHWEST,fill=HORIZONTAL,insets.top=0,weightx=1,gridx=0"); GridBagUtils.addToPanel(optionsPanel, axisRangeControls[X_VAR].getPanel(), gbc, "gridy=0, insets.top=2"); GridBagUtils.addToPanel(optionsPanel, xProductList, gbc, "gridy=1,insets.left=4,insets.right=2"); GridBagUtils.addToPanel(optionsPanel, xBandList, gbc, "gridy=2,insets.left=4,insets.right=2"); GridBagUtils.addToPanel(optionsPanel, axisRangeControls[Y_VAR].getPanel(), gbc, "gridy=3,insets.left=0,insets.right=0"); GridBagUtils.addToPanel(optionsPanel, yProductList, gbc, "gridy=4,insets.left=4,insets.right=2"); GridBagUtils.addToPanel(optionsPanel, yBandList, gbc, "gridy=5,insets.left=4,insets.right=2"); GridBagUtils.addToPanel(optionsPanel, new JPanel(), gbc, "gridy=6"); GridBagUtils.addToPanel(optionsPanel, new JSeparator(), gbc, "gridy=7,insets.left=4,insets.right=2"); GridBagUtils.addToPanel(optionsPanel, toggleColorCheckBox, gbc, "gridy=8,insets.left=0,insets.right=0"); return optionsPanel; } private ChartPanel createChartPanel(JFreeChart chart) { densityPlotDisplay = new ChartPanel(chart); MaskSelectionToolSupport maskSelectionToolSupport = new MaskSelectionToolSupport(this, densityPlotDisplay, "scatter_plot_area", "Mask generated from selected scatter plot area", Color.RED, PlotAreaSelectionTool.AreaType.ELLIPSE) { @Override protected String createMaskExpression(PlotAreaSelectionTool.AreaType areaType, Shape shape) { Rectangle2D bounds = shape.getBounds2D(); return createMaskExpression(bounds.getCenterX(), bounds.getCenterY(), 0.5 * bounds.getWidth(), 0.5 * bounds.getHeight()); } protected String createMaskExpression(double x0, double y0, double dx, double dy) { return String.format("sqrt(sq((%s - %s)/%s) + sq((%s - %s)/%s)) < 1.0", BandArithmetic.createExternalName(dataSourceConfig.xBand.getName()), x0, dx, BandArithmetic.createExternalName(dataSourceConfig.yBand.getName()), y0, dy); } }; densityPlotDisplay.getPopupMenu().addSeparator(); densityPlotDisplay.getPopupMenu().add(maskSelectionToolSupport.createMaskSelectionModeMenuItem()); densityPlotDisplay.getPopupMenu().add(maskSelectionToolSupport.createDeleteMaskMenuItem()); densityPlotDisplay.getPopupMenu().addSeparator(); densityPlotDisplay.getPopupMenu().add(createCopyDataToClipboardMenuItem()); return densityPlotDisplay; } private RasterDataNode getRaster(int varIndex) { final Product product = getProduct(); if (product == null) { return null; } final RasterDataNode raster; if (varIndex == X_VAR) { raster = dataSourceConfig.xBand; } else { raster = dataSourceConfig.yBand; } Debug.assertTrue(raster != null); return raster; } private void updateUIState() { super.updateComponents(); } private static void checkBandsForRange() throws IllegalArgumentException { if (axisRangeControls[X_VAR].getMin().equals(axisRangeControls[X_VAR].getMax()) && axisRangeControls[Y_VAR].getMin().equals(axisRangeControls[Y_VAR].getMax())) { throw new IllegalArgumentException("Value range of at least one band must be larger than one"); } } @Override protected void updateChartData() { final RasterDataNode rasterX = getRaster(X_VAR); final RasterDataNode rasterY = getRaster(Y_VAR); if (rasterX == null || rasterY == null) { return; } ProgressMonitorSwingWorker<BufferedImage, Object> swingWorker = new ProgressMonitorSwingWorker<BufferedImage, Object>( this, "Computing scatter plot") { @Override protected BufferedImage doInBackground(ProgressMonitor pm) throws Exception { pm.beginTask("Computing scatter plot...", 100); try { checkBandsForRange(); setRange(X_VAR, rasterX, dataSourceConfig.useRoiMask ? dataSourceConfig.roiMask : null, SubProgressMonitor.create(pm, 15)); setRange(Y_VAR, rasterY, dataSourceConfig.useRoiMask ? dataSourceConfig.roiMask : null, SubProgressMonitor.create(pm, 15)); final BufferedImage densityPlotImage = ProductUtils.createDensityPlotImage(rasterX, axisRangeControls[X_VAR].getMin().floatValue(), axisRangeControls[X_VAR].getMax().floatValue(), rasterY, axisRangeControls[Y_VAR].getMin().floatValue(), axisRangeControls[Y_VAR].getMax().floatValue(), dataSourceConfig.useRoiMask ? dataSourceConfig.roiMask : null, 512, 512, backgroundColor, null, SubProgressMonitor.create(pm, 70)); toggleColorCheckBox.setSelected(false); plotColorsInverted = false; return densityPlotImage; } finally { pm.done(); } } @Override public void done() { try { checkBandsForRange(); final BufferedImage densityPlotImage = get(); double minX = axisRangeControls[X_VAR].getMin(); double maxX = axisRangeControls[X_VAR].getMax(); double minY = axisRangeControls[Y_VAR].getMin(); double maxY = axisRangeControls[Y_VAR].getMax(); if (minX > maxX || minY > maxY) { Dialogs.showMessage(/*I18N*/ CHART_TITLE, /*I18N*/ "Failed to compute scatter plot.\n" + "No Pixels considered..", JOptionPane.ERROR_MESSAGE, null ); plot.setDataset(null); return; } if (MathUtils.equalValues(minX, maxX, 1.0e-4)) { minX = Math.floor(minX); maxX = Math.ceil(maxX); } if (MathUtils.equalValues(minY, maxY, 1.0e-4)) { minY = Math.floor(minY); maxY = Math.ceil(maxY); } plot.setImage(densityPlotImage); plot.setImageDataBounds(new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY)); axisRangeControls[X_VAR].adjustComponents(minX, maxX, NUM_DECIMALS); axisRangeControls[Y_VAR].adjustComponents(minY, maxY, NUM_DECIMALS); plot.getDomainAxis().setLabel(StatisticChartStyling.getAxisLabel(getRaster(X_VAR), "X", false)); plot.getRangeAxis().setLabel(StatisticChartStyling.getAxisLabel(getRaster(Y_VAR), "Y", false)); toggleColorCheckBox.setEnabled(true); } catch (InterruptedException | CancellationException e) { e.printStackTrace(); Dialogs.showMessage(CHART_TITLE, "Failed to compute scatter plot.\n" + "Calculation canceled.", JOptionPane.ERROR_MESSAGE, null ); } catch (ExecutionException | IllegalArgumentException e) { e.printStackTrace(); Dialogs.showMessage(CHART_TITLE, "Failed to compute scatter plot.\n" + "An error occurred:\n" + e.getCause().getMessage(), JOptionPane.ERROR_MESSAGE, null ); } } }; swingWorker.execute(); } private static void setRange(int varIndex, RasterDataNode raster, Mask mask, ProgressMonitor pm) throws IOException { final AxisRangeControl axisRangeControl = axisRangeControls[varIndex]; if (axisRangeControl.isAutoMinMax()) { Stx stx; if (mask == null) { stx = raster.getStx(false, pm); } else { stx = new StxFactory().withRoiMask(mask).create(raster, pm); } axisRangeControl.adjustComponents(stx.getMinimum(), stx.getMaximum(), NUM_DECIMALS); } } private static Product[] createAvailableProductList() { return SnapApp.getDefault().getProductManager().getProducts(); } private RasterDataNode[] createAvailableBandList(final Product product, boolean considerReferenceSize) { final List<RasterDataNode> availableBandList = new ArrayList<>(17); if (product != null) { for (int i = 0; i < product.getNumBands(); i++) { final Band band = product.getBandAt(i); if (!considerReferenceSize || band.getRasterSize().equals(referenceSize)) { availableBandList.add(band); } } if (!considerReferenceSize || product.getSceneRasterSize().equals(referenceSize)) { for (int i = 0; i < product.getNumTiePointGrids(); i++) { availableBandList.add(product.getTiePointGridAt(i)); } } } // if raster is only bound to the product and does not belong to it final RasterDataNode raster = getRaster(); if (raster != null && raster.getProduct() == product && (!considerReferenceSize || raster.getRasterSize().equals(raster.getProduct().getSceneRasterSize()))) { if (!availableBandList.contains(raster)) { availableBandList.add(raster); } } return availableBandList.toArray(new RasterDataNode[availableBandList.size()]); } @Override protected boolean checkDataToClipboardCopy() { final int warnLimit = 2000; final int excelLimit = 65536; final int numNonEmptyBins = getNumNonEmptyBins(); if (numNonEmptyBins > warnLimit) { String excelNote = ""; if (numNonEmptyBins > excelLimit - 100) { excelNote = "Note that e.g., Microsoft® Excel 2002 only supports a total of " + excelLimit + " rows in a sheet.\n"; /*I18N*/ } final String message = MessageFormat.format( "This scatter plot contains {0} non-empty bins.\n" + "For each bin, a text data row containing an x, y and z value will be created.\n" + "{1}\nPress ''Yes'' if you really want to copy this amount of data to the system clipboard.\n", numNonEmptyBins, excelNote); final int status = JOptionPane.showConfirmDialog(this, message, /*I18N*/ "Copy Data to Clipboard", /*I18N*/ JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (status != JOptionPane.YES_OPTION) { return false; } } return true; } private static byte[] getValidData(BufferedImage image) { if (image != null && image.getColorModel() instanceof IndexColorModel && image.getData().getDataBuffer() instanceof DataBufferByte) { return ((DataBufferByte) image.getData().getDataBuffer()).getData(); } return null; } protected int getNumNonEmptyBins() { final byte[] data = getValidData(plot.getImage()); int n = 0; if (data != null) { int b; for (byte aData : data) { b = aData & 0xff; if (b != 0) { n++; } } } return n; } @Override protected String getDataAsText() { final BufferedImage image = plot.getImage(); final Rectangle2D bounds = plot.getImageDataBounds(); final byte[] data = getValidData(image); if (data == null) { return null; } final StringBuilder sb = new StringBuilder(64000); final int w = image.getWidth(); final int h = image.getHeight(); final RasterDataNode rasterX = getRaster(X_VAR); assert rasterX != null; final String nameX = rasterX.getName(); final double sampleMinX = bounds.getMinX(); final double sampleMaxX = bounds.getMaxX(); final RasterDataNode rasterY = getRaster(Y_VAR); assert rasterY != null; final String nameY = rasterY.getName(); final double sampleMinY = bounds.getMinY(); final double sampleMaxY = bounds.getMaxY(); sb.append("Product name:\t").append(rasterX.getProduct().getName()).append("\n"); sb.append("Dataset X name:\t").append(nameX).append("\n"); sb.append("Dataset Y name:\t").append(nameY).append("\n"); sb.append('\n'); sb.append(nameX).append(" minimum:\t").append(sampleMinX).append("\t").append(rasterX.getUnit()).append( "\n"); sb.append(nameX).append(" maximum:\t").append(sampleMaxX).append("\t").append(rasterX.getUnit()).append( "\n"); sb.append(nameX).append(" bin size:\t").append((sampleMaxX - sampleMinX) / w).append("\t").append( rasterX.getUnit()).append("\n"); sb.append(nameX).append(" #bins:\t").append(w).append("\n"); sb.append('\n'); sb.append(nameY).append(" minimum:\t").append(sampleMinY).append("\t").append(rasterY.getUnit()).append( "\n"); sb.append(nameY).append(" maximum:\t").append(sampleMaxY).append("\t").append(rasterY.getUnit()).append( "\n"); sb.append(nameY).append(" bin size:\t").append((sampleMaxY - sampleMinY) / h).append("\t").append( rasterY.getUnit()).append("\n"); sb.append(nameY).append(" #bins:\t").append(h).append("\n"); sb.append('\n'); sb.append(nameX); sb.append('\t'); sb.append(nameY); sb.append('\t'); sb.append("Bin counts\t(cropped at 255)"); sb.append('\n'); int x, y, z; double v1, v2; for (int i = 0; i < data.length; i++) { z = data[i] & 0xff; if (z != 0) { x = i % w; y = h - i / w - 1; v1 = sampleMinX + ((x + 0.5) * (sampleMaxX - sampleMinX)) / w; v2 = sampleMinY + ((y + 0.5) * (sampleMaxY - sampleMinY)) / h; sb.append(v1); sb.append('\t'); sb.append(v2); sb.append('\t'); sb.append(z); sb.append('\n'); } } return sb.toString(); } @SuppressWarnings("unused") private static class DataSourceConfig { public boolean useRoiMask; public Mask roiMask; private Product xProduct; private Product yProduct; private RasterDataNode xBand; private RasterDataNode yBand; private Property xProductProperty; private Property yProductProperty; private Property xBandProperty; private Property yBandProperty; } private static class BandListCellRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (value != null) { this.setText(((RasterDataNode) value).getName()); } return this; } } private static class ProductListCellRenderer extends DefaultListCellRenderer { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (value != null) { this.setText(formatProductName((Product) value)); } return this; } } }