All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jdplus.toolkit.desktop.plugin.ui.JSlidingSpansView Maven / Gradle / Ivy

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package jdplus.toolkit.desktop.plugin.ui;

import jdplus.main.desktop.design.SwingComponent;
import jdplus.main.desktop.design.SwingProperty;
import jdplus.toolkit.desktop.plugin.components.parts.HasColorScheme;
import jdplus.toolkit.desktop.plugin.components.parts.HasColorSchemeResolver;
import jdplus.toolkit.desktop.plugin.components.parts.HasColorSchemeSupport;
import jdplus.toolkit.desktop.plugin.components.tools.JChartPanel;
import jdplus.toolkit.desktop.plugin.datatransfer.DataTransferManager;
import jdplus.toolkit.desktop.plugin.jfreechart.TsCharts;
import jdplus.toolkit.desktop.plugin.util.NbComponents;
import jdplus.toolkit.desktop.plugin.jfreechart.MatrixChartCommand;
import jdplus.toolkit.desktop.plugin.ui.processing.TsViewToolkit;
import jdplus.toolkit.desktop.plugin.html.processing.HtmlSlidingSpanDocument;
import jdplus.toolkit.base.api.timeseries.TsData;
import ec.util.chart.ColorScheme.KnownColor;
import ec.util.chart.swing.Charts;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.*;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYAreaRenderer;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.Range;
import org.jfree.data.xy.DefaultXYDataset;

import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.Transferable;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import jdplus.toolkit.base.core.stats.DescriptiveStatistics;
import jdplus.toolkit.base.core.timeseries.simplets.analysis.DiagnosticInfo;
import jdplus.toolkit.base.core.timeseries.simplets.analysis.SlidingSpans;
import ec.util.chart.swing.ChartCommand;
import ec.util.chart.swing.SwingColorSchemeSupport;

import java.util.Locale;
import java.util.function.Function;

/**
 * @author Kristof Bayens
 */
@SwingComponent
public final class JSlidingSpansView extends JComponent implements HasColorScheme {

    // CONSTANTS
    protected static final int N = 18;

    // PROPERTIES DEFINITION
    @SwingProperty
    public static final String SLIDING_SPANS_PROPERTY = "slidingSpans";

    @SwingProperty
    public static final String INFO_NAME_PROPERTY = "infoName";

    @SwingProperty
    public static final String EXTRACTOR_PROPERTY = "extractor";

    @SwingProperty
    public static final String THRESHOLD_PROPERTY = "threshold";

    @SwingProperty
    public static final String INFO_PROPERTY = "info";

    // DEFAULT PROPERTIES
    protected static final double DEFAULT_THRESHOLD = 3;
    protected static final DiagnosticInfo DEFAULT_INFO = DiagnosticInfo.RelativeDifference;
    // PROPERTIES
    private SlidingSpans slidingSpans;
    private boolean multiplicative;
    private Function extractor;
    private String infoName;
    private double threshold;
    private DiagnosticInfo info;
    // OTHER
    private final JChartPanel seriesPanel;
    private final JChartPanel distributionPanel;
    private final Box documentPanel;

    @lombok.experimental.Delegate
    private final HasColorScheme colorScheme = HasColorSchemeSupport.of(this::firePropertyChange);

    private final HasColorSchemeResolver colorSchemeResolver = new HasColorSchemeResolver(colorScheme, this::onColorSchemeChange);

    public JSlidingSpansView() {

        this.slidingSpans = null;
        this.extractor = null;
        this.threshold = DEFAULT_THRESHOLD;
        this.info = DEFAULT_INFO;

        this.seriesPanel = new JChartPanel(createSeriesChart());
        this.distributionPanel = new JChartPanel(createDistributionChart());
        this.documentPanel = Box.createHorizontalBox();

        JSplitPane splitpane1 = NbComponents.newJSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, distributionPanel, documentPanel);
        splitpane1.setDividerLocation(0.5);
        splitpane1.setResizeWeight(0.5);

        JSplitPane splitpane2 = NbComponents.newJSplitPane(JSplitPane.VERTICAL_SPLIT, true, seriesPanel, splitpane1);
        splitpane2.setDividerLocation(0.4);
        splitpane2.setResizeWeight(0.5);

        setLayout(new BorderLayout());
        add(splitpane2, BorderLayout.CENTER);

        addPropertyChangeListener(evt -> {
            switch (evt.getPropertyName()) {
                case SLIDING_SPANS_PROPERTY:
                    onSlidingSpansChange();
                    break;
                case INFO_NAME_PROPERTY:
                    onInfoNameChange();
                    break;
                case THRESHOLD_PROPERTY:
                    onThresholdChange();
                    break;
                case INFO_PROPERTY:
                    onInfoChange();
                    break;
            }
        });

