/* * ------------------------------------------------------------------------- * $Id: Forecasting.java,v 1.4 2005/03/14 14:55:08 estewart Exp $ * ------------------------------------------------------------------------- * Copyright (c) 1999 Visual Numerics Inc. All Rights Reserved. * * This software is confidential information which is proprietary to * and a trade secret of Visual Numerics, Inc. Use, duplication or * disclosure is subject to the terms of an appropriate license * agreement. * * VISUAL NUMERICS MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE * SUITABILITY OF THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. VISUAL * NUMERICS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR * ITS DERIVATIVES. *-------------------------------------------------------------------------- */ package com.imsl.demo.forecast; import javax.swing.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.util.*; import java.text.*; import com.imsl.stat.*; import com.imsl.chart.*; import com.imsl.demo.gallery.Describe; public class Forecasting extends JFrameChart implements ActionListener, ChangeListener, MouseListener, MouseMotionListener { private Chart chart; private AxisXY axis; private Data data, forecastData, upperLimit, lowerLimit; private JSlider slider; private JTextField predField, probField, displayField; private JRadioButtonMenuItem[] jRadioButtons; private final int numData = 3; private final String dataName[] = {"Oil", "Sales", "Sunspots"}; private boolean toForecast, zoom, iszoom; private double[] from, to, current; private ARMA arma; private SimpleDateFormat dateFormat = new SimpleDateFormat("M/yyyy"); private double[] y, x, yRange; private int[] yearRange; private String yTitle; private int dataIncrement; public Forecasting(boolean exitOnClose) { setTitle("ARMA Forecasting"); if (!exitOnClose) { // remove the WindowListener, installed by JFrameChart, that // exits the application when the window is closed. Object l[] = getListeners(java.awt.event.WindowListener.class); for (int k = 0; k < l.length; k++) { removeWindowListener((java.awt.event.WindowListener)l[k]); } } Describe des = new Describe(this, "/com/imsl/demo/forecast/Forecasting.html"); des.show(); Dimension ds = des.getSize(); Dimension ss = getToolkit().getScreenSize(); int h = Math.min(ss.width/2, ss.height-ds.height-32); int w = (int)(h/0.8); setSize(w, h); setLocation(ss.width-ds.width, ds.height); // Set default values. toForecast = zoom = false; chart = getChart(); axis = new AxisXY(chart); axis.getAxisX().getAxisLabel().setTextFormat(dateFormat); axis.getAxisX().getAxisLabel().setTextAngle(90); axis.getAxisX().getAxisTitle().setTitle("Date"); axis.getAxisY().setTextFormat(new java.text.DecimalFormat("###,###")); from = new double[2]; to = new double[2]; current = new double[2]; // Add MouseListener and MouseMotionListener to chart panel. chart.getLegend().setPaint(true); getPanel().addMouseListener(this); getPanel().addMouseMotionListener(this); Container cp = getContentPane(); JButton draw = new JButton("Forecast"); draw.addActionListener(this); JButton reset = new JButton("Reset"); reset.addActionListener(this); JLabel prob = new JLabel("Confidence", JLabel.CENTER); probField = new JTextField("0.50", 4); slider = new JSlider(1, 12, 6); slider.setPaintTicks(true); slider.setPaintLabels(true); slider.setMajorTickSpacing(1); slider.setMinorTickSpacing(1); slider.setSnapToTicks(true); slider.addChangeListener(this); JLabel pred = new JLabel("Predicts", JLabel.CENTER); predField = new JTextField("6", 3); predField.setEditable(false); JPanel buttonPanel = new JPanel(); buttonPanel.add(draw); buttonPanel.add(prob); buttonPanel.add(probField); buttonPanel.add(slider); buttonPanel.add(pred); buttonPanel.add(predField); buttonPanel.add(reset); // Create the display panel JPanel displayPanel = new JPanel(); displayField = new JTextField("Current position: X = ?? Y = ??", 53); displayField.setBorder(null); displayField.setCaretPosition(0); displayField.setEditable(false); displayPanel.add(displayField); cp.add(buttonPanel, BorderLayout.NORTH); cp.add(displayPanel, BorderLayout.SOUTH); JMenuBar jMenu = this.getJMenuBar(); JMenu menuData = new JMenu(); menuData.setMnemonic('D'); menuData.setText("Data"); jMenu.add(menuData); jRadioButtons = new javax.swing.JRadioButtonMenuItem[numData]; ButtonGroup group = new ButtonGroup(); for (int i=0; i<numData; i++) { jRadioButtons[i] = new javax.swing.JRadioButtonMenuItem(); jRadioButtons[i].setText(dataName[i]); jRadioButtons[i].addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { toForecast = false; zoom = false; iszoom = false; update(); } }); group.add(jRadioButtons[i]); menuData.add(jRadioButtons[i]); } jRadioButtons[1].setSelected(true); getData(); drawGraph(); setResizable(false); } // Get the description line. private String getDescription() { DecimalFormat nf = new DecimalFormat("0.0000"); StringBuffer sb = new StringBuffer(); if (iszoom) { sb.append("Zooming In from: "); sb.append("X = " + dateFormat.format(new Date((long) from[0])) + " Y = " + nf.format(from[1])); sb.append(" To: "); sb.append("X = " + dateFormat.format(new Date((long) to[0])) + " Y = " + nf.format(to[1])); } else { sb.append("Cursor At: X = " + dateFormat.format(new Date((long) current[0])) + " Y = " + nf.format(current[1])); } return sb.toString(); } // load up the selected data set private void getData() { int selected = -1; for (int i=0; i<numData; i++) { if (jRadioButtons[i].isSelected()) { selected = i; break; } } switch (selected) { case 2: // sunspot data y = new double[] {100.8, 81.6, 66.5, 34.8, 30.6, 7, 19.8, 92.5, 154.4, 125.9, 84.8, 68.1, 38.5, 22.8, 10.2, 24.1, 82.9, 132, 130.9, 118.1, 89.9, 66.6, 60, 46.9, 41, 21.3, 16, 6.4, 4.1, 6.8, 14.5, 34, 45, 43.1, 47.5, 42.2, 28.1, 10.1, 8.1, 2.5, 0, 1.4, 5, 12.2, 13.9, 35.4, 45.8, 41.1, 30.4, 23.9, 15.7, 6.6, 4, 1.8, 8.5, 16.6, 36.3, 49.7, 62.5, 67, 71, 47.8, 27.5, 8.5, 13.2, 56.9, 121.5, 138.3, 103.2, 85.8, 63.2, 36.8, 24.2, 10.7, 15, 40.1, 61.5, 98.5, 124.3, 95.9, 66.5, 64.5, 54.2, 39, 20.6, 6.7, 4.3, 22.8, 54.8, 93.8, 95.7, 77.2, 59.1, 44, 47, 30.5, 16.3, 7.3, 37.3, 73.9}; yearRange = new int[] {1770, 1870, 1910}; yRange = new double[] {-50, 200}; dataIncrement = GregorianCalendar.YEAR; yTitle = "Number of Sun Spots per Year"; break; case 1: // champagne sales data y = new double[] {2815, 2672, 2755, 2721, 2946, 3036, 2282, 2212, 2922, 4301, 5764, 7132, 2541, 2475, 3031, 3266, 3776, 3230, 3028, 1759, 3595, 4474, 6838, 8357, 3113, 3006, 4047, 3523, 3937, 3986, 3260, 1573, 3528, 5211, 7614, 9254, 5375, 3088, 3718, 4514, 4520, 4539, 3663, 1643, 4739, 5428, 8314, 10651, 3633, 4292, 4154, 4121, 4647, 4753, 3965, 1723, 5048, 6922, 9858, 11331, 4016, 3957, 4510, 4276, 4968, 4677, 3523, 1821, 5222, 6873, 10803, 13916, 2639, 2899, 3370, 3740, 2927, 3986, 4217, 1738, 5221, 6424, 9842, 13076, 3934, 3162, 4286, 4676, 5010, 4874, 4633, 1659, 5951, 6981, 9851, 12670}; yearRange = new int[] {1964, 1972, 1975}; yRange = new double[] {0, 14000}; dataIncrement = GregorianCalendar.MONTH; yTitle = "Cases of Champagne Sold per Month"; break; case 0: // west texas crude oil price y = new double[] {23,15.4,12.6,12.8,15.4,13.5,11.5,15,14.9,14.9, 15.1,16.1,18.6,17.7,18.3,18.6,19.4,20,21.3,20.2,19.5,19.8,19, 17.2,17.1,16.7,16.2,17.8,17.4,16.5,15.5,15.5,14.4,13.8,13.9, 16.2,17.9,17.8,19.4,21,20,20,19.6,18.5,19.5,20,19.8,21,22.6, 22.1,20.4,18.5,18.2,16.8,18.6,27.1,33.6,36,32.3,27.3,24.9,20.5, 19.8,20.8,21.2,20.2,21.4,21.6,21.8,23.2,22.4,19.5,18.8,19,18.9, 20.2,20.9,22.3,21.7,21.3,21.9,21.6,20.3,19.4,19,20,20.3,20.2, 19.9,19,17.8,18,17.5,18.1,16.6,14.5,15,14.7,14.6,16.3,17.8,19, 19.6,18.3,17.4,17.7,18.1,17.1,17.9,18.5,18.5,19.8,19.7,18.4, 17.3,18,18.2,17.4,17.9,19,18.8,19,21.3,23.5,21.2,20.4,21.3,21.9, 23.9,24.9,23.7,25.3,25.1,22.2,20.9,19.7,20.8,19.1,19.6,19.9, 19.7,21.2,20.1,18.3,16.7,16,15,15.4,14.8,13.6,14,13.3,14.9,14.3, 12.9,11.2,12.4,12,14.6,17.3,17.7,17.8,20,21.2,23.8,22.6,24.8, 26.1,27.2,29.3,29.8,25.7,28.7,31.8,29.7,31.2,33.8,33,34.4,28.4, 29.5,29.6,27.2,27.4,28.6,27.6,26.4,27.4,25.8,22.2,19.6,19.3, 19.6,20.7,24.4,26.2,27,25.5,26.9,28.3,29.6,28.8,26.2}; yearRange = new int[] {1986, 2003, 2006}; yRange = new double[] {0, 50}; dataIncrement = GregorianCalendar.MONTH; yTitle = "West Texas Crude Oil, Dollars per Barrel"; break; } x = new double[y.length]; for (int i = 0; i < y.length; i++) { switch (dataIncrement) { case GregorianCalendar.YEAR: x[i] = (new GregorianCalendar(yearRange[0]+i, 0, 1)).getTimeInMillis(); break; case GregorianCalendar.MONTH: x[i] = (new GregorianCalendar(yearRange[0], i, 1)).getTimeInMillis(); break; } } } // Draw the graph. private void drawGraph() { // If in zooming mode, then zoom the graph. Otherwise, use default ranges. if (zoom) { axis.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF); axis.getAxisY().setAutoscaleInput(AxisXY.AUTOSCALE_OFF); axis.getAxisX().setWindow(Math.min(from[0], to[0]), Math.max(from[0], to[0])); axis.getAxisY().setWindow(Math.min(from[1], to[1]), Math.max(from[1], to[1])); } else { axis.getAxisX().setAutoscaleInput(AxisXY.AUTOSCALE_OFF); axis.getAxisX().setWindow((new GregorianCalendar(yearRange[0], 0, 1)).getTimeInMillis(), (new GregorianCalendar(yearRange[2], 0, 1)).getTimeInMillis()); axis.getAxisY().setAutoscaleInput(AxisXY.AUTOSCALE_OFF); axis.getAxisY().setWindow(yRange); } axis.getAxisY().getAxisTitle().setTitle(yTitle); // Draw the actual data. data = new Data(axis, x, y); data.setTitle("Actual data"); data.setDataType(Data.DATA_TYPE_LINE | Data.DATA_TYPE_MARKER); data.setMarkerType(Data.MARKER_TYPE_FILLED_CIRCLE); data.setMarkerSize(0.7); data.setMarkerColor(Color.blue); data.setLineColor(Color.blue); // Use ARMA class (ARMA(2,1)) to get the forecast using backorigin = 3. // Number of predicts and confidence level are from the interface. if (toForecast) { try { int num = slider.getValue(); // Do this if Forecast is clicked. if (arma == null) { arma = new ARMA(2, 1, y); arma.setRelativeError(0.0); arma.setMaxIterations(0); arma.compute(); arma.setConfidence(Double.parseDouble(probField.getText())); arma.setBackwardOrigin(3); } double[][] forecast = arma.forecast(num); double[] tmp = new double[num]; double[] x1 = new double[num]; // Draw the forecast data. for (int i = 0; i < num; i++) { tmp[i] = forecast[i][3]; switch (dataIncrement) { case GregorianCalendar.YEAR: x1[i] = (new GregorianCalendar(yearRange[1]+i, 0, 1)).getTimeInMillis(); break; case GregorianCalendar.MONTH: x1[i] = (new GregorianCalendar(yearRange[1], i, 1)).getTimeInMillis(); break; } } // Draw upper and lower limits. double[] dev = arma.getDeviations(); double[] ul = new double[dev.length]; double[] ll = new double[dev.length]; double min = 0.0; for (int i = 0; i < dev.length; i++) { ul[i] = tmp[i] + dev[i]; ll[i] = tmp[i] - dev[i]; if (i == 0) min = ll[0]; if (min > ll[i]) min = ll[i]; } min = min - 0.01; upperLimit = new Data(axis, x1, ul); upperLimit.setTitle("Upper Limit"); upperLimit.setDataType(Data.DATA_TYPE_LINE | Data.DATA_TYPE_FILL); upperLimit.setReference(min); upperLimit.setFillColor(Color.yellow); upperLimit.setFillOutlineColor(Color.white); lowerLimit = new Data(axis, x1, ll); lowerLimit.setTitle("Lower Limit"); lowerLimit.setDataType(Data.DATA_TYPE_LINE | Data.DATA_TYPE_FILL); lowerLimit.setReference(min); lowerLimit.setFillColor(Color.white); lowerLimit.setFillOutlineColor(Color.white); forecastData = new Data(axis, x1, tmp); forecastData.setTitle("Forecast"); forecastData.setDataType(Data.DATA_TYPE_LINE); forecastData.setLineColor(Color.red); } catch (Exception e) { } } } // Get the information from the interface and redraw the chart. private void update() { double tmp = 0.0; // Check the range for the confidence level. Use 0.50 if there is // an error in the input. try { tmp = Double.parseDouble(probField.getText()); } catch (Exception exception) { tmp = 0.50; probField.setText("0.50"); } finally { if (data != null) data.remove(); if (forecastData != null) forecastData.remove(); if (upperLimit != null) upperLimit.remove(); if (lowerLimit != null) lowerLimit.remove(); if ((tmp <= 0.0) || (tmp >= 1.0)) probField.setText("0.50"); getData(); drawGraph(); repaint(); } } // Implement ActionListener public void actionPerformed(ActionEvent e) { // If Reset is clicked, re-size the chart using autoscale. // If Draw is clicked, redraw the chart. if (e.getActionCommand().equals("Reset")) { zoom = false; } else { toForecast = true; arma = null; } update(); } // Implement ChangeListener public void stateChanged(ChangeEvent e) { // Update text field for number of predicts. predField.setText(Integer.toString(slider.getValue())); } // Implement MouseListener public void mouseClicked(MouseEvent e) { } public void mousePressed(MouseEvent e) { // Get the starting position for zooming. axis.mapDeviceToUser(e.getX(), e.getY(), from); axis.mapDeviceToUser(e.getX(), e.getY(), to); zoom = true; iszoom = true; } public void mouseReleased(MouseEvent e) { int x = e.getX(); int y = e.getY(); // The selection must be from 2 different points. int[] point = new int[2]; axis.mapUserToDevice(from[0], from[1], point); if (point[0] == x) return; if (point[1] == y) return; // Get the ending position for zooming. axis.mapDeviceToUser(x, y, to); iszoom = false; update(); } public void mouseEntered(MouseEvent e) { setCursor(new java.awt.Cursor(java.awt.Cursor.CROSSHAIR_CURSOR)); } public void mouseExited(MouseEvent e) { setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); } // Implement MouseMotionListener public void mouseDragged(MouseEvent e) { // Draw the selection square and update display information. paint(e, true); displayField.setText(getDescription()); } public void mouseMoved(MouseEvent e) { // Update display information. axis.mapDeviceToUser(e.getX(), e.getY(), current); displayField.setText(getDescription()); } // Draw the selection in different color. protected void paint(MouseEvent mouseEvent, boolean erase) { Graphics2D g = (Graphics2D) Forecasting.this.getPanel().getGraphics(); if (erase) paint(g); axis.mapDeviceToUser(mouseEvent.getX(), mouseEvent.getY(), to); paint(g); g.dispose(); } protected void paint(Graphics2D g) { Color colorFill = new Color(255,0,255,128); Color colorOutline = new Color(0,255,0); int[] pointA = new int[2]; int[] pointB = new int[2]; axis.mapUserToDevice(from[0], from[1], pointA); axis.mapUserToDevice(to[0], to[1], pointB); Shape shape = new Rectangle(Math.min(pointA[0], pointB[0]), Math.min(pointA[1], pointB[1]), Math.abs(pointA[0]-pointB[0]), Math.abs(pointA[1]-pointB[1])); g.setXORMode(colorOutline); g.draw(shape); g.setXORMode(colorFill); g.fill(shape); } public static void main(String args[]) { boolean exitOnClose = true; if (args.length > 0 && args[0].equals("-noexit")) exitOnClose = false; new Forecasting(exitOnClose).show(); } }