org.locationtech.udig.processingtoolbox.tools.BubbleChartDialog.java Source code

Java tutorial

Introduction

Here is the source code for org.locationtech.udig.processingtoolbox.tools.BubbleChartDialog.java

Source

/*
 * uDig - User Friendly Desktop Internet GIS client
 * (C) MangoSystem - www.mangosystem.com 
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
 * License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
 */
package org.locationtech.udig.processingtoolbox.tools;

import java.awt.Font;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.process.spatialstatistics.StatisticsFeaturesProcess;
import org.geotools.process.spatialstatistics.operations.DataStatisticsOperation.DataStatisticsResult;
import org.geotools.util.logging.Logging;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.EntityCollection;
import org.jfree.chart.entity.XYItemEntity;
import org.jfree.chart.labels.BubbleXYItemLabelGenerator;
import org.jfree.chart.labels.ItemLabelAnchor;
import org.jfree.chart.labels.ItemLabelPosition;
import org.jfree.chart.labels.StandardXYZToolTipGenerator;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.SeriesRenderingOrder;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYBubbleRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.data.xy.XYZDataset;
import org.jfree.experimental.chart.swt.ChartComposite;
import org.jfree.ui.RectangleInsets;
import org.jfree.ui.TextAnchor;
import org.locationtech.udig.catalog.util.GeoToolsAdapters;
import org.locationtech.udig.processingtoolbox.ToolboxPlugin;
import org.locationtech.udig.processingtoolbox.internal.Messages;
import org.locationtech.udig.processingtoolbox.styler.MapUtils;
import org.locationtech.udig.processingtoolbox.styler.MapUtils.FieldType;
import org.locationtech.udig.processingtoolbox.styler.MapUtils.VectorLayerType;
import org.locationtech.udig.project.ILayer;
import org.locationtech.udig.project.IMap;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.identity.FeatureId;
import org.opengis.util.ProgressListener;

/**
 * Bubble Chart Dialog
 * 
 * @author Minpa Lee, MangoSystem
 * 
 * @source $URL$
 */
public class BubbleChartDialog extends AbstractGeoProcessingDialog implements IRunnableWithProgress {
    protected static final Logger LOGGER = Logging.getLogger(BubbleChartDialog.class);

