/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.tools;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.MouseEvent;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import org.opensourcephysics.display.CellBorder;
/**
* This displays statistics of data columns in a DataToolTable.
*
* @author Douglas Brown
* @version 1.0
*/
public class DataToolStatsTable extends JTable {
// instance fields
DataToolTable dataTable;
StatsTableModel statsModel;
DataToolTable.LabelRenderer labelRenderer;
NumberRenderer numberRenderer = new NumberRenderer(3);
Object[][] statsData; // statsData[col] contains stats for a table column
/**
* Constructor.
*
* @param table the datatable
*/
public DataToolStatsTable(DataToolTable table) {
dataTable = table;
statsModel = new StatsTableModel();
// add mouse listeners for table
addMouseMotionListener(new MouseInputAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
int col = columnAtPoint(e.getPoint());
int labelCol = convertColumnIndexToView(0);
if(col==labelCol) {
setToolTipText(null);
}
else {
int row = rowAtPoint(e.getPoint());
Object val = getValueAt(row, col);
Object stat = getValueAt(row, labelCol);
String name = dataTable.getColumnName(col);
setToolTipText(stat+"_"+name+" = "+val); //$NON-NLS-1$ //$NON-NLS-2$
}
}
});
addMouseListener(new MouseInputAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
dataTable.dataToolTab.refreshStatusBar(dataTable.dataToolTab.getCorrelationString());
}
});
init();
}
/**
* Initializes the table.
*/
protected void init() {
dataTable.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
public void columnAdded(TableColumnModelEvent e) {
/** empty block */
}
public void columnRemoved(TableColumnModelEvent e) {
/** empty block */
}
public void columnSelectionChanged(ListSelectionEvent e) {
/** empty block */
}
public void columnMarginChanged(ChangeEvent e) {
refreshTable();
}
public void columnMoved(TableColumnModelEvent e) {
refreshTable();
}
});
// create statistics data array
refreshStatistics();
// set and configure table model and header
setModel(statsModel);
setGridColor(Color.blue);
setTableHeader(null); // no table header
labelRenderer = dataTable.labelRenderer;
setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
ListSelectionModel selectionModel = dataTable.getSelectionModel();
selectionModel.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
// workaround to prevent exceptions
if(e.getFirstIndex()>-1) {
refreshStatistics();
}
}
});
refreshCellWidths();
}
/**
* Returns labels that describe the statistics.
*
* @return labels for max, min, mean, SD, SE and data count
*/
private String[] getStatLabels() {
return new String[] {ToolsRes.getString("Table.Entry.Max"), //$NON-NLS-1$
ToolsRes.getString("Table.Entry.Min"), //$NON-NLS-1$
ToolsRes.getString("Table.Entry.Mean"), //$NON-NLS-1$
ToolsRes.getString("Table.Entry.StandardDev"), //$NON-NLS-1$
ToolsRes.getString("Table.Entry.StandardError"), //$NON-NLS-1$
ToolsRes.getString("Table.Entry.Count")}; //$NON-NLS-1$
}
/**
* Calculates statistical values for a data array.
*
* @param data the data array
* @return the max, min, mean, SD, SE and non-NaN data count
*/
private Object[] getStatistics(double[] data) {
double max = -Double.MAX_VALUE;
double min = Double.MAX_VALUE;
double sum = 0.0;
double squareSum = 0.0;
int count = 0;
for(int i = 0; i<data.length; i++) {
if(Double.isNaN(data[i])) {
continue;
}
count++;
max = Math.max(max, data[i]);
min = Math.min(min, data[i]);
sum += data[i];
squareSum += data[i]*data[i];
}
double mean = sum/count;
double sd = (count<2) ? Double.NaN : Math.sqrt((squareSum-count*mean*mean)/(count-1));
if(max==-Double.MAX_VALUE) {
max = Double.NaN;
}
if(min==Double.MAX_VALUE) {
min = Double.NaN;
}
return new Object[] {new Double(max), new Double(min), new Double(mean), new Double(sd), new Double(sd/Math.sqrt(count)), new Integer(count)};
}
/**
* Refresh the data display in this table.
*/
public void refreshTable() {
Runnable refresh = new Runnable() {
public synchronized void run() {
tableChanged(new TableModelEvent(statsModel, TableModelEvent.HEADER_ROW));
refreshCellWidths();
}
};
if(SwingUtilities.isEventDispatchThread()) {
refresh.run();
} else {
SwingUtilities.invokeLater(refresh);
}
}
/**
* Refresh the statistics data.
*/
public void refreshStatistics() {
// assemble statistics data array
TableModel model = dataTable.getModel();
int[] rows = dataTable.getSelectedRows();
int[] cols = dataTable.getSelectedColumns();
statsData = new Object[model.getColumnCount()][0];
ArrayList<Double> datalist = new ArrayList<Double>();
// for each column, assemble valid selected data and get stats
statsData[0] = getStatLabels();
for(int j = 1; j<model.getColumnCount(); j++) {
datalist.clear();
for(int i = 0; i<model.getRowCount(); i++) {
Double val = (Double) model.getValueAt(i, j);
if(val==null) {
val = new Double(Double.NaN);
}
datalist.add(val);
}
double[] x = new double[datalist.size()];
for(int i = 0; i<x.length; i++) {
x[i] = datalist.get(i).doubleValue();
}
double[] selected = x;
if(rows.length>0) {
// is column selected?
boolean colSelected = false;
int col = dataTable.convertColumnIndexToView(j);
for(int k = 0; k<cols.length; k++) {
colSelected = colSelected||(col==cols[k]);
}
if(colSelected) {
selected = new double[rows.length];
for(int i = 0; i<rows.length; i++) {
selected[i] = x[rows[i]];
}
}
}
statsData[j] = getStatistics(selected);
}
refreshTable();
}
/**
* Refresh the cell widths in the table.
*/
public void refreshCellWidths() {
// set width of columns
if(getColumnCount()!=dataTable.getColumnCount()) {
return;
}
for(int i = 0; i<getColumnCount(); i++) {
TableColumn propColumn = getColumnModel().getColumn(i);
TableColumn dataColumn = dataTable.getColumnModel().getColumn(i);
propColumn.setMaxWidth(dataColumn.getWidth());
propColumn.setMinWidth(dataColumn.getWidth());
propColumn.setWidth(dataColumn.getWidth());
}
}
/**
* Refresh the GUI.
*/
public void refreshGUI() {
refreshStatistics();
}
/**
* Returns the renderer for a cell specified by row and column.
*
* @param row the row number
* @param column the column number
* @return the cell renderer
*/
public TableCellRenderer getCellRenderer(int row, int column) {
int i = dataTable.convertColumnIndexToModel(column);
if(i==0) {
return labelRenderer;
}
return numberRenderer;
}
public void setFont(Font font) {
super.setFont(font);
if(numberRenderer!=null) {
numberRenderer.font = font;
}
setRowHeight(font.getSize()+4);
}
/**
* A class to provide model data for this table.
*/
class StatsTableModel extends AbstractTableModel {
public String getColumnName(int col) {
return dataTable.getColumnName(col);
}
public int getRowCount() {
return statsData[0].length;
}
public int getColumnCount() {
return dataTable.getModel().getColumnCount();
}
public Object getValueAt(int row, int col) {
int i = dataTable.convertColumnIndexToModel(col);
return statsData[i][row];
}
public boolean isCellEditable(int row, int col) {
return false;
}
public Class<?> getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
}
/**
* A class to render numbers and strings.
*/
class NumberRenderer extends JLabel implements TableCellRenderer {
NumberFormat format = NumberFormat.getInstance();
Font font;
/**
* Constructor NumberRenderer
* @param sigfigs
*/
public NumberRenderer(int sigfigs) {
sigfigs = Math.min(sigfigs, 6);
if(format instanceof DecimalFormat) {
String pattern = "0.0"; //$NON-NLS-1$
for(int i = 0; i<sigfigs-1; i++) {
pattern += "0"; //$NON-NLS-1$
}
pattern += "E0"; //$NON-NLS-1$
((DecimalFormat) format).applyPattern(pattern);
}
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if(font==null) {
font = getDefaultRenderer(String.class).getTableCellRendererComponent(DataToolStatsTable.this, "", false, false, 0, 0).getFont(); //$NON-NLS-1$
}
setFont(font);
setHorizontalAlignment(SwingConstants.TRAILING);
setBorder(new CellBorder(new Color(240, 240, 240)));
if(value instanceof Integer) {
setText(String.valueOf(value));
} else {
setText(format.format(value));
}
return this;
}
}
}
/*
* Open Source Physics software is free software; you can redistribute
* it and/or modify it under the terms of the GNU General Public License (GPL) as
* published by the Free Software Foundation; either version 2 of the License,
* or(at your option) any later version.
* Code that uses any portion of the code in the org.opensourcephysics package
* or any subpackage (subdirectory) of this package must must also be be released
* under the GNU GPL license.
*
* This software 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA
* or view the license online at http://www.gnu.org/copyleft/gpl.html
*
* Copyright (c) 2007 The Open Source Physics project
* http://www.opensourcephysics.org
*/