/* * Copyright (c) 2011 Pentaho Corporation. All rights reserved. * This software was developed by Pentaho Corporation and is provided under the terms * of the GNU Lesser General Public License, Version 2.1. You may not use * this file except in compliance with the license. If you need a copy of the license, * please go to http://www.gnu.org/licenses/lgpl-2.1.txt. The Original Code is Time Series * Forecasting. The Initial Developer is Pentaho Corporation. * * Software distributed under the GNU Lesser Public License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. Please refer to * the license for the specific language governing your rights and limitations. */ /* * TimeSeriesForecastingCustomizer.java * Copyright (C) 2011 Pentaho Corporation * */ package weka.gui.beans; import java.awt.BorderLayout; import java.awt.Font; import java.awt.GridLayout; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.Customizer; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.ObjectInputStream; import java.util.ArrayList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.SwingConstants; import weka.classifiers.timeseries.WekaForecaster; import weka.core.Environment; import weka.core.Instances; import weka.gui.PropertySheetPanel; /** * Customizer for the TimeSeriesForecasting bean * * @author Mark Hall (mhall{[at]}pentaho{[dot]}com) * @version $Revision: 49983 $ * */ public class TimeSeriesForecastingCustomizer extends JPanel implements Customizer, CustomizerClosingListener, CustomizerCloseRequester { /** * For serialization */ private static final long serialVersionUID = -8638861579301145591L; /** The forecaster to customize */ protected TimeSeriesForecasting m_forecaster = null; /** The underlying WekaForecaster */ protected WekaForecaster m_forecastingModel = null; /** The header of the data used to train the forecaster */ protected Instances m_header; /** Handles the text field and file browser */ protected FileEnvironmentField m_filenameField = new FileEnvironmentField(); /** Label for the num steps field */ protected JLabel m_numStepsLab; /** Number of steps to forecast */ protected EnvironmentField m_numStepsToForecast = new EnvironmentField(); /** Label for the artificial time stamp offset fields */ protected JLabel m_artificialLab; /** * Number of steps beyond the end of the training data that incoming * historical priming data is */ protected EnvironmentField m_artificialOffset = new EnvironmentField(); /** Rebuild the forecaster ? */ protected JCheckBox m_rebuildForecasterCheck = new JCheckBox(); /** Label for the save forecaster field */ protected JLabel m_saveLab; /** Text field for the filename to save the forecaster to */ protected FileEnvironmentField m_saveFilenameField = new FileEnvironmentField(); /** The text area to display the model in */ protected JTextArea m_modelDisplay = new JTextArea(20, 60); /** Property sheet panel used to get the "About" panel for global info */ protected PropertySheetPanel m_sheetPanel = new PropertySheetPanel(); /** Environment variables to use */ protected transient Environment m_env = Environment.getSystemWide(); /** Frame containing us */ protected Window m_parentWindow; /** * Constructor */ public TimeSeriesForecastingCustomizer() { setLayout(new BorderLayout()); m_filenameField.addPropertyChangeListener(new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { // first check if the field is blank (i.e. been cleared) and // we have a loaded model - if so then just return. We'll // encode the forecaster to base 64 and clear the filename // field when the customizer closes. if (TimeSeriesForecasting.isEmpty(m_filenameField.getText())) { return; } loadModel(); if (m_forecastingModel != null) { m_modelDisplay.setText(m_forecastingModel.toString()); checkIfModelIsUsingArtificialTimeStamp(); checkIfModelIsUsingOverlayData(); } } }); } /** * Set the object to edit * * @param object the object to edit */ public void setObject(Object object) { setupLayout(); m_forecaster = (TimeSeriesForecasting)object; m_sheetPanel.setTarget(m_forecaster); String loadFilename = m_forecaster.getFilename(); if (!TimeSeriesForecasting.isEmpty(loadFilename) && !loadFilename.equals("-NONE-")) { m_filenameField.setText(loadFilename); loadModel(); } else { String encodedForecaster = m_forecaster.getEncodedForecaster(); if (!TimeSeriesForecasting.isEmpty(encodedForecaster) && !encodedForecaster.equals("-NONE-")) { try { List<Object> model = TimeSeriesForecasting.getForecaster(encodedForecaster); if (model != null) { m_forecastingModel = (WekaForecaster)model.get(0); m_header = (Instances)model.get(1); m_modelDisplay.setText(m_forecastingModel.toString()); } } catch (Exception ex) { ex.printStackTrace(); } } } if (!TimeSeriesForecasting.isEmpty(m_forecaster.getSaveFilename())) { m_saveFilenameField.setText(m_forecaster.getSaveFilename()); } m_numStepsToForecast.setText(m_forecaster.getNumStepsToForecast()); m_artificialOffset.setText(m_forecaster.getArtificialTimeStartOffset()); m_rebuildForecasterCheck.setSelected(m_forecaster.getRebuildForecaster()); m_saveLab.setEnabled(m_rebuildForecasterCheck.isSelected()); m_saveFilenameField.setEnabled(m_rebuildForecasterCheck.isSelected()); checkIfModelIsUsingArtificialTimeStamp(); checkIfModelIsUsingOverlayData(); } private void checkIfModelIsUsingArtificialTimeStamp() { if (m_forecastingModel != null) { boolean usingA = m_forecastingModel.getTSLagMaker().isUsingAnArtificialTimeIndex(); m_artificialLab.setEnabled(usingA); m_artificialOffset.setEnabled(usingA); } } private void checkIfModelIsUsingOverlayData() { if (m_forecastingModel != null) { if (m_forecastingModel.isUsingOverlayData()) { m_numStepsToForecast.setEnabled(false); m_numStepsLab.setEnabled(false); // remove any number set here since size of the overlay data (with missing // targets set) determines the number of steps that will be forecast m_numStepsToForecast.setText(""); } else { m_numStepsToForecast.setEnabled(true); m_numStepsLab.setEnabled(true); } } } private void setupLayout() { removeAll(); JTabbedPane tabHolder = new JTabbedPane(); JPanel modelFilePanel = new JPanel(); modelFilePanel.setLayout(new BorderLayout()); JPanel tempP1 = new JPanel(); tempP1.setLayout(new GridLayout(5, 2)); JLabel fileLab = new JLabel("Load/import forecaster", SwingConstants.RIGHT); tempP1.add(fileLab); tempP1.add(m_filenameField); m_numStepsLab = new JLabel("Number of steps to forecast", SwingConstants.RIGHT); tempP1.add(m_numStepsLab); tempP1.add(m_numStepsToForecast); m_artificialLab = new JLabel("Number of historical instances " + "beyond end of training data", SwingConstants.RIGHT); tempP1.add(m_artificialLab); tempP1.add(m_artificialOffset); JLabel rebuildLab = new JLabel("Rebuild/reestimate on incoming data", SwingConstants.RIGHT); tempP1.add(rebuildLab); tempP1.add(m_rebuildForecasterCheck); m_saveLab = new JLabel("Save forecaster", SwingConstants.RIGHT); tempP1.add(m_saveLab); tempP1.add(m_saveFilenameField); modelFilePanel.add(tempP1, BorderLayout.NORTH); tabHolder.addTab("Model file", modelFilePanel); add(tabHolder, BorderLayout.CENTER); JPanel modelPanel = new JPanel(); modelPanel.setLayout(new BorderLayout()); m_modelDisplay.setEditable(false); m_modelDisplay.setFont(new Font("Monospaced", Font.PLAIN, 12)); m_modelDisplay.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); JScrollPane scrollPane = new JScrollPane(m_modelDisplay); modelPanel.add(scrollPane, BorderLayout.CENTER); tabHolder.addTab("Model", modelPanel); JButton okBut = new JButton("OK"); JButton cancelBut = new JButton("Cancel"); JPanel butHolder1 = new JPanel(); butHolder1.setLayout(new GridLayout(1, 2)); butHolder1.add(okBut); butHolder1.add(cancelBut); JPanel butHolder2 = new JPanel(); butHolder2.setLayout(new GridLayout(1, 3)); butHolder2.add(new JPanel()); butHolder2.add(butHolder1); butHolder2.add(new JPanel()); add(butHolder2, BorderLayout.SOUTH); cancelBut.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // just close the dialog m_parentWindow.dispose(); } }); okBut.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { // apply the changes customizerClosing(); m_parentWindow.dispose(); } }); m_saveLab.setEnabled(false); m_saveFilenameField.setEnabled(false); m_rebuildForecasterCheck.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { m_saveFilenameField.setEnabled(m_rebuildForecasterCheck.isSelected()); m_saveLab.setEnabled(m_rebuildForecasterCheck.isSelected()); } }); } private void loadModel() { if (!TimeSeriesForecasting.isEmpty(m_filenameField.getText())) { try { String filename = m_filenameField.getText(); try { if (m_env == null) { m_env = Environment.getSystemWide(); } filename = m_env.substitute(filename); } catch (Exception ex) { // Quietly ignore any problems with environment variables. // A variable might not be set now, but could be when the // component is executed at a later time } File theFile = new File(filename); if (theFile.isFile()) { ObjectInputStream is = new ObjectInputStream(new BufferedInputStream(new FileInputStream(filename))); m_forecastingModel = (WekaForecaster)is.readObject(); m_header = (Instances)is.readObject(); // Instances header = (Instances)is.readObject(); is.close(); } } catch (Exception ex) { ex.printStackTrace(); } } } /** * Set the environment variables to use. * * @param env the environment variables to use */ public void setEnvironment(Environment env) { m_env = env; m_filenameField.setEnvironment(env); m_numStepsToForecast.setEnvironment(env); m_artificialOffset.setEnvironment(env); m_saveFilenameField.setEnvironment(env); } /** * Called when the window containing the customizer is closed */ public void customizerClosing() { if (m_forecaster != null) { if (!TimeSeriesForecasting.isEmpty(m_filenameField.getText())) { m_forecaster.setFilename(m_filenameField.getText()); // clear base64 field with -NONE- m_forecaster.setEncodedForecaster("-NONE-"); } else { if (m_forecastingModel != null) { // set base64 field and clear filename field with -NONE- try { String encodedModel = TimeSeriesForecasting.encodeForecasterToBase64(m_forecastingModel, m_header); m_forecaster.setFilename("-NONE-"); m_forecaster.setEncodedForecaster(encodedModel); } catch (Exception ex) { ex.printStackTrace(); } } } m_forecaster.setRebuildForecaster(m_rebuildForecasterCheck.isSelected()); m_forecaster.setNumStepsToForecast(m_numStepsToForecast.getText()); m_forecaster.setArtificialTimeStartOffset(m_artificialOffset.getText()); if (m_rebuildForecasterCheck.isSelected() && !TimeSeriesForecasting.isEmpty(m_saveFilenameField.getText())) { m_forecaster.setSaveFilename(m_saveFilenameField.getText()); } else { m_forecaster.setSaveFilename(""); } } } /** * Set the Window that contains this customizer * * @param parent the parent Window */ public void setParentWindow(Window parent) { m_parentWindow = parent; } }