    protected final FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);

    private ChartComposite3 chartComposite;

    private ILayer inputLayer;

    private Combo cboLayer, cboXField, cboYField, cboSize;

    private Button chkStatistics;

    private Browser browser;

    private CTabItem inputTab, plotTab, outputTab;

    private XYMinMaxVisitor minMaxVisitor = new XYMinMaxVisitor();

    public BubbleChartDialog(Shell parentShell, IMap map) {
        super(parentShell, map);

        setShellStyle(SWT.CLOSE | SWT.MAX | SWT.TITLE | SWT.BORDER | SWT.MODELESS | SWT.RESIZE);

        this.windowTitle = Messages.BubbleChartDialog_title;
        this.windowDesc = Messages.BubbleChartDialog_description;
        this.windowSize = new Point(650, 450);
    }

    @Override
    protected Control createDialogArea(final Composite parent) {
        Composite area = (Composite) super.createDialogArea(parent);

        // 0. Tab Folder
        final CTabFolder parentTabFolder = new CTabFolder(area, SWT.BOTTOM);
        parentTabFolder.setUnselectedCloseVisible(false);
        parentTabFolder.setLayout(new FillLayout());
        parentTabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        // 1. Input Tab
        createInputTab(parentTabFolder);

        parentTabFolder.setSelection(inputTab);
        parentTabFolder.pack();
        area.pack(true);
        return area;
    }

    private void createInputTab(final CTabFolder parentTabFolder) {
        inputTab = new CTabItem(parentTabFolder, SWT.NONE);
        inputTab.setText(Messages.ProcessExecutionDialog_tabparameters);

        ScrolledComposite scroller = new ScrolledComposite(parentTabFolder, SWT.NONE | SWT.V_SCROLL | SWT.H_SCROLL);
        scroller.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        Composite container = new Composite(scroller, SWT.NONE);
        container.setLayout(new GridLayout(1, false));
        container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        // local moran's i
        Image image = ToolboxPlugin.getImageDescriptor("icons/public_co.gif").createImage(); //$NON-NLS-1$
        uiBuilder.createLabel(container, Messages.ScatterPlotDialog_InputLayer, EMPTY, image, 1);
        cboLayer = uiBuilder.createCombo(container, 1, true);
        fillLayers(map, cboLayer, VectorLayerType.ALL);

        uiBuilder.createLabel(container, Messages.BubbleChartDialog_XField, EMPTY, image, 1);
        cboXField = uiBuilder.createCombo(container, 1, true);

        uiBuilder.createLabel(container, Messages.BubbleChartDialog_YField, EMPTY, image, 1);
        cboYField = uiBuilder.createCombo(container, 1, true);

        uiBuilder.createLabel(container, Messages.BubbleChartDialog_SizeField, EMPTY, image, 1);
        cboSize = uiBuilder.createCombo(container, 1, true);

        uiBuilder.createLabel(container, null, null, 1);
        chkStatistics = uiBuilder.createCheckbox(container, Messages.ScatterPlotDialog_BasicStatistics, null, 1);

        // register events
        cboLayer.addModifyListener(new ModifyListener() {
            @Override
            public void modifyText(ModifyEvent e) {
                inputLayer = MapUtils.getLayer(map, cboLayer.getText());
                if (inputLayer != null) {
                    fillFields(cboXField, inputLayer.getSchema(), FieldType.Number);
                    fillFields(cboYField, inputLayer.getSchema(), FieldType.Number);
                    fillFields(cboSize, inputLayer.getSchema(), FieldType.Number);
                }
            }
        });

        // finally
        scroller.setContent(container);
        inputTab.setControl(scroller);

        scroller.setMinSize(450, container.getSize().y - 2);
        scroller.setExpandVertical(true);
        scroller.setExpandHorizontal(true);

        scroller.pack();
        container.pack();
    }

    private void createOutputTab(final CTabFolder parentTabFolder) {
        outputTab = new CTabItem(parentTabFolder, SWT.NONE);
        outputTab.setText(Messages.ScatterPlotDialog_Summary);

        try {
            browser = new Browser(parentTabFolder, SWT.NONE);
            GridData layoutData = new GridData(GridData.FILL_BOTH);
            browser.setLayoutData(layoutData);
            outputTab.setControl(browser);
        } catch (Exception e) {
            LOGGER.log(Level.WARNING, e.getMessage(), e);
        }
    }

    private void createGraphTab(final CTabFolder parentTabFolder) {
        plotTab = new CTabItem(parentTabFolder, SWT.NONE);
        plotTab.setText(Messages.ScatterPlotDialog_Graph);

        XYPlot plot = new XYPlot();
        plot.setOrientation(PlotOrientation.VERTICAL);
        plot.setBackgroundPaint(java.awt.Color.WHITE);
        plot.setDomainPannable(false);
        plot.setRangePannable(false);
        plot.setSeriesRenderingOrder(SeriesRenderingOrder.FORWARD);

        JFreeChart chart = new JFreeChart(EMPTY, JFreeChart.DEFAULT_TITLE_FONT, plot, false);
        chart.setBackgroundPaint(java.awt.Color.WHITE);
        chart.setBorderVisible(false);

        chartComposite = new ChartComposite3(parentTabFolder, SWT.NONE | SWT.EMBEDDED, chart, true);
        chartComposite.setLayout(new FillLayout());
        chartComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        chartComposite.setDomainZoomable(false);
        chartComposite.setRangeZoomable(false);
        chartComposite.setMap(map);
        chartComposite.addChartMouseListener(new PlotMouseListener());

        plotTab.setControl(chartComposite);

        chartComposite.pack();
    }

    class PlotMouseListener implements ChartMouseListener {
        @Override
        public void chartMouseMoved(ChartMouseEvent event) {
        }

        @Override
        public void chartMouseClicked(ChartMouseEvent event) {
            DefaultXYZDataset2 ds = (DefaultXYZDataset2) chartComposite.getChart().getXYPlot().getDataset(2);
            ChartEntity entity = event.getEntity();
            if (entity != null && (entity instanceof XYItemEntity)) {
                XYItemEntity item = (XYItemEntity) entity;
                if (item.getSeriesIndex() == 0) {
                    DefaultXYZDataset2 dataSet = (DefaultXYZDataset2) item.getDataset();
                    String featureID = dataSet.getFeatrureID(0, item.getItem());
                    Filter selectionFilter = ff.id(ff.featureId(featureID));
                    map.select(selectionFilter, inputLayer);

                    ds.addSeries(EMPTY,
                            new double[][] { new double[] { dataSet.getXValue(0, item.getItem()) },
                                    new double[] { dataSet.getYValue(0, item.getItem()) },
                                    new double[] { dataSet.getZValue(0, item.getItem()) } });
                    ds.addFeatrureIDS(EMPTY, new String[] { featureID });
                } else {
                    map.select(Filter.EXCLUDE, inputLayer);
                    ds.removeSeries(EMPTY);
                }
            } else {
                map.select(Filter.EXCLUDE, inputLayer);
                ds.removeSeries(EMPTY);
            }
        }
    }

    private void updateChart(SimpleFeatureCollection features, String xField, String yField, String sizeField) {
        // 1. Create a single plot containing both the scatter and line
        XYPlot plot = new XYPlot();
        plot.setOrientation(PlotOrientation.VERTICAL);
        plot.setBackgroundPaint(java.awt.Color.WHITE);
        plot.setDomainPannable(false);
        plot.setRangePannable(false);
        plot.setSeriesRenderingOrder(SeriesRenderingOrder.FORWARD);

        plot.setDomainCrosshairVisible(false);
        plot.setRangeCrosshairVisible(false);
        plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0));
        plot.setForegroundAlpha(0.75f);

        // 2. Setup Scatter plot
        // Create the bubble chart data, renderer, and axis
        int fontStyle = java.awt.Font.BOLD;
        FontData fontData = getShell().getDisplay().getSystemFont().getFontData()[0];
        NumberAxis xPlotAxis = new NumberAxis(xField); // Independent variable
        xPlotAxis.setLabelFont(new Font(fontData.getName(), fontStyle, 12));
        xPlotAxis.setTickLabelFont(new Font(fontData.getName(), fontStyle, 10));

        NumberAxis yPlotAxis = new NumberAxis(yField); // Dependent variable
        yPlotAxis.setLabelFont(new Font(fontData.getName(), fontStyle, 12));
        yPlotAxis.setTickLabelFont(new Font(fontData.getName(), fontStyle, 10));

        XYItemRenderer plotRenderer = new XYBubbleRenderer(XYBubbleRenderer.SCALE_ON_RANGE_AXIS);
        plotRenderer.setSeriesPaint(0, java.awt.Color.ORANGE); // dot
        plotRenderer.setBaseItemLabelGenerator(new BubbleXYItemLabelGenerator());
        plotRenderer.setBaseToolTipGenerator(new StandardXYZToolTipGenerator());
        plotRenderer
                .setBasePositiveItemLabelPosition(new ItemLabelPosition(ItemLabelAnchor.CENTER, TextAnchor.CENTER));

        // Set the bubble chart data, renderer, and axis into plot
        plot.setDataset(0, getBubbleChartData(features, xField, yField, sizeField));

        xPlotAxis.setAutoRangeIncludesZero(false);
        xPlotAxis.setAutoRange(false);
        double differUpper = minMaxVisitor.getMaxX() - minMaxVisitor.getAverageX();
        double differLower = minMaxVisitor.getAverageX() - minMaxVisitor.getMinX();
        double gap = Math.abs(differUpper - differLower);
        if (differUpper > differLower) {
            xPlotAxis.setRange(minMaxVisitor.getMinX() - gap, minMaxVisitor.getMaxX());
        } else {
            xPlotAxis.setRange(minMaxVisitor.getMinX(), minMaxVisitor.getMaxX() + gap);
        }

        yPlotAxis.setAutoRangeIncludesZero(false);
        yPlotAxis.setAutoRange(false);
        differUpper = minMaxVisitor.getMaxY() - minMaxVisitor.getAverageY();
        differLower = minMaxVisitor.getAverageY() - minMaxVisitor.getMinY();
        gap = Math.abs(differUpper - differLower);
        if (differUpper > differLower) {
            yPlotAxis.setRange(minMaxVisitor.getMinY() - gap, minMaxVisitor.getMaxY());
        } else {
            yPlotAxis.setRange(minMaxVisitor.getMinY(), minMaxVisitor.getMaxY() + gap);
        }

        plot.setRenderer(0, plotRenderer);
        plot.setDomainAxis(0, xPlotAxis);
        plot.setRangeAxis(0, yPlotAxis);

        // Map the scatter to the first Domain and first Range
        plot.mapDatasetToDomainAxis(0, 0);
        plot.mapDatasetToRangeAxis(0, 0);

        // 3. Setup line
        // Create the line data, renderer, and axis
        XYItemRenderer lineRenderer = new XYLineAndShapeRenderer(true, false); // Lines only
        lineRenderer.setSeriesPaint(0, java.awt.Color.GRAY);
        lineRenderer.setSeriesPaint(1, java.awt.Color.GRAY);
        lineRenderer.setSeriesPaint(2, java.awt.Color.GRAY);

        // Set the line data, renderer, and axis into plot
        NumberAxis xLineAxis = new NumberAxis(EMPTY);
        xLineAxis.setTickMarksVisible(false);
        xLineAxis.setTickLabelsVisible(false);

        NumberAxis yLineAxis = new NumberAxis(EMPTY);
        yLineAxis.setTickMarksVisible(false);
        yLineAxis.setTickLabelsVisible(false);

        XYSeriesCollection lineDataset = new XYSeriesCollection();

        // AverageY
        XYSeries horizontal = new XYSeries("AverageY"); //$NON-NLS-1$
        horizontal.add(xPlotAxis.getRange().getLowerBound(), minMaxVisitor.getAverageY());
        horizontal.add(xPlotAxis.getRange().getUpperBound(), minMaxVisitor.getAverageY());
        lineDataset.addSeries(horizontal);

        // AverageX
        XYSeries vertical = new XYSeries("AverageX"); //$NON-NLS-1$
        vertical.add(minMaxVisitor.getAverageX(), yPlotAxis.getRange().getLowerBound());
        vertical.add(minMaxVisitor.getAverageX(), yPlotAxis.getRange().getUpperBound());
        lineDataset.addSeries(vertical);

        plot.setDataset(1, lineDataset);
        plot.setRenderer(1, lineRenderer);
        plot.setDomainAxis(1, xLineAxis);
        plot.setRangeAxis(1, yLineAxis);

        // Map the line to the second Domain and second Range
        plot.mapDatasetToDomainAxis(1, 0);
        plot.mapDatasetToRangeAxis(1, 0);

        // 4. Setup Selection
        NumberAxis xSelectionAxis = new NumberAxis(EMPTY);
        xSelectionAxis.setTickMarksVisible(false);
        xSelectionAxis.setTickLabelsVisible(false);

        NumberAxis ySelectionAxis = new NumberAxis(EMPTY);
        ySelectionAxis.setTickMarksVisible(false);
        ySelectionAxis.setTickLabelsVisible(false);

        XYItemRenderer selectionRenderer = new XYBubbleRenderer(XYBubbleRenderer.SCALE_ON_RANGE_AXIS);
        selectionRenderer.setSeriesPaint(0, java.awt.Color.RED); // dot
        selectionRenderer.setSeriesOutlinePaint(0, java.awt.Color.RED);

        plot.setDataset(2, new DefaultXYZDataset2());
        plot.setRenderer(2, selectionRenderer);
        plot.setDomainAxis(2, xSelectionAxis);
        plot.setRangeAxis(2, ySelectionAxis);

        // Map the scatter to the second Domain and second Range
        plot.mapDatasetToDomainAxis(2, 0);
        plot.mapDatasetToRangeAxis(2, 0);

        // 5. Finally, Create the chart with the plot and a legend
        java.awt.Font titleFont = new Font(fontData.getName(), fontStyle, 20);
        JFreeChart chart = new JFreeChart(EMPTY, titleFont, plot, false);
        chart.setBackgroundPaint(java.awt.Color.WHITE);
        chart.setBorderVisible(false);

        chartComposite.setChart(chart);
        chartComposite.forceRedraw();
    }

    private XYZDataset getBubbleChartData(SimpleFeatureCollection features, String xField, String yField,
            String sizeField) {
        DefaultXYZDataset2 xyzDataset = new DefaultXYZDataset2();

        // 1. prepare bubble size
        minMaxVisitor.visit(features, xField, yField, sizeField);

        final double minVal = minMaxVisitor.getMinZ();
        final double maxVal = minMaxVisitor.getMaxZ();
        final double diffVal = maxVal - minVal;
        final double scale = Math.min(minMaxVisitor.getMaxX(), minMaxVisitor.getMaxY()) / 8d;

        // 2. calculate x, y, z values
        final int featureCount = features.size();
        double[] xAxis = new double[featureCount];
        double[] yAxis = new double[featureCount];
        double[] zAxis = new double[featureCount];
        String[] featureIDS = new String[featureCount];

        Expression xExpression = ff.property(xField);
        Expression yExpression = ff.property(yField);
        Expression sizeExpression = ff.property(sizeField);

        int index = 0;
        SimpleFeatureIterator featureIter = features.features();
        try {
            while (featureIter.hasNext()) {
                SimpleFeature feature = featureIter.next();
                featureIDS[index] = feature.getID();

                Double xVal = xExpression.evaluate(feature, Double.class);
                if (xVal == null || xVal.isNaN() || xVal.isInfinite()) {
                    continue;
                }

                Double yVal = yExpression.evaluate(feature, Double.class);
                if (yVal == null || yVal.isNaN() || yVal.isInfinite()) {
                    continue;
                }

                Double sizeVal = sizeExpression.evaluate(feature, Double.class);
                if (sizeVal == null || sizeVal.isNaN() || sizeVal.isInfinite()) {
                    continue;
                }

                xAxis[index] = xVal;
                yAxis[index] = yVal;

                double transformed = 0;
                if (diffVal != 0) {
                    transformed = (sizeVal - minVal) / diffVal;
                }

                zAxis[index] = transformed * scale;
                index++;
            }
        } finally {
            featureIter.close();
        }

        xyzDataset.addSeries(EMPTY, new double[][] { xAxis, yAxis, zAxis });
        xyzDataset.addFeatrureIDS(EMPTY, featureIDS);

        return xyzDataset;
    }

    @Override
    protected void okPressed() {
        if (invalidWidgetValue(cboLayer, cboXField, cboYField, cboSize)) {
            openInformation(getShell(), Messages.Task_ParameterRequired);
            return;
        }

        if (inputLayer.getFilter() != Filter.EXCLUDE) {
            map.select(Filter.EXCLUDE, inputLayer);
        }

        try {
            PlatformUI.getWorkbench().getProgressService().run(false, true, this);
        } catch (InvocationTargetException e) {
            MessageDialog.openError(getShell(), Messages.General_Error, e.getMessage());
        } catch (InterruptedException e) {
            MessageDialog.openInformation(getShell(), Messages.General_Cancelled, e.getMessage());
        }
    }

    @SuppressWarnings("nls")
    @Override
    public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
        monitor.beginTask(String.format(Messages.Task_Executing, windowTitle), 100);
        try {
            if (plotTab == null) {
                monitor.subTask("Preparing bubble chart...");
                createGraphTab(inputTab.getParent());
            }

            monitor.worked(increment);

            String xField = cboXField.getText();
            String yField = cboYField.getText();
            String sizeField = cboSize.getText();
            SimpleFeatureCollection features = MapUtils.getFeatures(inputLayer);

            String fields = xField + "," + yField + "," + sizeField;

            if (chkStatistics.getSelection()) {
                if (outputTab == null) {
                    createOutputTab(inputTab.getParent());
                }

                ProgressListener subMonitor = GeoToolsAdapters
                        .progress(SubMonitor.convert(monitor, Messages.Task_Internal, 20));
                DataStatisticsResult statistics = StatisticsFeaturesProcess.process(features, fields, subMonitor);
                HtmlWriter writer = new HtmlWriter(inputLayer.getName());
                writer.writeDataStatistics(statistics);
                browser.setText(writer.getHTML());
            }

            monitor.subTask("Updating bubble chart...");
            chartComposite.setLayer(inputLayer);
            updateChart(features, xField, yField, sizeField);
            plotTab.getParent().setSelection(plotTab);
            monitor.worked(increment);
        } catch (Exception e) {
            ToolboxPlugin.log(e.getMessage());
            throw new InvocationTargetException(e.getCause(), e.getMessage());
        } finally {
            ToolboxPlugin.log(String.format(Messages.Task_Completed, windowTitle));
            monitor.done();
        }
    }

    class ChartComposite3 extends ChartComposite {

        private final FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);

        private org.locationtech.udig.project.internal.Map map;

        private ILayer layer;

        public ChartComposite3(Composite comp, int style, JFreeChart chart, boolean useBuffer) {
            super(comp, style, chart, useBuffer);
        }

        public org.locationtech.udig.project.internal.Map getMap() {
            return map;
        }

        public void setMap(org.locationtech.udig.project.internal.Map map) {
            this.map = map;
        }

        public ILayer getLayer() {
            return layer;
        }

        public void setLayer(ILayer layer) {
            this.layer = layer;
        }

        @SuppressWarnings("rawtypes")
        @Override
        public void zoom(Rectangle selection) {
            if (map == null || layer == null) {
                return;
            }

            DefaultXYZDataset2 ds = (DefaultXYZDataset2) getChart().getXYPlot().getDataset(2);
            List<XYZItem> itemList = new ArrayList<XYZItem>();
            try {
                EntityCollection entities = this.getChartRenderingInfo().getEntityCollection();
                Iterator iter = entities.iterator();
                while (iter.hasNext()) {
                    ChartEntity entity = (ChartEntity) iter.next();
                    if (entity instanceof XYItemEntity) {
                        XYItemEntity item = (XYItemEntity) entity;
                        if (item.getSeriesIndex() != 0 || !(item.getDataset() instanceof DefaultXYZDataset2)) {
                            continue;
                        }

                        java.awt.Rectangle bound = item.getArea().getBounds();
                        if (selection.intersects(bound.x, bound.y, bound.width, bound.height)) {
                            DefaultXYZDataset2 dataSet = (DefaultXYZDataset2) item.getDataset();
                            String featureID = dataSet.getFeatrureID(0, item.getItem());
                            itemList.add(new XYZItem(featureID, dataSet.getXValue(0, item.getItem()),
                                    dataSet.getYValue(0, item.getItem()), dataSet.getZValue(0, item.getItem())));
                        }
                    }
                }
            } catch (Exception e) {
                // skip
            } finally {
                if (itemList.size() > 0) {
                    Set<FeatureId> selected = new HashSet<FeatureId>();
                    double[] xAxis = new double[itemList.size()];
                    double[] yAxis = new double[itemList.size()];
                    double[] zAxis = new double[itemList.size()];
                    String[] featureIDS = new String[itemList.size()];
                    for (int i = 0; i < itemList.size(); i++) {
                        XYZItem item = itemList.get(i);
                        xAxis[i] = item.x;
                        yAxis[i] = item.y;
                        zAxis[i] = item.z;
                        featureIDS[i] = item.featureID;
                        selected.add(ff.featureId(item.featureID));
                    }

                    ds.addSeries(EMPTY, new double[][] { xAxis, yAxis, zAxis });
                    ds.addFeatrureIDS(EMPTY, featureIDS);
                    map.select(ff.id(selected), layer);
                } else {
                    map.select(Filter.EXCLUDE, layer);
                    ds.removeSeries(EMPTY);
                }
                this.forceRedraw();
            }
        }

        @Override
        public void restoreAutoBounds() {
            return;
        }

        final class XYZItem {
            public double x;

            public double y;

            public double z;

            public String featureID;

            public XYZItem() {

            }

            public XYZItem(String featureID, double x, double y, double z) {
                this.featureID = featureID;
                this.x = x;
                this.y = y;
                this.z = z;
            }
        }

    }
}