/* * 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.awt.geom.Ellipse2D; import java.lang.reflect.InvocationTargetException; 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.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.PearsonCorrelationProcess; import org.geotools.process.spatialstatistics.StatisticsFeaturesProcess; import org.geotools.process.spatialstatistics.operations.DataStatisticsOperation.DataStatisticsResult; import org.geotools.process.spatialstatistics.operations.PearsonOperation.PearsonResult; 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.XYItemEntity; import org.jfree.chart.labels.StandardXYToolTipGenerator; import org.jfree.chart.labels.XYToolTipGenerator; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.plot.SeriesRenderingOrder; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.XYItemRenderer; import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; import org.jfree.data.xy.XYDataset; import org.jfree.data.xy.XYSeries; import org.jfree.data.xy.XYSeriesCollection; import org.jfree.ui.RectangleInsets; 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.util.ProgressListener; /** * Scatter Plot Dialog * * @author Minpa Lee, MangoSystem * * @source $URL$ */ public class ScatterPlotDialog extends AbstractGeoProcessingDialog implements IRunnableWithProgress { protected static final Logger LOGGER = Logging.getLogger(ScatterPlotDialog.class); protected final FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null); private ChartComposite2 chartComposite; private ILayer inputLayer; private Combo cboLayer, cboXField, cboYField; private Button chkStatistics, chkPearson; private Browser browser; private CTabItem inputTab, plotTab, outputTab; private XYMinMaxVisitor minMaxVisitor = new XYMinMaxVisitor(); public ScatterPlotDialog(Shell parentShell, IMap map) { super(parentShell, map); setShellStyle(SWT.CLOSE | SWT.MIN | SWT.TITLE | SWT.BORDER | SWT.MODELESS | SWT.RESIZE); this.windowTitle = Messages.ScatterPlotDialog_title; this.windowDesc = Messages.ScatterPlotDialog_description; this.windowSize = ToolboxPlugin.rescaleSize(parentShell, 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.ScatterPlotDialog_IndependentField, EMPTY, image, 1); cboXField = uiBuilder.createCombo(container, 1, true); uiBuilder .createLabel(container, Messages.ScatterPlotDialog_DependentField, EMPTY, image, 1); cboYField = uiBuilder.createCombo(container, 1, true); uiBuilder.createLabel(container, null, null, 1); chkStatistics = uiBuilder.createCheckbox(container, Messages.ScatterPlotDialog_BasicStatistics, null, 1); chkPearson = uiBuilder.createCheckbox(container, Messages.ScatterPlotDialog_Pearson, 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); } } }); // 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 ChartComposite2(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) { ChartEntity entity = event.getEntity(); if (entity != null && (entity instanceof XYItemEntity)) { XYItemEntity item = (XYItemEntity) entity; if (item.getSeriesIndex() == 0) { XYSeriesCollection dataSet = (XYSeriesCollection) item.getDataset(); XYSeries xySeries = dataSet.getSeries(item.getSeriesIndex()); XYDataItem2 dataItem = (XYDataItem2) xySeries.getDataItem(item.getItem()); Filter selectionFilter = ff.id(ff.featureId(dataItem.getFeature().getID())); map.select(selectionFilter, inputLayer); } else { map.select(Filter.EXCLUDE, inputLayer); } } else { map.select(Filter.EXCLUDE, inputLayer); } } } private void updateChart(SimpleFeatureCollection features, String xField, String yField) { // 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)); // 2. Setup Scatter plot // Create the scatter 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)); XYToolTipGenerator plotToolTip = new StandardXYToolTipGenerator(); XYItemRenderer plotRenderer = new XYLineAndShapeRenderer(false, true); // Shapes only plotRenderer.setSeriesShape(0, new Ellipse2D.Double(0, 0, 3, 3)); plotRenderer.setSeriesPaint(0, java.awt.Color.BLUE); // dot plotRenderer.setBaseToolTipGenerator(plotToolTip); // Set the scatter data, renderer, and axis into plot plot.setDataset(0, getScatterPlotData(features, xField, yField)); 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); // Degree XYSeries degree = new XYSeries("Deegree"); //$NON-NLS-1$ degree.add(xPlotAxis.getRange().getLowerBound(), yPlotAxis.getRange().getLowerBound()); degree.add(xPlotAxis.getRange().getUpperBound(), yPlotAxis.getRange().getUpperBound()); lineDataset.addSeries(degree); 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 XYLineAndShapeRenderer(false, true); // Shapes only selectionRenderer.setSeriesShape(0, new Ellipse2D.Double(0, 0, 6, 6)); selectionRenderer.setSeriesPaint(0, java.awt.Color.RED); // dot plot.setDataset(2, new XYSeriesCollection(new XYSeries(EMPTY))); 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 XYDataset getScatterPlotData(SimpleFeatureCollection features, String xField, String yField) { XYSeries xySeries = new XYSeries(features.getSchema().getTypeName()); minMaxVisitor.reset(); Expression xExpression = ff.property(xField); Expression yExpression = ff.property(yField); SimpleFeatureIterator featureIter = features.features(); try { while (featureIter.hasNext()) { SimpleFeature feature = featureIter.next(); 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; } minMaxVisitor.visit(xVal, yVal); xySeries.add(new XYDataItem2(feature, xVal, yVal)); } } finally { featureIter.close(); } return new XYSeriesCollection(xySeries); } @Override protected void okPressed() { if (invalidWidgetValue(cboLayer, cboXField, cboYField)) { 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 scatter plot..."); createGraphTab(inputTab.getParent()); } if (outputTab == null) { createOutputTab(inputTab.getParent()); } monitor.worked(increment); String xField = cboXField.getText(); String yField = cboYField.getText(); SimpleFeatureCollection features = MapUtils.getFeatures(inputLayer); String fields = xField + "," + yField; HtmlWriter writer = new HtmlWriter(inputLayer.getName()); DataStatisticsResult statistics = null; PearsonResult pearson = null; if (chkStatistics.getSelection()) { ProgressListener subMonitor = GeoToolsAdapters.progress(SubMonitor.convert(monitor, Messages.Task_Internal, 20)); statistics = StatisticsFeaturesProcess.process(features, fields, subMonitor); writer.writeDataStatistics(statistics); } if (chkPearson.getSelection()) { ProgressListener subMonitor = GeoToolsAdapters.progress(SubMonitor.convert(monitor, Messages.Task_Internal, 20)); pearson = PearsonCorrelationProcess.process(features, fields, subMonitor); writer.writePearson(pearson); } if (statistics != null || pearson != null) { browser.setText(writer.getHTML()); } monitor.subTask("Updating scatter plot..."); chartComposite.setLayer(inputLayer); updateChart(features, xField, yField); 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(); } } }