/*
This file is part of the software framework JStatCom,
a library for building applications for data based analysis.
Copyright (C) 2000-2005 Markus Kraetzig
This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
*/
package com.jstatcom.ts;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import com.jstatcom.component.StdMessages;
import com.jstatcom.util.UString;
/**
* A table model to be used with the TSTable component for
* representing merged time series data. The model takes an array of time series
* with possibly different time ranges but equal sub periodicities and merges
* the series to a common time range, which includes all ranges.
*
* @author Markus Kraetzig
*/
public final class TSTableModel extends AbstractTableModel {
/**
* The array for storing the data.
*/
private Double[][] dataArray = new Double[0][0];
/**
* The array for storing the TS names.
*/
private String[] namesArray = new String[1];
/**
* The array for storing the row header.
*/
private String[] rowHeader = new String[0];
private boolean editable = true;
private TSDate startDate = null;
/**
* TSTableModel default constructor.
*/
public TSTableModel() {
super();
}
/**
* Finds the index of the row, where the given Double value
* appears first, starting from a given row index. It also gives back the
* row with the closest match for val. In case of an exact
* match, both values are the same. In case of no exact match, the first
* value is -1 and the second the index of the row containing
* the value that is closest to val.
*
* @param val
* the value to search for
* @param startIndex
* index to start from, if out of range start from 0
* @return two dimensional integer array of indices:
*
* int[0] row index of 1st exact match or
* -1 if no exact match or if
* val == null
* int[1] row index of closest match or
* -1 if val == null
*
* @throws IllegalStateException
* if (getRowCount() == 0)
*
*/
public int[] findRowIndexOf(Double val, int startIndex) {
if (getRowCount() == 0)
throw new IllegalStateException("Nothing to search for.");
if (startIndex < 0 || startIndex > getRowCount() - 1)
startIndex = 0;
if (val == null)
return new int[] { -1, -1 };
double minDiff = Double.POSITIVE_INFINITY;
int minIndex = -1;
for (int i = startIndex; i < getRowCount(); i++)
for (int j = 0; j < getColumnCount(); j++) {
double tmpDiff = Math.abs(dataArray[i][j].doubleValue()
- val.doubleValue());
if (tmpDiff == 0 || dataArray[i][j].equals(val))
return new int[] { i, i };
if (tmpDiff < minDiff) {
minDiff = tmpDiff;
minIndex = i;
}
}
return new int[] { -1, minIndex };
}
/**
* Gets column class. The method always returns String.class,
* to make sure that the string editor is choosen.
*
* @return String.class
* @param col
* does not matter
*/
public Class getColumnClass(int col) {
return String.class;
}
/**
* Gets number of displayed TS.
*
* @return column count
*/
public int getColumnCount() {
if (dataArray.length > 0)
return dataArray[0].length;
return 0;
}
/**
* Gets the name of the TS displayed in a certain column.
*
* @return time series name
* @param col
* column index
*/
public String getColumnName(int col) {
if (namesArray.length > 0)
return namesArray[col];
return null;
}
/**
* Gets underlying observation for a given row and column as a
* double value.
*
* @return the observation as double
*/
public double getDoubleValueAt(int row, int col) {
if (getRowCount() > 0 && getColumnCount() > 0)
return dataArray[row][col].doubleValue();
return Double.NaN;
}
/**
* Gets number of displayed observations in time range of all
* TS.
*
* @return row count
*/
public int getRowCount() {
return dataArray.length;
}
/**
* Gets a JViewport that can be used as row header for a
* scrollpane. This contains the time axis that can be shown with the table
* together with an index for the current row. Use the method with the
* JScrollPane that encloses a TSTable using
* this model.
*
* @param table
* the JTable to synchronize selection with
* @return JViewport to be used as a row header for a
* JScrollPane
*/
public JViewport getRowHeader(JTable table) {
AbstractTableModel rowHModel = new AbstractTableModel() {
public Object getValueAt(int row, int col) {
if (col == 0)
return rowHeader[row];
return (row + 1) + "";
}
public int getColumnCount() {
return 2;
}
public int getRowCount() {
return dataArray.length;
}
public boolean isCellEditable(int row, int col) {
return false;
}
};
JTable headerCol = new JTable(rowHModel) {
public Component prepareRenderer(TableCellRenderer renderer,
int row, int column) {
Object value = getValueAt(row, column);
boolean isSelected = isCellSelected(row, column);
boolean hasFocus = false;
return renderer.getTableCellRendererComponent(this, value,
isSelected, hasFocus, row, column);
}
};
String proto1 = rowHeader[rowHeader.length - 1];
String proto2 = rowHeader.length + "";
double sw1 = headerCol.getFontMetrics(headerCol.getFont()).stringWidth(
proto1);
double sw2 = headerCol.getFontMetrics(headerCol.getFont()).stringWidth(
proto2);
headerCol.createDefaultColumnsFromModel();
headerCol.setBackground(Color.lightGray);
headerCol.setSelectionBackground(Color.lightGray);
headerCol.setColumnSelectionAllowed(false);
headerCol.setRowSelectionAllowed(true);
headerCol.setShowGrid(true);
headerCol.setSelectionModel(table.getSelectionModel());
headerCol.getColumnModel().getColumn(0).setMinWidth(
new Double(sw1).intValue() + 15);
JViewport jv = new JViewport();
jv.setView(headerCol);
jv.setPreferredSize(new Dimension(
new Double(sw1 + sw2).intValue() + 30, 10000));
return jv;
}
/**
* Gets the TSDate where the very first observation in the
* model occurs.
*
* @return the starting date for all series in the model
*/
public TSDate getStartTSDate() {
return startDate;
}
/**
* Gets underlying observation for a given row and column.
*
* @param row
* index for row
* @param col
* index for column
* @return a Double wrapping the observation
*/
public Object getValueAt(int row, int col) {
if (getRowCount() > 0 && getColumnCount() > 0)
return dataArray[row][col];
return null;
}
/**
* Gets whether given cell can be edited. This is the same for all cells and
* set for the whole model via the editable property.
*
* @return the editable property for the whole table
* @param row
* index for row
* @param col
* index for column
*/
public boolean isCellEditable(int row, int col) {
return editable;
}
/**
* Sets the TS to be held by the model. It merges the series
* and creates the data and names arrays.
*
* @param tsArray
* an array of TS objects with the same sub
* periodicity
*/
public void setTS(TS[] tsArray) {
double[][] data = TS.getMergedData(tsArray);
namesArray = new String[tsArray.length];
for (int i = 0; i < tsArray.length; i++)
namesArray[i] = tsArray[i].name();
dataArray = new Double[data.length][data[0].length];
for (int i = 0; i < data.length; i++)
for (int j = 0; j < data[0].length; j++)
dataArray[i][j] = new Double(data[i][j]);
TSDateRange tsRange = TSDateRange.getMergedRange(tsArray);
rowHeader = tsRange.timeAxisStringArray();
startDate = tsRange.lowerBound();
fireTableDataChanged();
fireTableStructureChanged();
}
// set data
public void setArrays(Double[][] data, String[] names) {
namesArray=names;
dataArray=data;
// dataArray = new Double[data.length][data[0].length];
// for (int i = 0; i < data.length; i++)
// for (int j = 0; j < data[0].length; j++)
// dataArray[i][j] = new Double(data[i][j]);
// TSDateRange tsRange = TSDateRange.getMergedRange(tsArray);
rowHeader = namesArray;
startDate = null;
fireTableDataChanged();
fireTableStructureChanged();
}
// insert table names
public void updateTable(String[] array) {
namesArray = array;
fireTableDataChanged();
fireTableStructureChanged();
}
/**
* Sets data at a given position and displays an error message if input
* cannot be parsed to a double.
*
* @param obj
* Object that the method tries to parse to a Double
* @param row
* index for row
* @param col
* index for column
*/
public void setValueAt(Object obj, int row, int col) {
Double previousValue = (Double) getValueAt(row, col);
if ((obj instanceof Double)) {
dataArray[row][col] = (Double) obj;
if (!previousValue.equals(dataArray[row][col]))
fireTableCellUpdated(row, col);
return;
}
String text = obj.toString().trim();
Double val = UString.parseToNumber(obj.toString());
if (val == null) {
StdMessages.errorInput("Input \"" + text
+ "\" is not a valid number.\nThe previous value {"
+ previousValue + "} is restored.");
dataArray[row][col] = previousValue;
} else if (!val.equals(previousValue)) {
dataArray[row][col] = val;
fireTableCellUpdated(row, col);
}
}
}
Ads help maintain this website.