        seriesPanel.setPopupMenu(createSeriesMenu(seriesPanel).getPopupMenu());
        distributionPanel.setPopupMenu(createDistributionMenu(distributionPanel).getPopupMenu());
    }

    //
    protected void onSlidingSpansChange() {
        if (slidingSpans == null) {
            return;
        }

        clear();
        TsData data = slidingSpans.statistics(info, x -> extractor.apply(x));
        if (data == null || data.getValues().count(x -> Double.isNaN(x)) == data.length()) {
            return;
        }

        DescriptiveStatistics stats = DescriptiveStatistics.of(data.getValues());
        if (stats.isConstant()) {
            return;
        }

        showSeries(data, multiplicative);
        showDistribution(stats, multiplicative);
        showStatistics();

        onColorSchemeChange();
    }

    protected void onInfoNameChange() {
        onSlidingSpansChange();
    }

    protected void onThresholdChange() {
        onSlidingSpansChange();
    }

    protected void onInfoChange() {
        onSlidingSpansChange();
    }

    protected void onColorSchemeChange() {
        SwingColorSchemeSupport themeSupport = colorSchemeResolver.resolve();

        for (JChartPanel o : Arrays.asList(seriesPanel, distributionPanel)) {
            XYPlot plot = o.getChart().getXYPlot();
            plot.setBackgroundPaint(themeSupport.getPlotColor());
            plot.setDomainGridlinePaint(themeSupport.getGridColor());
            plot.setRangeGridlinePaint(themeSupport.getGridColor());
            plot.getRenderer().setBasePaint(themeSupport.getAreaColor(KnownColor.BLUE));
            plot.getRenderer().setBaseOutlinePaint(themeSupport.getLineColor(KnownColor.BLUE));
            o.getChart().setBackgroundPaint(themeSupport.getBackColor());
        }
    }
    //

    private Range calcRange(double[] values) {
        double min = Double.NEGATIVE_INFINITY, max = -Double.POSITIVE_INFINITY;

        DescriptiveStatistics stats = DescriptiveStatistics.ofInternal(values);
        double smin = stats.getMin(), smax = stats.getMax();
        if (Double.isInfinite(min) || smin < min) {
            min = smin;
        }
        if (Double.isInfinite(max) || smax > max) {
            max = smax;
        }

        if (Double.isInfinite(max) || Double.isInfinite(min)) {
            return new Range(0, 1);
        }
        double length = max - min;
        if (length == 0) {
            return new Range(0, 1);
        } else {
            //double eps = length * .05;
            //return new Range(min - eps, max + eps);
            return new Range(min, max);
        }
    }

    private double calcTick(Range rng) {
        double tick = 0;
        double avg = (rng.getUpperBound() - rng.getLowerBound()) / 6;
        for (int i = 0; i < 10 && tick == 0; i++) {
            double power = Math.pow(10, i);
            if (avg > (0.1 * power) && avg <= (0.2 * power)) {
                tick = (0.2 * power);
            } else if (avg > (0.2 * power) && avg <= (0.5 * power)) {
                tick = (0.5 * power);
            } else if (avg > (0.5 * power) && avg <= (1 * power)) {
                tick = (1 * power);
            }
        }
        return tick;
    }

    private void showDistribution(DescriptiveStatistics stats, boolean mul) {
        DefaultXYDataset dataset = new DefaultXYDataset();

        double nobs = stats.getObservationsCount();
        double step = threshold / 6;
        double[] xvalues = new double[N];
        double[] values = new double[N];
        for (int i = 0; i < N; ++i) {
            xvalues[i] = step * (i + .5);
            values[i] = (stats.countBetween(i * step, (i + 1) * step) / nobs);
        }
        dataset.addSeries("", new double[][]{xvalues, values});

        XYPlot plot = distributionPanel.getChart().getXYPlot();
        plot.setDataset(dataset);

        NumberAxis xAxis = (NumberAxis) plot.getDomainAxis();
        NumberAxis yAxis = (NumberAxis) plot.getRangeAxis();
        if (mul) {
            xAxis.setTickUnit(new NumberTickUnit(0.02), true, false);
            xAxis.setRange(0, 0.1);
            yAxis.setTickUnit(new PercentageTickUnit(0.05), true, false);
        } else {
            Range rng = calcRange(xvalues);
            xAxis.setTickUnit(new NumberTickUnit(calcTick(rng)), true, false);
            yAxis.setTickUnit(new PercentageTickUnit(0.05), true, false);
        }
    }

    private void showStatistics() {
        HtmlSlidingSpanDocument document = new HtmlSlidingSpanDocument<>(slidingSpans, info, extractor);
        document.setThreshold(threshold);
        Disposables.disposeAndRemoveAll(documentPanel).add(TsViewToolkit.getHtmlViewer(document));
    }

    private void showSeries(TsData data, boolean mul) {
        // used by copy action
        seriesPanel.putClientProperty("TS_DATA", data);

        XYPlot plot = seriesPanel.getChart().getXYPlot();
        plot.setDataset(TsXYDatasets.from(infoName, data));
    }

    private void clear() {
        //dataChart_.getTsCollection().clear();
    }

    //
    public String getInfoName() {
        return infoName;
    }

    public void setInfoName(String infoName) {
        String old = this.infoName;
        this.infoName = infoName;
        firePropertyChange(INFO_NAME_PROPERTY, old, this.infoName);
    }

    public Function getExtractor() {
        return extractor;
    }

    public void setExtractor(Function extractor) {
        String old = this.infoName;
        this.extractor = extractor;
        firePropertyChange(EXTRACTOR_PROPERTY, old, this.infoName);
    }

    public boolean getMultiplicative() {
        return multiplicative;
    }

    public void setMultiplicative(boolean multiplicative) {
        this.multiplicative = multiplicative;
    }

    public double getThreshold() {
        return threshold;
    }

    public void setThreshold(double threshold) {
        double old = this.threshold;
        this.threshold = threshold;
        firePropertyChange(THRESHOLD_PROPERTY, old, this.threshold);
    }

    public DiagnosticInfo getInfo() {
        return info;
    }

    public void setInfo(DiagnosticInfo info) {
        DiagnosticInfo old = this.info;
        this.info = info;
        firePropertyChange(INFO_NAME_PROPERTY, old, this.info);
    }

    public SlidingSpans getSlidingSpans() {
        return slidingSpans;
    }

    public void setSlidingSpans(SlidingSpans slidingspans) {
        SlidingSpans old = this.slidingSpans;
        this.slidingSpans = slidingspans;
        firePropertyChange(SLIDING_SPANS_PROPERTY, old, this.slidingSpans);
    }
    //

    static JFreeChart createSeriesChart() {
        JFreeChart result = ChartFactory.createXYBarChart("", "", false, "", Charts.emptyXYDataset(), PlotOrientation.VERTICAL, false, false, false);
        result.setPadding(TsCharts.CHART_PADDING);
        result.getTitle().setFont(TsCharts.CHART_TITLE_FONT);

        XYPlot plot = result.getXYPlot();

        DateAxis domainAxis = new DateAxis();
        //dateAxis.setTickMarkPosition(DateTickMarkPosition.MIDDLE);
        domainAxis.setTickUnit(new DateTickUnit(DateTickUnitType.YEAR, 1), true, false);
        domainAxis.setDateFormatOverride(new SimpleDateFormat("yyyy-MM", Locale.getDefault(Locale.Category.FORMAT)));
        domainAxis.setTickLabelPaint(TsCharts.CHART_TICK_LABEL_COLOR);
        plot.setDomainAxis(domainAxis);

        plot.getRangeAxis().setTickLabelPaint(TsCharts.CHART_TICK_LABEL_COLOR);

        XYBarRenderer renderer = (XYBarRenderer) plot.getRenderer();
        renderer.setShadowVisible(false);
        renderer.setDrawBarOutline(true);
        renderer.setAutoPopulateSeriesPaint(false);
        renderer.setAutoPopulateSeriesOutlinePaint(false);

        return result;
    }

    static JMenu createSeriesMenu(ChartPanel chartPanel) {
        JMenu result = new JMenu();

        ChartCommand copy = new ChartCommand() {
            @Override
            public void execute(ChartPanel chartPanel) {
                TsData data = (TsData) chartPanel.getClientProperty("TS_DATA");
                Transferable t = DataTransferManager.get().fromTsData(data);
                Toolkit.getDefaultToolkit().getSystemClipboard().setContents(t, null);
            }

            @Override
            public boolean isEnabled(ChartPanel chartPanel) {
                return chartPanel.getClientProperty("TS_DATA") instanceof TsData;
            }
        };

        result.add(copy.toAction(chartPanel)).setText("Copy series");

        JMenu export = new JMenu("Export image to");
        export.add(ChartCommand.printImage().toAction(chartPanel)).setText("Printer...");
        export.add(ChartCommand.copyImage().toAction(chartPanel)).setText("Clipboard");
        export.add(ChartCommand.saveImage().toAction(chartPanel)).setText("File...");
        result.add(export);

        return result;
    }

    static JFreeChart createDistributionChart() {
        JFreeChart result = ChartFactory.createXYAreaChart("Distribution", "", "", Charts.emptyXYDataset(), PlotOrientation.VERTICAL, false, false, false);
        result.setPadding(TsCharts.CHART_PADDING);
        result.getTitle().setFont(TsCharts.CHART_TITLE_FONT);

        XYPlot plot = result.getXYPlot();
        plot.getDomainAxis().setTickLabelPaint(TsCharts.CHART_TICK_LABEL_COLOR);
        plot.getRangeAxis().setTickLabelPaint(TsCharts.CHART_TICK_LABEL_COLOR);

        XYAreaRenderer renderer = (XYAreaRenderer) plot.getRenderer();
        renderer.setAutoPopulateSeriesPaint(false);
        renderer.setOutline(true);

        return result;
    }

    static JMenu createDistributionMenu(ChartPanel chartPanel) {
        JMenu result = new JMenu();

        result.add(MatrixChartCommand.copySeries(0, 0).toAction(chartPanel)).setText("Copy distribution");

        JMenu export = new JMenu("Export image to");
        export.add(ChartCommand.printImage().toAction(chartPanel)).setText("Printer...");
        export.add(ChartCommand.copyImage().toAction(chartPanel)).setText("Clipboard");
        export.add(ChartCommand.saveImage().toAction(chartPanel)).setText("File...");
        result.add(export);

        return result;
    }
}