/*
* -------------------------------------------------------------------------
* $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();
}
}