org.datacleaner.widgets.result.DateGapAnalyzerResultSwingRenderer.java Source code

Java tutorial

Introduction

Here is the source code for org.datacleaner.widgets.result.DateGapAnalyzerResultSwingRenderer.java

Source

/**
 * DataCleaner (community edition)
 * Copyright (C) 2014 Neopost - Customer Information Management
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * 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 Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.datacleaner.widgets.result;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollBar;

import org.apache.metamodel.schema.Table;
import org.datacleaner.api.AnalyzerResult;
import org.datacleaner.api.InputColumn;
import org.datacleaner.api.RendererBean;
import org.datacleaner.beans.dategap.DateGapAnalyzer;
import org.datacleaner.beans.dategap.DateGapAnalyzerResult;
import org.datacleaner.beans.dategap.TimeInterval;
import org.datacleaner.bootstrap.WindowContext;
import org.datacleaner.components.convert.ConvertToStringTransformer;
import org.datacleaner.configuration.DataCleanerConfiguration;
import org.datacleaner.connection.Datastore;
import org.datacleaner.connection.DatastoreCatalog;
import org.datacleaner.connection.DatastoreConnection;
import org.datacleaner.data.MutableInputColumn;
import org.datacleaner.guice.DCModuleImpl;
import org.datacleaner.job.builder.AnalysisJobBuilder;
import org.datacleaner.job.runner.AnalysisResultFuture;
import org.datacleaner.job.runner.AnalysisRunner;
import org.datacleaner.job.runner.AnalysisRunnerImpl;
import org.datacleaner.panels.DCPanel;
import org.datacleaner.result.renderer.AbstractRenderer;
import org.datacleaner.result.renderer.RendererFactory;
import org.datacleaner.result.renderer.SwingRenderingFormat;
import org.datacleaner.util.ChartUtils;
import org.datacleaner.util.LabelUtils;
import org.datacleaner.util.LookAndFeelManager;
import org.datacleaner.util.VFSUtils;
import org.datacleaner.util.WidgetUtils;
import org.datacleaner.windows.DetailsResultWindow;
import org.jdesktop.swingx.VerticalLayout;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.entity.ChartEntity;
import org.jfree.chart.entity.PlotEntity;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.data.gantt.SlidingGanttCategoryDataset;
import org.jfree.data.gantt.Task;
import org.jfree.data.gantt.TaskSeries;
import org.jfree.data.gantt.TaskSeriesCollection;
import org.jfree.data.time.SimpleTimePeriod;
import org.jfree.data.time.TimePeriod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.Guice;
import com.google.inject.Injector;

@RendererBean(SwingRenderingFormat.class)
public class DateGapAnalyzerResultSwingRenderer extends AbstractRenderer<DateGapAnalyzerResult, JComponent> {

    private static final Logger logger = LoggerFactory.getLogger(DateGapAnalyzerResultSwingRenderer.class);

    private static final String LABEL_OVERLAPS = "Overlaps";
    private static final String LABEL_GAPS = "Gaps";
    private static final String LABEL_COMPLETE_DURATION = "Complete duration";
    private static final int GROUPS_VISIBLE = 8;

    @Override
    public JComponent render(DateGapAnalyzerResult result) {

        final TaskSeriesCollection dataset = new TaskSeriesCollection();
        final Set<String> groupNames = result.getGroupNames();
        final TaskSeries completeDurationTaskSeries = new TaskSeries(LABEL_COMPLETE_DURATION);
        final TaskSeries gapsTaskSeries = new TaskSeries(LABEL_GAPS);
        final TaskSeries overlapsTaskSeries = new TaskSeries(LABEL_OVERLAPS);
        for (final String groupName : groupNames) {
            final String groupDisplayName;

            if (groupName == null) {
                if (groupNames.size() == 1) {
                    groupDisplayName = "All";
                } else {
                    groupDisplayName = LabelUtils.NULL_LABEL;
                }
            } else {
                groupDisplayName = groupName;
            }

            final TimeInterval completeDuration = result.getCompleteDuration(groupName);
            final Task completeDurationTask = new Task(groupDisplayName,
                    createTimePeriod(completeDuration.getFrom(), completeDuration.getTo()));
            completeDurationTaskSeries.add(completeDurationTask);

            // plot gaps
            {
                final SortedSet<TimeInterval> gaps = result.getGaps(groupName);

                int i = 1;
                Task rootTask = null;
                for (TimeInterval interval : gaps) {
                    final TimePeriod timePeriod = createTimePeriod(interval.getFrom(), interval.getTo());

                    if (rootTask == null) {
                        rootTask = new Task(groupDisplayName, timePeriod);
                        gapsTaskSeries.add(rootTask);
                    } else {
                        Task task = new Task(groupDisplayName + " gap" + i, timePeriod);
                        rootTask.addSubtask(task);
                    }

                    i++;
                }
            }

            // plot overlaps
            {
                final SortedSet<TimeInterval> overlaps = result.getOverlaps(groupName);

                int i = 1;
                Task rootTask = null;
                for (TimeInterval interval : overlaps) {
                    final TimePeriod timePeriod = createTimePeriod(interval.getFrom(), interval.getTo());

                    if (rootTask == null) {
                        rootTask = new Task(groupDisplayName, timePeriod);
                        overlapsTaskSeries.add(rootTask);
                    } else {
                        Task task = new Task(groupDisplayName + " overlap" + i, timePeriod);
                        rootTask.addSubtask(task);
                    }

                    i++;
                }
            }
        }
        dataset.add(overlapsTaskSeries);
        dataset.add(gapsTaskSeries);
        dataset.add(completeDurationTaskSeries);

        final SlidingGanttCategoryDataset slidingDataset = new SlidingGanttCategoryDataset(dataset, 0,
                GROUPS_VISIBLE);

        final JFreeChart chart = ChartFactory.createGanttChart(
                "Date gaps and overlaps in " + result.getFromColumnName() + " / " + result.getToColumnName(),
                result.getGroupColumnName(), "Time", slidingDataset, true, true, false);
        ChartUtils.applyStyles(chart);

        // make sure the 3 timeline types have correct coloring
        {
            final CategoryPlot plot = (CategoryPlot) chart.getPlot();

            plot.setDrawingSupplier(new DCDrawingSupplier(WidgetUtils.ADDITIONAL_COLOR_GREEN_BRIGHT,
                    WidgetUtils.ADDITIONAL_COLOR_RED_BRIGHT, WidgetUtils.BG_COLOR_BLUE_BRIGHT));
        }

        final int visibleLines = Math.min(GROUPS_VISIBLE, groupNames.size());
        final ChartPanel chartPanel = ChartUtils.createPanel(chart, ChartUtils.WIDTH_WIDE, visibleLines * 50 + 200);

        chartPanel.addChartMouseListener(new ChartMouseListener() {
            @Override
            public void chartMouseMoved(ChartMouseEvent event) {
                Cursor cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
                ChartEntity entity = event.getEntity();
                if (entity instanceof PlotEntity) {
                    cursor = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
                }
                chartPanel.setCursor(cursor);
            }

            @Override
            public void chartMouseClicked(ChartMouseEvent event) {
                // do nothing
            }
        });

        final JComponent decoratedChartPanel;

        final StringBuilder chartDescription = new StringBuilder("<html>");
        chartDescription.append("<p>The chart displays the recorded timeline based on FROM and TO dates.</p>");
        chartDescription.append(
                "<p>The <b>red items</b> represent gaps in the timeline and the <b>green items</b> represent points in the timeline where more than one record show activity.</p>");
        chartDescription.append(
                "<p>You can <b>zoom in</b> by clicking and dragging the area that you want to examine in further detail.</p>");

        if (groupNames.size() > GROUPS_VISIBLE) {
            final JScrollBar scroll = new JScrollBar(JScrollBar.VERTICAL);
            scroll.setMinimum(0);
            scroll.setMaximum(groupNames.size());
            scroll.addAdjustmentListener(new AdjustmentListener() {

                @Override
                public void adjustmentValueChanged(AdjustmentEvent e) {
                    int value = e.getAdjustable().getValue();
                    slidingDataset.setFirstCategoryIndex(value);
                }
            });

            chartPanel.addMouseWheelListener(new MouseWheelListener() {

                @Override
                public void mouseWheelMoved(MouseWheelEvent e) {
                    int scrollType = e.getScrollType();
                    if (scrollType == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
                        int wheelRotation = e.getWheelRotation();
                        scroll.setValue(scroll.getValue() + wheelRotation);
                    }
                }
            });

            final DCPanel outerPanel = new DCPanel();
            outerPanel.setLayout(new BorderLayout());
            outerPanel.add(chartPanel, BorderLayout.CENTER);
            outerPanel.add(scroll, BorderLayout.EAST);
            chartDescription.append("<p>Use the right <b>scrollbar</b> to scroll up and down on the chart.</p>");
            decoratedChartPanel = outerPanel;
        } else {
            decoratedChartPanel = chartPanel;
        }

        chartDescription.append("</html>");

        final JLabel chartDescriptionLabel = new JLabel(chartDescription.toString());

        final DCPanel panel = new DCPanel();
        panel.setLayout(new VerticalLayout());
        panel.add(chartDescriptionLabel);
        panel.add(decoratedChartPanel);

        return panel;
    }

    private TimePeriod createTimePeriod(long from, long to) {
        if (from > to) {
            logger.warn("An illegal from/to combination occurred: {}, {}", from, to);
        }
        return new SimpleTimePeriod(from, to);
    }

    /**
     * A main method that will display the results of a few example value
     * distributions. Useful for tweaking the charts and UI.
     * 
     * @param args
     * @throws Throwable
     */
    public static void main(String[] args) throws Throwable {
        LookAndFeelManager.get().init();

        Injector injector = Guice
                .createInjector(new DCModuleImpl(VFSUtils.getFileSystemManager().resolveFile("."), null));

        // run a small job
        final AnalysisJobBuilder ajb = injector.getInstance(AnalysisJobBuilder.class);
        final Datastore ds = injector.getInstance(DatastoreCatalog.class).getDatastore("orderdb");
        ajb.setDatastore(ds);

        final DataCleanerConfiguration conf = injector.getInstance(DataCleanerConfiguration.class);
        final AnalysisRunner runner = new AnalysisRunnerImpl(conf);

        DatastoreConnection con = ds.openConnection();
        Table table = con.getSchemaNavigator().convertToTable("PUBLIC.ORDERS");

        ajb.addSourceColumn(table.getColumnByName("ORDERDATE"));
        ajb.addSourceColumn(table.getColumnByName("SHIPPEDDATE"));
        ajb.addSourceColumn(table.getColumnByName("CUSTOMERNUMBER"));

        @SuppressWarnings("unchecked")
        InputColumn<Date> orderDateColumn = (InputColumn<Date>) ajb.getSourceColumnByName("ORDERDATE");
        @SuppressWarnings("unchecked")
        InputColumn<Date> shippedDateColumn = (InputColumn<Date>) ajb.getSourceColumnByName("SHIPPEDDATE");
        @SuppressWarnings("unchecked")
        InputColumn<Integer> customerNumberColumn = (InputColumn<Integer>) ajb
                .getSourceColumnByName("CUSTOMERNUMBER");
        @SuppressWarnings("unchecked")
        MutableInputColumn<String> customerNumberAsStringColumn = (MutableInputColumn<String>) ajb
                .addTransformer(ConvertToStringTransformer.class).addInputColumn(customerNumberColumn)
                .getOutputColumns().get(0);

        DateGapAnalyzer dga = ajb.addAnalyzer(DateGapAnalyzer.class).getComponentInstance();
        dga.setFromColumn(orderDateColumn);
        dga.setToColumn(shippedDateColumn);
        dga.setGroupColumn(customerNumberAsStringColumn);

        AnalysisResultFuture resultFuture = runner.run(ajb.toAnalysisJob());

        if (resultFuture.isErrornous()) {
            throw resultFuture.getErrors().get(0);
        }

        List<AnalyzerResult> list = Collections.emptyList();
        RendererFactory rendererFactory = new RendererFactory(conf);
        DetailsResultWindow window = new DetailsResultWindow("Example", list,
                injector.getInstance(WindowContext.class), rendererFactory);
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        List<AnalyzerResult> results = resultFuture.getResults();
        for (AnalyzerResult analyzerResult : results) {
            JComponent renderedResult = new DateGapAnalyzerResultSwingRenderer()
                    .render((DateGapAnalyzerResult) analyzerResult);
            window.addRenderedResult(renderedResult);
        }
        window.repaint();

        window.setPreferredSize(new Dimension(800, 600));

        window.setVisible(true);
    }
}