Java tutorial
/* * Copyright 2014 National Bank of Belgium * * Licensed under the EUPL, Version 1.1 or as soon they will be approved * by the European Commission - subsequent versions of the EUPL (the "Licence"); * You may not use this work except in compliance with the Licence. * You may obtain a copy of the Licence at: * * http://ec.europa.eu/idabc/eupl * * Unless required by applicable law or agreed to in writing, software * distributed under the Licence is distributed on an "AS IS" basis, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the Licence for the specific language governing permissions and * limitations under the Licence. */ package be.nbb.demetra.dfm.output.simulation; import be.nbb.demetra.dfm.output.simulation.utils.FilterEvaluationSamplePanel; import com.google.common.base.Optional; import ec.nbdemetra.ui.DemetraUI; import ec.tss.datatransfer.TssTransferSupport; import ec.tss.dfm.DfmDocument; import ec.tss.dfm.DfmSeriesDescriptor; import ec.tss.dfm.DfmSimulation; import ec.tss.dfm.DfmSimulationResults; import ec.tss.tsproviders.utils.Formatters; import ec.tstoolkit.data.Table; import ec.tstoolkit.timeseries.simplets.TsPeriod; import ec.ui.chart.BasicXYDataset; import ec.ui.chart.TsCharts; import ec.ui.view.JChartPanel; import ec.util.chart.ColorScheme; import ec.util.chart.ColorScheme.KnownColor; import ec.util.chart.swing.Charts; import ec.util.chart.swing.SwingColorSchemeSupport; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Paint; import java.awt.Stroke; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Ellipse2D; import java.beans.PropertyChangeEvent; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import javax.swing.AbstractAction; import javax.swing.DefaultComboBoxModel; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.NumberTickUnit; import org.jfree.chart.plot.DatasetRenderingOrder; import org.jfree.chart.plot.ValueMarker; import org.jfree.chart.plot.XYPlot; import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; /** * * @author Mats Maggi */ public class RealTimePerspGraphView extends javax.swing.JPanel { public static final String DFM_SIMULATION_PROPERTY = "dfmSimulation"; private Optional<DfmSimulation> dfmSimulation; private static final int FCTS_INDEX = 0; private static final int TRUE_DATA_INDEX = 1; private static final int ARIMA_DATA_INDEX = 2; private static final Stroke MARKER_STROKE = new BasicStroke(0.5f); private static final Paint MARKER_PAINT = Color.GRAY; private static final float MARKER_ALPHA = 1f; private final DemetraUI demetraUI; private Formatters.Formatter<Number> formatter; private SwingColorSchemeSupport defaultColorSchemeSupport; private final XYLineAndShapeRenderer trueDataRenderer; private final XYLineAndShapeRenderer forecastsRenderer; private final XYLineAndShapeRenderer arimaRenderer; private final Map<Bornes, Graphs> graphs_; private Graphs selectedGraph; private JFreeChart mainChart; private JFreeChart detailChart; private JChartPanel chartPanel; private FilterEvaluationSamplePanel filterPanel; private int indexSelected = -1; /** * Creates new form FixedHorizonsGraphView */ public RealTimePerspGraphView(DfmDocument doc) { initComponents(); demetraUI = DemetraUI.getDefault(); formatter = demetraUI.getDataFormat().numberFormatter(); defaultColorSchemeSupport = new SwingColorSchemeSupport() { @Override public ColorScheme getColorScheme() { return demetraUI.getColorScheme(); } }; this.graphs_ = new LinkedHashMap<>(); trueDataRenderer = new XYLineAndShapeRenderer(true, false); trueDataRenderer.setAutoPopulateSeriesPaint(false); trueDataRenderer.setBasePaint(defaultColorSchemeSupport.getLineColor(KnownColor.RED)); forecastsRenderer = new XYLineAndShapeRenderer(true, false); forecastsRenderer.setAutoPopulateSeriesPaint(false); forecastsRenderer.setAutoPopulateSeriesShape(false); forecastsRenderer.setBaseShape(new Ellipse2D.Double(-2, -2, 4, 4)); forecastsRenderer.setBasePaint(defaultColorSchemeSupport.getLineColor(KnownColor.BLUE)); forecastsRenderer.setBaseShapesFilled(false); arimaRenderer = new XYLineAndShapeRenderer(true, false); arimaRenderer.setAutoPopulateSeriesPaint(false); arimaRenderer.setAutoPopulateSeriesShape(false); arimaRenderer.setBaseShape(new Ellipse2D.Double(-2, -2, 4, 4)); arimaRenderer.setBasePaint(defaultColorSchemeSupport.getLineColor(KnownColor.GREEN)); arimaRenderer.setBaseShapesFilled(false); mainChart = createChart(); detailChart = createChart(); this.dfmSimulation = Optional.absent(); chartPanel = new JChartPanel(null); chartPanel.setPopupMenu(buildMenu().getPopupMenu()); chartPanel.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { indexSelected = -1; if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { double x = chartPanel.getChartX(e.getX()); Graphs g = null; for (Bornes b : graphs_.keySet()) { indexSelected++; if (x >= b.min_ && x <= b.max_) { g = graphs_.get(b); break; } } if (g == null) { return; } showDetail(g); } } }); comboBox.setRenderer(new ComboBoxRenderer()); comboBox.addItemListener((ItemEvent e) -> { filterPanel = null; updateChart(); }); addPropertyChangeListener((PropertyChangeEvent evt) -> { switch (evt.getPropertyName()) { case DFM_SIMULATION_PROPERTY: updateComboBox(); updateChart(); break; } }); updateComboBox(); updateChart(); demetraUI.addPropertyChangeListener((PropertyChangeEvent evt) -> { switch (evt.getPropertyName()) { case DemetraUI.DATA_FORMAT_PROPERTY: onDataFormatChanged(); break; case DemetraUI.COLOR_SCHEME_NAME_PROPERTY: onColorSchemeChanged(); break; } }); add(chartPanel, BorderLayout.CENTER); } private JFreeChart createChart() { XYPlot plot = new XYPlot(); plot.setDataset(TRUE_DATA_INDEX, Charts.emptyXYDataset()); plot.setRenderer(TRUE_DATA_INDEX, trueDataRenderer); plot.mapDatasetToDomainAxis(TRUE_DATA_INDEX, 0); plot.mapDatasetToRangeAxis(TRUE_DATA_INDEX, 0); plot.setDataset(FCTS_INDEX, Charts.emptyXYDataset()); plot.setRenderer(FCTS_INDEX, forecastsRenderer); plot.mapDatasetToDomainAxis(FCTS_INDEX, 0); plot.mapDatasetToRangeAxis(FCTS_INDEX, 0); plot.setDataset(ARIMA_DATA_INDEX, Charts.emptyXYDataset()); plot.setRenderer(ARIMA_DATA_INDEX, arimaRenderer); plot.mapDatasetToDomainAxis(ARIMA_DATA_INDEX, 0); plot.mapDatasetToRangeAxis(ARIMA_DATA_INDEX, 0); plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD); JFreeChart result = new JFreeChart("", TsCharts.CHART_TITLE_FONT, plot, false); result.setPadding(TsCharts.CHART_PADDING); return result; } private JMenu buildMenu() { JMenu menu = new JMenu(); JMenuItem back = new JMenuItem(new AbstractAction("Show complete data...") { @Override public void actionPerformed(ActionEvent e) { showMain(); indexSelected = -1; } }); menu.add(back); JMenuItem item = new JMenuItem(new CopyDetailAction()); item.setText("Copy current data"); menu.add(item); return menu; } private void showMain() { XYPlot plot = mainChart.getXYPlot(); rescaleAxis((NumberAxis) plot.getRangeAxis()); chartPanel.setChart(mainChart); onColorSchemeChanged(); chartPanel.setPopupMenu(null); } private void rescaleAxis(NumberAxis axis) { axis.setAutoRangeIncludesZero(false); } private void showDetail(Graphs g) { XYPlot plot = detailChart.getXYPlot(); NumberAxis yAxis = new NumberAxis(); yAxis.setTickLabelPaint(Color.GRAY); plot.setRangeAxis(yAxis); NumberAxis xAxis = new NumberAxis(); xAxis.setTickLabelPaint(Color.GRAY); xAxis.setTickUnit(new NumberTickUnit(10)); xAxis.setRange(horizons.get(0), horizons.get(horizons.size() - 1)); xAxis.setVerticalTickLabels(true); plot.setDomainAxis(xAxis); plot.setDataset(TRUE_DATA_INDEX, new BasicXYDataset(Collections.singletonList(g.S1_))); plot.setDataset(FCTS_INDEX, new BasicXYDataset(Collections.singletonList(g.S2_))); plot.setDataset(ARIMA_DATA_INDEX, new BasicXYDataset(Collections.singletonList(g.S3_))); rescaleAxis((NumberAxis) plot.getRangeAxis()); detailChart.setTitle(g.label_); chartPanel.setChart(detailChart); chartPanel.setToolTipText("Right click to show complete data"); chartPanel.setPopupMenu(buildMenu().getPopupMenu()); onColorSchemeChanged(); selectedGraph = g; } private void onDataFormatChanged() { formatter = demetraUI.getDataFormat().numberFormatter(); } private void onColorSchemeChanged() { defaultColorSchemeSupport = new SwingColorSchemeSupport() { @Override public ColorScheme getColorScheme() { return demetraUI.getColorScheme(); } }; forecastsRenderer.setBasePaint(defaultColorSchemeSupport.getLineColor(KnownColor.BLUE)); trueDataRenderer.setBasePaint(defaultColorSchemeSupport.getLineColor(KnownColor.RED)); arimaRenderer.setBasePaint(defaultColorSchemeSupport.getLineColor(KnownColor.GREEN)); XYPlot mainPlot = mainChart.getXYPlot(); mainPlot.setBackgroundPaint(defaultColorSchemeSupport.getPlotColor()); mainPlot.setDomainGridlinePaint(defaultColorSchemeSupport.getGridColor()); mainPlot.setRangeGridlinePaint(defaultColorSchemeSupport.getGridColor()); mainChart.setBackgroundPaint(defaultColorSchemeSupport.getBackColor()); XYPlot detailPlot = detailChart.getXYPlot(); detailPlot.setBackgroundPaint(defaultColorSchemeSupport.getPlotColor()); detailPlot.setDomainGridlinePaint(defaultColorSchemeSupport.getGridColor()); detailPlot.setRangeGridlinePaint(defaultColorSchemeSupport.getGridColor()); detailChart.setBackgroundPaint(defaultColorSchemeSupport.getBackColor()); } //<editor-fold defaultstate="collapsed" desc="Getters/Setters"> public Optional<DfmSimulation> getSimulationResults() { return dfmSimulation; } public void setSimulationResults(Optional<DfmSimulation> dfmSimulation) { Optional<DfmSimulation> old = this.dfmSimulation; this.dfmSimulation = dfmSimulation != null ? dfmSimulation : Optional.<DfmSimulation>absent(); firePropertyChange(DFM_SIMULATION_PROPERTY, old, this.dfmSimulation); } //</editor-fold> private void updateChart() { if (dfmSimulation.isPresent() && comboBox.getSelectedIndex() != -1) { displayResults(dfmSimulation.get()); showMain(); indexSelected = -1; } } private void updateComboBox() { if (dfmSimulation.isPresent()) { comboBox.setModel(toComboBoxModel(dfmSimulation.get().getDescriptions())); comboBox.setEnabled(true); } else { comboBox.setModel(new DefaultComboBoxModel()); comboBox.setEnabled(false); } } private DefaultComboBoxModel toComboBoxModel(List<DfmSeriesDescriptor> data) { List<DfmSeriesDescriptor> desc = new ArrayList<>(); List<Boolean> watched = dfmSimulation.get().getWatched(); for (int i = 0; i < watched.size(); i++) { if (watched.get(i)) { desc.add(data.get(i)); } } DefaultComboBoxModel result = new DefaultComboBoxModel(desc.toArray()); return result; } private List<Integer> horizons; private List<TsPeriod> periods; private List<TsPeriod> filteredPeriods; private void displayResults(DfmSimulation dfmSimulation) { graphs_.clear(); chartPanel.setChart(null); BasicXYDataset fctsDataset = new BasicXYDataset(); BasicXYDataset trueDataset = new BasicXYDataset(); BasicXYDataset arimaDataset = new BasicXYDataset(); Objects.requireNonNull(dfmSimulation); int selectedIndex = comboBox.getSelectedIndex(); DfmSimulationResults dfm = dfmSimulation.getDfmResults().get(selectedIndex); DfmSimulationResults arima = dfmSimulation.getArimaResults().get(selectedIndex); List<Double> trueValues = dfm.getTrueValues(); horizons = dfm.getForecastHorizons(); periods = dfm.getEvaluationSample(); // Remove periods of evaluation sample not in true values domain filteredPeriods = filterEvaluationSample(trueValues); if (filterPanel == null) { filterPanel = new FilterEvaluationSamplePanel(filteredPeriods); } int np = horizons.size() - 1; // - 1 ??? double xstart = -0.4; double xend = 0.4; final double xstep = 0.8 / np; Double[][] dfmFcts = dfm.getForecastsArray(); Double[][] arimaFcts = arima.getForecastsArray(); //Double[][] arimaFcts = arima.getForecastsArray(); TsPeriod start = filteredPeriods.get(filterPanel.getStart()); TsPeriod end = filteredPeriods.get(filterPanel.getEnd()); int startIndex = periods.indexOf(start); int endIndex = periods.indexOf(end); for (int i = startIndex; i <= endIndex; i++) { double x = xstart; double[] dfmData = new double[horizons.size()]; double[] arimaData = new double[horizons.size()]; for (int j = 0; j < horizons.size(); j++) { dfmData[j] = dfmFcts[j][i] == null ? Double.NaN : dfmFcts[j][i]; arimaData[j] = arimaFcts[j][i] == null ? Double.NaN : arimaFcts[j][i]; } int n = dfmData.length; double m = trueValues.get(i) == null ? Double.NaN : trueValues.get(i); double[] trueX = { xstart, xend }; double[] true2X = { horizons.get(0), horizons.get(np) }; double[] trueY = { m, m }; double[] dfmX = new double[n], dfm2X = new double[n]; double[] dfmY = new double[n], dfm2Y = new double[n]; double[] arimaX = new double[n], arima2X = new double[n]; double[] arimaY = new double[n], arima2Y = new double[n]; for (int j = 0; j < n; j++) { dfmX[j] = x; dfmY[j] = dfmData[j]; dfm2X[j] = horizons.get(j).doubleValue(); dfm2Y[j] = dfmData[j]; arimaX[j] = x; arimaY[j] = arimaData[j]; arima2X[j] = horizons.get(j).doubleValue(); arima2Y[j] = arimaData[j]; x += xstep; } String itemName = periods.get(i).toString(); BasicXYDataset.Series mean = BasicXYDataset.Series.of(itemName, trueX, trueY); BasicXYDataset.Series mean2 = BasicXYDataset.Series.of(itemName, true2X, trueY); BasicXYDataset.Series points = BasicXYDataset.Series.of(itemName, dfmX, dfmY); BasicXYDataset.Series points2 = BasicXYDataset.Series.of(itemName, dfm2X, dfm2Y); BasicXYDataset.Series arimaSeries = BasicXYDataset.Series.of(itemName, arimaX, arimaY); BasicXYDataset.Series arima2Series = BasicXYDataset.Series.of(itemName, arima2X, arima2Y); Bornes b = new Bornes(xstart, xend); Graphs g = new Graphs(mean2, points2, arima2Series, itemName); graphs_.put(b, g); trueDataset.addSeries(mean); fctsDataset.addSeries(points); arimaDataset.addSeries(arimaSeries); xstart++; xend++; } XYPlot plot = mainChart.getXYPlot(); configureAxis(plot, startIndex, endIndex); plot.setDataset(TRUE_DATA_INDEX, trueDataset); plot.setDataset(FCTS_INDEX, fctsDataset); plot.setDataset(ARIMA_DATA_INDEX, arimaDataset); } private List<TsPeriod> filterEvaluationSample(List<Double> trueValues) { List<TsPeriod> p = new ArrayList<>(); for (int i = 0; i < trueValues.size(); i++) { if (trueValues.get(i) != null) { p.add(periods.get(i)); } } return p; } private void configureAxis(XYPlot plot, int start, int end) { int nb = graphs_.size(); List<String> names = new ArrayList<>(); for (int i = start; i <= end; i++) { names.add(periods.get(i).toString()); } NumberAxis xAxis = new NumberAxis(); xAxis.setTickLabelPaint(Color.GRAY); xAxis.setTickUnit(new MyTickUnit(names)); xAxis.setRange(-0.5, nb - 0.5); xAxis.setVerticalTickLabels(true); plot.setDomainAxis(xAxis); plot.setDomainGridlinesVisible(false); NumberAxis yaxis = new NumberAxis(); rescaleAxis(yaxis); yaxis.configure(); plot.setRangeAxis(yaxis); for (int i = 0; i < nb; i++) { ValueMarker marker = new ValueMarker(i + 0.5); marker.setStroke(MARKER_STROKE); marker.setPaint(MARKER_PAINT); marker.setAlpha(MARKER_ALPHA); plot.addDomainMarker(marker); } } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { comboBoxPanel = new javax.swing.JPanel(); variableLabel = new javax.swing.JLabel(); comboBox = new javax.swing.JComboBox(); filterSampleButton = new javax.swing.JButton(); setLayout(new java.awt.BorderLayout()); comboBoxPanel.setLayout(new javax.swing.BoxLayout(comboBoxPanel, javax.swing.BoxLayout.LINE_AXIS)); org.openide.awt.Mnemonics.setLocalizedText(variableLabel, org.openide.util.NbBundle .getMessage(RealTimePerspGraphView.class, "RealTimePerspGraphView.variableLabel.text")); // NOI18N variableLabel.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 5, 1, 10)); comboBoxPanel.add(variableLabel); comboBoxPanel.add(comboBox); org.openide.awt.Mnemonics.setLocalizedText(filterSampleButton, org.openide.util.NbBundle .getMessage(RealTimePerspGraphView.class, "RealTimePerspGraphView.filterSampleButton.text")); // NOI18N filterSampleButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { filterSampleButtonActionPerformed(evt); } }); comboBoxPanel.add(filterSampleButton); add(comboBoxPanel, java.awt.BorderLayout.NORTH); }// </editor-fold>//GEN-END:initComponents private void filterSampleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_filterSampleButtonActionPerformed int r = JOptionPane.showConfirmDialog(chartPanel, filterPanel, "Select evaluation sample", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); if (r == JOptionPane.OK_OPTION) { updateChart(); } }//GEN-LAST:event_filterSampleButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JComboBox comboBox; private javax.swing.JPanel comboBoxPanel; private javax.swing.JButton filterSampleButton; private javax.swing.JLabel variableLabel; // End of variables declaration//GEN-END:variables static class Bornes { static final Bornes ZERO = new Bornes(0, 0); final double min_; final double max_; Bornes(double min, double max) { min_ = min; max_ = max; } } static class Graphs { final BasicXYDataset.Series S1_; final BasicXYDataset.Series S2_; final BasicXYDataset.Series S3_; final String label_; Graphs(BasicXYDataset.Series s1, BasicXYDataset.Series s2, BasicXYDataset.Series s3, String label) { S1_ = s1; S2_ = s2; S3_ = s3; label_ = label; } int getMaxElements() { int elements = S1_.getItemCount(); if (elements < S2_.getItemCount()) { elements = S2_.getItemCount(); } if (elements < S3_.getItemCount()) { elements = S3_.getItemCount(); } return elements; } } static class MyTickUnit extends NumberTickUnit { private List<String> names; public MyTickUnit(List<String> names) { super(1); this.names = names; } @Override public String valueToString(double value) { if (value < 0 || value >= names.size()) { return ""; } return names.get((int) value); } } private class CopyDetailAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { if (chartPanel.getChart().equals(detailChart) && detailChart != null && selectedGraph != null) { BasicXYDataset.Series trueData = selectedGraph.S1_; BasicXYDataset.Series fcts = selectedGraph.S2_; BasicXYDataset.Series arima = selectedGraph.S3_; int trueCount = trueData == null ? 0 : trueData.getItemCount(); int fctsCount = fcts == null ? 0 : fcts.getItemCount(); int arimaCount = arima == null ? 0 : arima.getItemCount(); int maxItems = Math.max(arimaCount, Math.max(trueCount, fctsCount)); Table t = new Table(maxItems + 2, 6); t.set(0, 2, selectedGraph.label_); t.set(1, 0, "Horizons"); t.set(1, 1, "True data"); t.set(1, 2, "Horizons"); t.set(1, 3, "Forecasts"); t.set(1, 4, "Horizons"); t.set(1, 5, "Arima"); for (int i = 0; i < trueCount; i++) { t.set((i + 2), 0, trueData.getXValue(i)); t.set((i + 2), 1, trueData.getYValue(i)); } for (int i = 0; i < fctsCount; i++) { t.set((i + 2), 2, fcts.getXValue(i)); t.set((i + 2), 3, fcts.getYValue(i)); } for (int i = 0; i < arimaCount; i++) { t.set((i + 2), 4, arima.getXValue(i)); t.set((i + 2), 5, arima.getYValue(i)); } Toolkit.getDefaultToolkit().getSystemClipboard() .setContents(TssTransferSupport.getDefault().fromTable(t), null); } } } }