/*
* 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.text.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.basic.BasicComboBoxRenderer;
import javax.swing.table.*;
import org.opensourcephysics.display.*;
import org.opensourcephysics.numerics.*;
/**
* A panel that displays and controls functional curve fits to a Dataset.
*
* @author Douglas Brown
* @version 1.0
*/
@SuppressWarnings("serial")
public class DatasetCurveFitter extends JPanel {
// static fields
/** defaultFits are available in every instance */
static ArrayList<KnownFunction> defaultFits = new ArrayList<KnownFunction>();
static JFileChooser chooser;
static NumberFormat SEFormat = NumberFormat.getInstance();
// instance fields
/** localFits contains local copies of all fits */
ArrayList<KnownFunction> localFits = new ArrayList<KnownFunction>();
/** fitMap maps localized names to all available fits */
Map<String, KnownFunction> fitMap = new TreeMap<String, KnownFunction>();
PropertyChangeListener fitListener;
Dataset dataset; // the data to be fit
KnownFunction fit; // the function to fit to the data
HessianMinimize hessian = new HessianMinimize();
LevenbergMarquardt levmar = new LevenbergMarquardt();
FunctionDrawer drawer;
Color color = Color.MAGENTA;
JButton colorButton, closeButton;
JCheckBox autofitCheckBox;
JLabel fitLabel, eqnLabel, rmsLabel;
JToolBar fitBar, eqnBar, rmsBar;
JComboBox fitDropDown;
JTextField eqnField;
NumberField rmsField;
ParamTableModel paramModel;
JTable paramTable;
ParamCellRenderer cellRenderer;
SpinCellEditor spinCellEditor; // uses number-crawler spinner
int fitNumber = 1;
JButton fitBuilderButton;
boolean refreshing = false, isActive, neverBeenActive = true;
JSplitPane splitPane;
JDialog colorDialog;
int fontLevel;
FitBuilder fitBuilder;
double correlation = Double.NaN;
double[] uncertainties = new double[2];
DataToolTab tab;
boolean fitEvaluatedToNaN = false;
static {
defaultFits.add(new KnownPolynomial(new double[] {0, 0}));
defaultFits.add(new KnownPolynomial(new double[] {0, 0, 0}));
defaultFits.add(new KnownPolynomial(new double[] {0, 0, 0, 0}));
UserFunction f = new UserFunction("Gaussian"); //$NON-NLS-1$
f.setParameters(new String[] {"A", "B", "C"}, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
new double[] {1, 0, 1},
new String[] {ToolsRes.getString("Function.Parameter.PeakHeight.Description"), //$NON-NLS-1$
ToolsRes.getString("Function.Parameter.PeakPosition.Description"), //$NON-NLS-1$
ToolsRes.getString("Function.Parameter.GaussianRMSWidth.Description")} ); //$NON-NLS-1$
f.setExpression("A*exp(-(x-B)^2/(2*C^2))", new String[] {"x"}); //$NON-NLS-1$ //$NON-NLS-2$
f.setDescription(ToolsRes.getString("Function.Gaussian.Description")); //$NON-NLS-1$
defaultFits.add(f);
f = new UserFunction("Exponential"); //$NON-NLS-1$
f.setParameters(new String[] {"A", "B"}, //$NON-NLS-1$ //$NON-NLS-2$
new double[] {1, 1},
new String[] {ToolsRes.getString("Function.Parameter.Intercept.Description"), //$NON-NLS-1$
ToolsRes.getString("Function.Parameter.ExponentialMultiplier.Description")} ); //$NON-NLS-1$
f.setExpression("A*exp(-x*B)", new String[] {"x"}); //$NON-NLS-1$ //$NON-NLS-2$
f.setDescription(ToolsRes.getString("Function.Exponential.Description")); //$NON-NLS-1$
defaultFits.add(f);
f = new UserFunction("Sinusoid"); //$NON-NLS-1$
f.setParameters(new String[] {"A", "B", "C"}, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
new double[] {1, 1, 0},
new String[] {ToolsRes.getString("Function.Parameter.Amplitude.Description"), //$NON-NLS-1$
ToolsRes.getString("Function.Parameter.Omega.Description"), //$NON-NLS-1$
ToolsRes.getString("Function.Parameter.Phase.Description")} ); //$NON-NLS-1$
f.setExpression("A*sin(B*x+C)", new String[] {"x"}); //$NON-NLS-1$ //$NON-NLS-2$
f.setDescription(ToolsRes.getString("Function.Sinusoid.Description")); //$NON-NLS-1$
defaultFits.add(f);
}
/**
* Constructs a DatasetCurveFitter for the specified Dataset.
*
* @param data the dataset
* @param builder the FitBuilder used for constructing custom fits
*/
public DatasetCurveFitter(Dataset data, FitBuilder builder) {
dataset = data;
fitBuilder = builder;
createGUI();
// fit(fit);
}
/**
* Gets the function drawer.
*
* @return the drawer
*/
public FunctionDrawer getDrawer() {
return drawer;
}
/**
* Gets the data.
*
* @return the dataset
*/
public Dataset getData() {
return dataset;
}
/**
* Sets the dataset.
*
* @param data the dataset
*/
public void setData(Dataset data) {
dataset = data;
if (isActive)
fit(fit);
if (dataset != null) {
String var = dataset.getXColumnName();
var = TeXParser.removeSubscripting(var);
fitBuilder.setDefaultVariables(new String[] {var});
if (!isActive) { // if active, regression done in fit method
// double x0 = 0, y0 = 0;
// if (tab!=null && tab.dataShiftEnabled && tab.plot!=null) {
// TPoint origin = tab.plot.origin;
// x0 = -origin.getX();
// y0 = -origin.getY();
// }
// double[] x = shiftValues(dataset.getValidXPoints(), x0);
// double[] y = shiftValues(dataset.getValidYPoints(), y0);
double[] x = dataset.getValidXPoints();
double[] y = dataset.getValidYPoints();
doLinearRegression(x, y, false);
refreshStatusBar();
}
}
}
/**
* Sets the color.
*
* @param newColor the color
*/
public void setColor(Color newColor) {
color = newColor;
if(drawer!=null) {
drawer.setColor(newColor);
LookAndFeel currentLF = UIManager.getLookAndFeel();
boolean nimbus = currentLF.getClass().getName().indexOf("Nimbus")>-1; //$NON-NLS-1$
if(nimbus) {
colorButton.setIcon(new ColorIcon(color, 12, DataTool.buttonHeight-8));
} else {
colorButton.setBackground(color);
}
firePropertyChange("changed", null, null); //$NON-NLS-1$
}
}
/**
* Sets the autofit flag.
*
* @param auto true to autofit
*/
public void setAutofit(boolean auto) {
if (auto && !autofitCheckBox.isSelected())
autofitCheckBox.doClick(0);
else if (!auto && autofitCheckBox.isSelected())
autofitCheckBox.doClick(0);
}
/**
* Sets the active flag.
*
* @param active true
*/
public void setActive(boolean active) {
isActive = active;
if (active) {
if (neverBeenActive) {
neverBeenActive = false;
autofitCheckBox.setSelected(true);
}
fit(fit);
}
}
/**
* Fits a fit function to the current data.
*
* @param fit the function to fit
* @return the rms deviation
*/
public double fit(KnownFunction fit) {
if (drawer==null) {
selectFit((String) fitDropDown.getSelectedItem());
}
if (fit==null) return Double.NaN;
if (dataset==null) {
if (fit instanceof UserFunction) {
eqnField.setText("y = "+ //$NON-NLS-1$
((UserFunction) fit).getFullExpression(new String[] {"x"})); //$NON-NLS-1$
}
else {
eqnField.setText("y = "+ fit.getExpression("x")); //$NON-NLS-1$ //$NON-NLS-2$
}
autofitCheckBox.setSelected(false);
autofitCheckBox.setEnabled(false);
spinCellEditor.stopCellEditing();
paramTable.setEnabled(false);
rmsField.setText(ToolsRes.getString("DatasetCurveFitter.RMSField.NoData")); //$NON-NLS-1$
rmsField.setForeground(Color.RED);
return Double.NaN;
}
autofitCheckBox.setEnabled(true);
paramTable.setEnabled(true);
double[] x = dataset.getValidXPoints();
double[] y = dataset.getValidYPoints();
double devSq = 0;
double[] prevParams = null;
// get deviation before fitting
double prevDevSq = getDevSquared(fit, x, y);
boolean isLinearFit = false;
// autofit if checkbox is selected
if (autofitCheckBox.isSelected() && !Double.isNaN(prevDevSq)) {
if(fit instanceof KnownPolynomial) {
KnownPolynomial poly = (KnownPolynomial) fit;
poly.fitData(x, y);
isLinearFit = poly.degree()==1;
}
else if (fit instanceof UserFunction) {
// use HessianMinimize to autofit user function
UserFunction f = (UserFunction) fit;
double[] params = new double[f.getParameterCount()];
// can't autofit if no parameters or data length < parameter count
if (params.length>0 && params.length<=x.length && params.length<=y.length) {
MinimizeUserFunction minFunc = new MinimizeUserFunction(f, x, y);
prevParams = new double[params.length];
for(int i = 0; i<params.length; i++) {
params[i] = prevParams[i] = f.getParameterValue(i);
}
double tol = 1.0E-6;
int iterations = 20;
hessian.minimize(minFunc, params, iterations, tol);
// get deviation after minimizing
devSq = getDevSquared(fit, x, y);
// restore parameters and try Levenberg-Marquardt if Hessian fit is worse
if(devSq>prevDevSq) {
for(int i = 0; i<prevParams.length; i++) {
f.setParameterValue(i, prevParams[i]);
}
levmar.minimize(minFunc, params, iterations, tol);
// get deviation after minimizing
devSq = getDevSquared(fit, x, y);
}
// restore parameters and deviation if new fit is worse
if(devSq>prevDevSq) {
for(int i = 0; i<prevParams.length; i++) {
f.setParameterValue(i, prevParams[i]);
}
devSq = prevDevSq;
autofitCheckBox.setSelected(false);
// Toolkit.getDefaultToolkit().beep();
}
}
}
drawer.functionChanged = true;
paramTable.repaint();
}
doLinearRegression(x, y, isLinearFit);
if(devSq==0) {
devSq = getDevSquared(fit, x, y);
}
double rmsDev = Math.sqrt(devSq/x.length);
rmsField.setForeground(eqnField.getForeground());
if (x.length==0 || y.length==0) {
rmsField.setText(ToolsRes.getString("DatasetCurveFitter.RMSField.NoData")); //$NON-NLS-1$
rmsField.setForeground(Color.RED);
}
else if (Double.isNaN(rmsDev)) {
rmsField.setText(ToolsRes.getString("DatasetCurveFitter.RMSField.Undefined")); //$NON-NLS-1$
rmsField.setForeground(Color.RED);
}
else {
rmsField.applyPattern("0.000E0"); //$NON-NLS-1$
rmsField.setValue(rmsDev);
}
refreshStatusBar();
firePropertyChange("fit", null, null); //$NON-NLS-1$
return rmsDev;
}
/**
* Adds a fit function.
*
* @param f the fit function to add
* @param addToFitBuilder ignored--all fits are added to the fit builder
*/
public void addFitFunction(KnownFunction f, boolean addToFitBuilder) {
// check for duplicates
KnownFunction existing = fitMap.get(f.getName());
if (existing != null) {
if (existing.getExpression("x").equals(f.getExpression("x"))) { //$NON-NLS-1$ //$NON-NLS-2$
return; // duplicate name and expression, so ignore
}
// different expression, so change name to something unique
f.setName(fitBuilder.getUniqueName(f.getName()));
}
String selectedFitName = fit==null? getLineFitName(): fit.getName();
fitBuilder.addFitFunction(f);
fitDropDown.setSelectedItem(selectedFitName);
}
/**
* Refreshes the parent tab's status bar
*/
public void refreshStatusBar() {
if (tab!=null && tab.statsCheckbox.isSelected())
tab.refreshStatusBar(tab.getCorrelationString());
}
/**
* Gets the estimated uncertainty (standard error or other) of a best fit parameter.
* Returns Double.NaN if uncertainty is unknown or is not best fit.
*
* @param paramIndex the parameter index
* @return the estimated uncertainty in the parameter
*/
public double getUncertainty(int paramIndex) {
if (paramIndex<uncertainties.length && autofitCheckBox.isSelected()) {
return uncertainties[paramIndex];
}
return Double.NaN;
}
/**
* Returns a string of the uncertainty with appropriate formatting.
*
* @param paramIndex the parameter index
* @return the uncertainty string
*/
public String getUncertaintyString(int paramIndex) {
double uncertainty = getUncertainty(paramIndex);
if (Double.isNaN(uncertainty)) return null;
if(SEFormat instanceof DecimalFormat) {
DecimalFormat format = (DecimalFormat) SEFormat;
if (uncertainty<0.1) format.applyPattern("0.0E0"); //$NON-NLS-1$
else if (uncertainty<1) format.applyPattern("0.00"); //$NON-NLS-1$
else if (uncertainty<10) format.applyPattern("0.0"); //$NON-NLS-1$
else if (uncertainty<100) format.applyPattern("0"); //$NON-NLS-1$
else format.applyPattern("0.0E0"); //$NON-NLS-1$
}
return "� "+SEFormat.format(uncertainty); //$NON-NLS-1$
}
/**
* Gets a fit function by name.
*
* @param name the name
* @return the fit function, or null if none found
*/
public KnownFunction getFitFunction(String name) {
for (KnownFunction f: localFits) {
if (f.getName().equals(name)) return f;
}
return null;
}
/**
* Gets the selected fit parameters.
*
* @return a map of parameter names to values
*/
public Map<String, Double> getSelectedFitParameters() {
return null;
}
public Dimension getMinimumSize() {
Dimension dim = fitBar.getPreferredSize();
dim.height += eqnBar.getPreferredSize().height;
dim.height += rmsBar.getPreferredSize().height+1;
return dim;
}
// _______________________ protected & private methods __________________________
/**
* Creates the GUI.
*/
protected void createGUI() {
setLayout(new BorderLayout());
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
splitPane.setResizeWeight(0.8);
splitPane.setDividerSize(6);
// create autofit checkbox
autofitCheckBox = new JCheckBox("", true); //$NON-NLS-1$
autofitCheckBox.setOpaque(false);
autofitCheckBox.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
spinCellEditor.stopCellEditing();
paramTable.clearSelection();
fit(fit);
firePropertyChange("changed", null, null); //$NON-NLS-1$
}
});
// create labels
fitLabel = new JLabel(ToolsRes.getString("DatasetCurveFitter.Label.FitName")); //$NON-NLS-1$
fitLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
eqnLabel = new JLabel(ToolsRes.getString("DatasetCurveFitter.Label.Equation")); //$NON-NLS-1$
eqnLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
rmsLabel = new JLabel();
rmsLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
fitDropDown = new JComboBox() {
// override getPreferredSize method so has same height as buttons
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.height = DataTool.buttonHeight-2;
return dim;
}
// override addItem method so items are in alphabetical order
public void addItem(Object obj) {
if (obj==null) return;
String name = FitBuilder.localize((String)obj);
int count = getItemCount();
// add in alphabetical order, ignoring case
boolean added = false;
for (int i=0; i<count; i++) {
String next = FitBuilder.localize((String)getItemAt(i));
if (next!=null && name.compareToIgnoreCase(next)<0) {
// item comes after name, so insert name here
insertItemAt(obj, i);
added = true;
break;
}
}
if (!added) {
// add at end
super.addItem(obj);
}
}
};
for (KnownFunction f: defaultFits) {
localFits.add(f.clone());
}
// refresh fitMap and initialize fitDropDown with local fits
refreshFitMap();
for (String next: fitMap.keySet()) {
fitDropDown.addItem(next);
}
fitDropDown.setSelectedItem(getLineFitName());
fitDropDown.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (refreshing || fitBuilder.getSelectedCurveFitter()!=DatasetCurveFitter.this) return;
String selection = (String)fitDropDown.getSelectedItem();
if(selection!=null && fit!=null && !selection.equals(fit.getName())) {
firePropertyChange("changed", null, null); //$NON-NLS-1$
}
selectFit(selection);
fitDropDown.setToolTipText(fit==null? null: fit.getDescription());
}
});
class FitDropDownRenderer extends BasicComboBoxRenderer {
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
int length = fitDropDown.getItemCount();
if (-1<index && index<length) {
String fitName = (String)fitDropDown.getItemAt(index);
KnownFunction func = getFitFunction(fitName);
list.setToolTipText(func==null? null: func.getDescription());
}
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setFont(list.getFont());
setText((value == null) ? "" : FitBuilder.localize(value.toString())); //$NON-NLS-1$
return this;
}
}
fitDropDown.setRenderer(new FitDropDownRenderer());
// create equation field
eqnField = new JTextField() {
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.height = DataTool.buttonHeight-2;
return dim;
}
};
eqnField.setEditable(false);
eqnField.setEnabled(true);
eqnField.setBackground(Color.white);
eqnField.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
// create clone or open user function if double-clicked
if(e.getClickCount() == 2) {
String name = fitDropDown.getSelectedItem().toString();
if (fitBuilder.getPanelNames().contains(name)) {
fitBuilder.setSelectedPanel(name);
}
else {
UserFunction uf = createClone(fit, name);
UserFunctionEditor editor = new UserFunctionEditor();
editor.setMainFunctions(new UserFunction[] {uf});
FitFunctionPanel panel = new FitFunctionPanel(editor);
fitBuilder.addPanel(uf.getName(), panel);
fitDropDown.setSelectedItem(uf.getName());
}
fitBuilder.setVisible(true);
}
}
});
// create dataBuilder button
colorButton = DataTool.createButton(""); //$NON-NLS-1$
colorButton.setToolTipText(ToolsRes.getString("DatasetCurveFitter.Button.Color.Tooltip")); //$NON-NLS-1$
colorButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JDialog dialog = getColorDialog();
closeButton.setText(ToolsRes.getString("Button.OK")); //$NON-NLS-1$
dialog.setTitle(ToolsRes.getString("DatasetCurveFitter.Dialog.Color.Title")); //$NON-NLS-1$
dialog.setVisible(true);
}
});
// create rms field
rmsField = new NumberField(6) {
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.height = DataTool.buttonHeight-2;
return dim;
}
};
rmsField.setEditable(false);
rmsField.setEnabled(true);
rmsField.setBackground(Color.white);
// create table
cellRenderer = new ParamCellRenderer();
spinCellEditor = new SpinCellEditor();
paramModel = new ParamTableModel();
paramTable = new ParamTable(paramModel);
paramTable.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
// clear selection if pressed on the name column
if(paramTable.getSelectedColumn()==0) {
paramTable.clearSelection();
}
}
});
JScrollPane scroller = new JScrollPane(paramTable) {
public Dimension getMinimumSize() {
Dimension dim = spinCellEditor.spinner.getPreferredSize();
dim.width += cellRenderer.fieldFont.getSize()*7;
return dim;
}
};
splitPane.setRightComponent(scroller);
add(splitPane, BorderLayout.CENTER);
// create fit builder button
fitBuilderButton = DataTool.createButton(ToolsRes.getString("DatasetCurveFitter.Button.Define.Text")); //$NON-NLS-1$
fitBuilderButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
autofitCheckBox.setSelected(false);
String fitName = fit.getName();
if (fitName!=null && fitBuilder.getPanelNames().contains(fitName)) {
fitBuilder.setSelectedPanel(fitName);
}
else if (fitBuilder.getSelectedName()!=null) {
fitDropDown.setSelectedItem(fitBuilder.getSelectedName());
}
fitBuilder.refreshGUI();
fitBuilder.setVisible(true);
}
});
// create fit listener
fitListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
if(refreshing) return;
String prop = e.getPropertyName();
if(!prop.equals("function") //$NON-NLS-1$
&& !prop.equals("panel")) { //$NON-NLS-1$
return;
}
boolean isSelectedCurveFitter = fitBuilder.getSelectedCurveFitter()==DatasetCurveFitter.this;
if (prop.equals("panel")) { // fit panel selected, added or deleted //$NON-NLS-1$
if (e.getNewValue()!=null) { // panel selected or added
FitFunctionPanel panel = (FitFunctionPanel)e.getNewValue();
KnownFunction f = getFitFunction(panel);
String name = f.getName();
if (!fitMap.keySet().contains(name)) {
// new fit panel added
localFits.add(f);
fitMap.put(name, f);
fitDropDown.addItem(name);
}
else {
// existing panel selected
}
if (fitBuilder.isVisible() && isSelectedCurveFitter
&& tab!=null && tab.dataTool!=null && !tab.dataTool.isLoading) {
fitDropDown.setSelectedItem(name);
}
}
if (e.getOldValue()!=null) {
FitFunctionPanel panel = (FitFunctionPanel)e.getOldValue();
KnownFunction f = getFitFunction(panel);
String name = f.getName();
if (!fitBuilder.getPanelNames().contains(name)) {
// fit panel deleted
localFits.remove(f);
}
}
}
else if (prop.equals("function")) { // fit function has changed //$NON-NLS-1$
String name = (String)e.getNewValue(); // fit or parameter name
// determine old and new fit names
FitFunctionPanel panel = (FitFunctionPanel)fitBuilder.getSelectedPanel();
String fitName = panel.getName();
String oldFitName = fitName; // assume name unchanged
if (name.equals(fitName)
&& e.getOldValue()!=null
&& e.getOldValue() instanceof String) {
oldFitName = (String)e.getOldValue();
}
KnownFunction f = getFitFunction(panel);
replaceFit(oldFitName, fitName, f);
if (!fitName.equals(oldFitName)) {
fitDropDown.addItem(fitName);
}
if (isSelectedCurveFitter && tab!=null && tab.dataTool!=null && !tab.dataTool.isLoading) {
fitDropDown.setSelectedItem(fitName);
}
}
firePropertyChange("changed", null, null); //$NON-NLS-1$
refreshGUI();
}
};
// add local fits to fitBuilder
Collection<KnownFunction> fits = new ArrayList<KnownFunction>(localFits);
for (KnownFunction f: fits) {
if (!fitBuilder.addFitFunction(f)) {
// fit declined--a modified version of it must have been loaded
// so remove it from localFits
localFits.remove(f);
}
}
// add fitBuilder functions to localFits list
for (String next: fitBuilder.getPanelNames()) {
FitFunctionPanel panel = (FitFunctionPanel)fitBuilder.getPanel(next);
KnownFunction f = getFitFunction(panel);
if (localFits.contains(f)) continue;
localFits.add(f);
}
// assemble components
JPanel fitPanel = new JPanel(new BorderLayout());
splitPane.setLeftComponent(fitPanel);
fitBar = new JToolBar();
fitBar.setFloatable(false);
fitBar.setBorder(BorderFactory.createEtchedBorder());
fitBar.add(fitLabel);
fitBar.add(fitDropDown);
fitBar.addSeparator();
fitBar.add(fitBuilderButton);
fitPanel.add(fitBar, BorderLayout.NORTH);
JPanel eqnPanel = new JPanel(new BorderLayout());
fitPanel.add(eqnPanel, BorderLayout.CENTER);
eqnBar = new JToolBar();
eqnBar.setFloatable(false);
eqnBar.setBorder(BorderFactory.createEtchedBorder());
eqnBar.add(eqnLabel);
eqnBar.add(eqnField);
eqnBar.add(colorButton);
eqnPanel.add(eqnBar, BorderLayout.NORTH);
JPanel rmsPanel = new JPanel(new BorderLayout());
eqnPanel.add(rmsPanel, BorderLayout.CENTER);
rmsBar = new JToolBar();
rmsBar.setFloatable(false);
rmsBar.setBorder(BorderFactory.createEtchedBorder());
rmsBar.add(autofitCheckBox);
rmsBar.addSeparator();
rmsBar.add(rmsLabel);
rmsBar.add(rmsField);
rmsPanel.add(rmsBar, BorderLayout.NORTH);
refreshGUI();
// refreshFitDropDown();
}
/**
* Refreshes the GUI.
*/
protected void refreshGUI() {
autofitCheckBox.setText(ToolsRes.getString("Checkbox.Autofit.Label")); //$NON-NLS-1$
rmsLabel.setText(ToolsRes.getString("DatasetCurveFitter.Label.RMSDeviation")); //$NON-NLS-1$
fitBuilderButton.setText(ToolsRes.getString("DatasetCurveFitter.Button.Define.Text")); //$NON-NLS-1$
fitBuilderButton.setToolTipText(ToolsRes.getString("DatasetCurveFitter.Button.Define.Tooltip")); //$NON-NLS-1$
fitLabel.setText(ToolsRes.getString("DatasetCurveFitter.Label.FitName")); //$NON-NLS-1$
eqnLabel.setText(ToolsRes.getString("DatasetCurveFitter.Label.Equation")); //$NON-NLS-1$
LookAndFeel currentLF = UIManager.getLookAndFeel();
boolean nimbus = currentLF.getClass().getName().indexOf("Nimbus")>-1; //$NON-NLS-1$
if(nimbus) {
colorButton.setIcon(new ColorIcon(color, 12, DataTool.buttonHeight-8));
} else {
colorButton.setBackground(color);
}
refreshFitDropDown();
}
/**
* Refreshes the fitDropDown.
*/
protected void refreshFitDropDown() {
Runnable runner = new Runnable() {
public synchronized void run() {
refreshFitMap();
fitBuilder.defaultFitName = getLineFitName();
String toSelect = fitBuilder.defaultFitName;
refreshing = true;
fitDropDown.removeAllItems();
for (String name: fitMap.keySet()) {
if (fit!=null && name.equals(fit.getName())) {
toSelect = name;
}
fitDropDown.addItem(name);
}
refreshing = false;
fitDropDown.setSelectedItem(toSelect);
}
};
// invoke later so UI responds
SwingUtilities.invokeLater(runner);
}
/**
* Refreshes the fit map with localized names.
*
* @return a list of
*/
protected void refreshFitMap() {
fitMap.clear();
for (KnownFunction f: localFits) {
fitMap.put(f.getName(), f);
}
}
/**
* Gets the name of the line fit function.
*
* @return the name of the line function (polynomial degree 1)
*/
protected String getLineFitName() {
for (String key: fitMap.keySet()) {
KnownFunction f = fitMap.get(key);
if (f instanceof KnownPolynomial) {
KnownPolynomial poly = (KnownPolynomial)f;
if (poly.getParameterCount()==2) return key;
}
}
return null;
}
protected void setDataToolTab(DataToolTab tab) {
this.tab = tab;
}
/**
* Sets the font level.
*
* @param level the level
*/
protected void setFontLevel(int level) {
fontLevel = level;
FontSizer.setFonts(this, fontLevel);
fitBuilder.setFontLevel(level);
splitPane.setDividerLocation(splitPane.getMaximumDividerLocation());
}
/**
* Sets the value of a parameter.
*
* @param row the row number
* @param value the value
*/
protected void setParameterValue(int row, double value) {
if(row<fit.getParameterCount()) {
fit.setParameterValue(row, value);
}
}
/**
* Selects a named fit.
* @param name the name of the fit function
*/
protected void selectFit(String name) {
if (refreshing) return;
if (name==null) name = getLineFitName();
fit = fitMap.get(name);
if(fit!=null) {
FunctionDrawer prev = drawer;
drawer = new FunctionDrawer(fit);
drawer.setColor(color);
paramTable.tableChanged(null);
// construct equation string
String depVar = (dataset==null)? "y": //$NON-NLS-1$
TeXParser.removeSubscripting(dataset.getColumnName(1));
String indepVar = (dataset==null)? "x": //$NON-NLS-1$
TeXParser.removeSubscripting(dataset.getColumnName(0));
if(fit instanceof UserFunction) {
eqnField.setText(depVar+" = "+ //$NON-NLS-1$
((UserFunction)fit).getFullExpression(new String[] {indepVar}));
}
else {
eqnField.setText(depVar+" = "+fit.getExpression(indepVar)); //$NON-NLS-1$
}
firePropertyChange("drawer", prev, drawer); //$NON-NLS-1$
if (isActive)
fit(fit);
if(fitBuilder.isVisible()) {
fitBuilder.setSelectedPanel(fit.getName());
}
revalidate();
}
}
protected UserFunction createClone(KnownFunction f, String name) {
String var = (dataset==null)? "x": //$NON-NLS-1$
TeXParser.removeSubscripting(dataset.getColumnName(0));
f.getExpression(var);
UserFunction uf = null;
if (f instanceof UserFunction)
uf = ((UserFunction)f).clone();
else {
uf = new UserFunction(f.getName());
String[] params = new String[f.getParameterCount()];
double[] values = new double[f.getParameterCount()];
String[] desc = new String[f.getParameterCount()];
for (int i = 0; i < params.length; i++) {
params[i] = f.getParameterName(i);
values[i] = f.getParameterValue(i);
desc[i] = f.getParameterDescription(i);
}
uf.setParameters(params, values, desc);
uf.setExpression(f.getExpression(var), new String[] {var});
}
// add digit to end of name
int n = 1;
try {
String number = name.substring(name.length()-1);
n = Integer.parseInt(number)+1;
name = name.substring(0, name.length()-1);
} catch (Exception ex) {}
// make a set of existing fit names
Set<String> names = new HashSet<String>();
for (int i = 0; i < fitDropDown.getItemCount(); i++) {
names.add(fitDropDown.getItemAt(i).toString());
}
// increment digit at end of name if necessary
try {
while (names.contains(name+n)) {
n++;
}
} catch (Exception ex) {}
uf.setName(name+n);
return uf;
}
// /**
// * Shifts data values by a fixed offset
// *
// * @param values an array of values
// * @param offset the shift
// * @return an array with shifted values
// */
// private double[] shiftValues(double[] values, double offset) {
// if (offset==0) return values;
// for (int i=0; i<values.length; i++) {
// values[i] += offset;
// }
// return values;
// }
//
/**
* Gets the total deviation squared between function and data
*/
private double getDevSquared(Function f, double[] x, double[] y) {
fitEvaluatedToNaN = false;
double total = 0;
for(int i = 0; i<x.length; i++) {
double next = f.evaluate(x[i]);
if (f instanceof UserFunction && tab!=null) {
fitEvaluatedToNaN = fitEvaluatedToNaN || ((UserFunction)f).evaluatedToNaN();
}
double dev = (next-y[i]);
total += dev*dev;
}
if (tab!=null) {
if (fitEvaluatedToNaN) {
String s = ToolsRes.getString("DatasetCurveFitter.Warning.FunctionError"); //$NON-NLS-1$
tab.plot.setMessage(s, 2);
}
else {
tab.plot.setMessage("", 2); //$NON-NLS-1$
}
}
return fitEvaluatedToNaN? Double.NaN: total;
}
/**
* Determines the Pearson correlation and linear fit parameter SEs.
*
* @param xd double[]
* @param yd double[]
* @param isLinearFit true if linear fit (sets uncertainties to slope and intercept SE)
*/
public void doLinearRegression(double[] xd, double[] yd, boolean isLinearFit) {
int n = xd.length;
// set Double.NaN defaults
correlation = Double.NaN;
for (int i=0; i< uncertainties.length; i++)
uncertainties[i] = Double.NaN;
// return if less than 3 data points
if (n<3) return;
double mean_x = xd[0];
double mean_y = yd[0];
for(int i=1; i<n; i++){
mean_x += xd[i];
mean_y += yd[i];
}
mean_x /= n;
mean_y /= n;
double sum_sq_x = 0;
double sum_sq_y = 0;
double sum_coproduct = 0;
for(int i=0; i<n; i++){
double delta_x = xd[i]-mean_x;
double delta_y = yd[i]-mean_y;
sum_sq_x += delta_x*delta_x;
sum_sq_y += delta_y*delta_y;
sum_coproduct += delta_x*delta_y;
}
if (sum_sq_x==0 || sum_sq_y==0) {
correlation = Double.NaN;
for (int i=0; i< uncertainties.length; i++)
uncertainties[i] = Double.NaN;
return;
}
double pop_sd_x = sum_sq_x/n;
double pop_sd_y = sum_sq_y/n;
double cov_x_y = sum_coproduct/n;
correlation = cov_x_y*cov_x_y/(pop_sd_x*pop_sd_y);
if (isLinearFit) {
double sumSqErr = Math.max(0.0, sum_sq_y - sum_coproduct * sum_coproduct / sum_sq_x);
double meanSqErr = sumSqErr/(n-2);
uncertainties[0] = Math.sqrt(meanSqErr / sum_sq_x); // slope SE
uncertainties[1] = Math.sqrt(meanSqErr * ((1.0/n) + (mean_x*mean_x) / sum_sq_x)); // intercept SE
}
}
private KnownFunction getFitFunction(FitFunctionPanel panel) {
UserFunction f = panel.getFitFunction();
if (f.polynomial!=null) {
f.updatePolynomial();
return f.polynomial.clone();
}
return f.clone();
}
/**
* Replaces an existing fit function with a new one.
*
* @param oldName the (localized) name of the existing fit function
* @param newName the (localized) new name of the function
* @param newFit the new fit function
*/
protected void replaceFit(String oldName, String newName, KnownFunction newFit) {
KnownFunction oldFit = fitMap.get(oldName);
if (oldFit!=null) {
if (localFits.contains(oldFit)) {
localFits.remove(oldFit);
localFits.add(newFit);
}
refreshFitDropDown();
}
refreshFitMap();
}
/**
* Gets a color dialog for the plotted curve fit drawer
*/
protected JDialog getColorDialog() {
if(colorDialog==null) {
// create color dialog
final Frame frame = JOptionPane.getFrameForComponent(this);
final JColorChooser cc = new JColorChooser();
cc.getSelectionModel().addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
color = cc.getColor();
setColor(color);
frame.repaint();
}
});
colorDialog = new JDialog(frame, false);
closeButton = new JButton();
closeButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
colorDialog.setVisible(false);
}
});
JPanel contentPane = new JPanel(new BorderLayout());
JPanel buttonPanel = new JPanel();
buttonPanel.add(closeButton);
JPanel chooser = cc.getChooserPanels()[0];
chooser.setBorder(BorderFactory.createEmptyBorder(2, 2, 12, 2));
contentPane.add(chooser, BorderLayout.CENTER);
contentPane.add(buttonPanel, BorderLayout.SOUTH);
colorDialog.setContentPane(contentPane);
colorDialog.pack();
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int x = (dim.width-colorDialog.getWidth())/2;
Point p = this.getLocationOnScreen();
int y = Math.max(0, p.y-colorDialog.getHeight());
colorDialog.setLocation(x, y);
}
return colorDialog;
}
//__________________________ inner classes _____________________________
/**
* A table to display and edit parameters.
*/
class ParamTable extends JTable {
/**
* Constructor ParamTable
* @param model
*/
public ParamTable(ParamTableModel model) {
super(model);
setGridColor(Color.blue);
JTableHeader header = getTableHeader();
header.setForeground(Color.blue);
}
public TableCellRenderer getCellRenderer(int row, int column) {
return cellRenderer;
}
public TableCellEditor getCellEditor(int row, int column) {
spinCellEditor.rowNumber = row;
return spinCellEditor;
}
public void setFont(Font font) {
super.setFont(font);
if(cellRenderer!=null) {
Font aFont = cellRenderer.labelFont;
aFont = aFont.deriveFont(font.getSize2D());
cellRenderer.labelFont = aFont;
spinCellEditor.stepSizeLabel.setFont(aFont);
aFont = cellRenderer.fieldFont;
aFont = aFont.deriveFont(font.getSize2D());
cellRenderer.fieldFont = aFont;
spinCellEditor.field.setFont(aFont);
}
getTableHeader().setFont(font);
setRowHeight(font.getSize()+4);
TableModel model = getModel();
if (model instanceof DefaultTableModel) {
DefaultTableModel tm = (DefaultTableModel)model;
tm.fireTableDataChanged();
}
}
}
/**
* A class to provide model data for the parameters table.
*/
class ParamTableModel extends AbstractTableModel {
public String getColumnName(int col) {
return (col==0)? ToolsRes.getString("Table.Heading.Parameter"): //$NON-NLS-1$
ToolsRes.getString("Table.Heading.Value"); //$NON-NLS-1$
}
public int getRowCount() {
return (fit==null)? 0: fit.getParameterCount();
}
public int getColumnCount() {
return 2;
}
public Object getValueAt(int row, int col) {
if(col==0) {
return fit.getParameterName(row);
}
return new Double(fit.getParameterValue(row));
}
public boolean isCellEditable(int row, int col) {
return col==1;
}
public Class<?> getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
}
/**
* A cell renderer for the parameter table.
*/
class ParamCellRenderer extends JLabel implements TableCellRenderer {
Color lightBlue = new Color(204, 204, 255);
Color lightGray = javax.swing.UIManager.getColor("Panel.background"); //$NON-NLS-1$
Font fieldFont = new JTextField().getFont();
Font labelFont = getFont();
// Constructor
/**
* Constructor ParamCellRenderer
*/
public ParamCellRenderer() {
super();
setOpaque(true); // make background visible
setBorder(BorderFactory.createEmptyBorder(2, 1, 2, 2));
}
// Returns a label for the specified cell.
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int row, int col) {
setHorizontalAlignment(SwingConstants.LEFT);
setBorder(new CellBorder(new Color(240, 240, 240)));
String tooltip = col==1? ToolsRes.getString("DatasetCurveFitter.SE.Description"): null; //$NON-NLS-1$
if(value instanceof String) { // parameter name string
setFont(labelFont);
setBackground(isSelected
? Color.LIGHT_GRAY
: lightGray);
setForeground(Color.black);
setText(value.toString());
if (col==0) { // parameter name: tooltip is description
tooltip = fit.getParameterDescription(row);
}
} else { // Double value
setFont(fieldFont);
setBackground(isSelected
? lightBlue
: Color.white);
setForeground(isSelected
? Color.red
: table.isEnabled()? Color.black
: Color.gray);
Format format = spinCellEditor.field.format;
setText(format.format(value));
if (!autofitCheckBox.isSelected()) {
tooltip += " "+ToolsRes.getString("DatasetCurveFitter.SE.Autofit"); //$NON-NLS-1$//$NON-NLS-2$
}
else if (fit instanceof KnownPolynomial) {
tooltip += " "+ToolsRes.getString("DatasetCurveFitter.SE.Unknown"); //$NON-NLS-1$//$NON-NLS-2$
KnownPolynomial poly = (KnownPolynomial) fit;
if (poly.degree()==1) {
String uncert = getUncertaintyString(row);
if (uncert!=null)
tooltip = uncert+" ("+ToolsRes.getString("DatasetCurveFitter.SE.Name")+")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
else {
tooltip += " "+ToolsRes.getString("DatasetCurveFitter.SE.Unknown"); //$NON-NLS-1$//$NON-NLS-2$
}
}
setToolTipText(tooltip);
return this;
}
}
/**
* A cell editor that uses a JSpinner with a number crawler model.
*/
class SpinCellEditor extends AbstractCellEditor implements TableCellEditor {
JPanel panel = new JPanel(new BorderLayout());
SpinnerNumberCrawlerModel crawlerModel = new SpinnerNumberCrawlerModel(1);
JSpinner spinner;
NumberField field;
int rowNumber;
JLabel stepSizeLabel = new JLabel("10%"); //$NON-NLS-1$
// Constructor.
SpinCellEditor() {
panel.setOpaque(false);
spinner = new JSpinner(crawlerModel);
spinner.setToolTipText(ToolsRes.getString("Table.Spinner.ToolTip")); //$NON-NLS-1$
spinner.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
autofitCheckBox.setSelected(false);
double val = ((Double) spinner.getValue()).doubleValue();
field.setValue(val);
fit.setParameterValue(rowNumber, val);
if (fit instanceof UserFunction) {
// get dependent parameter values from fit builder
UserFunction f = (UserFunction)fit;
String name = f.getName();
FitFunctionPanel panel = (FitFunctionPanel) fitBuilder.getPanel(name);
if(panel!=null) {
name = f.getParameterName(rowNumber);
Parameter seed = new Parameter(name, field.getText());
Iterator<?> it = panel.getParamEditor().evaluateDependents(seed).iterator();
while(it.hasNext()) {
Parameter p = (Parameter) it.next();
// find row number, set value in fit
for(int i = 0; i<f.getParameterCount(); i++) {
if(f.getParameterName(i).equals(p.getName())) {
f.setParameterValue(i, p.getValue());
paramModel.fireTableCellUpdated(i, 1);
break;
}
}
}
panel.getFitFunctionEditor().parametersValid = false;
f.updateReferenceParameters();
}
}
drawer.functionChanged = true;
fit(fit);
firePropertyChange("changed", null, null); //$NON-NLS-1$
}
});
field = new NumberField(10);
field.applyPattern("0.000E0"); //$NON-NLS-1$
field.setBorder(BorderFactory.createEmptyBorder(1, 1, 0, 0));
spinner.setBorder(BorderFactory.createEmptyBorder(0, 1, 1, 0));
spinner.setEditor(field);
stepSizeLabel.addMouseListener(new MouseInputAdapter() {
public void mousePressed(MouseEvent e) {
JPopupMenu popup = new JPopupMenu();
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
// set the percent delta
double percent = Double.parseDouble(e.getActionCommand());
crawlerModel.setPercentDelta(percent);
crawlerModel.refreshDelta();
stepSizeLabel.setText(e.getActionCommand()+"%"); //$NON-NLS-1$
}
};
for(int i = 0; i<3; i++) {
String val = (i==0)? "10": (i==1)? "1.0": "0.1"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
JMenuItem item = new JMenuItem(val+"%"); //$NON-NLS-1$
item.setActionCommand(val);
item.addActionListener(listener);
popup.add(item);
}
// show the popup
popup.show(stepSizeLabel, 0, stepSizeLabel.getHeight());
}
});
field.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
JComponent comp = (JComponent) e.getSource();
if(e.getKeyCode()==KeyEvent.VK_ENTER) {
spinner.setValue(new Double(field.getValue()));
comp.setBackground(Color.white);
crawlerModel.refreshDelta();
} else {
comp.setBackground(Color.yellow);
}
}
});
panel.add(spinner, BorderLayout.CENTER);
panel.add(stepSizeLabel, BorderLayout.EAST);
}
// Gets the component to be displayed while editing.
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
spinner.setValue(value);
crawlerModel.refreshDelta();
return panel;
}
// Determines when editing starts.
public boolean isCellEditable(EventObject e) {
if(e instanceof MouseEvent) {
return true;
} else if(e instanceof ActionEvent) {
return true;
}
return false;
}
// Called when editing is completed.
public Object getCellEditorValue() {
if(field.getBackground()==Color.yellow) {
fit.setParameterValue(rowNumber, field.getValue());
drawer.functionChanged = true;
DatasetCurveFitter.this.firePropertyChange("fit", null, null); //$NON-NLS-1$
field.setBackground(Color.white);
firePropertyChange("changed", null, null); //$NON-NLS-1$
}
return null;
}
}
/**
* A number spinner model with a settable delta.
*/
class SpinnerNumberCrawlerModel extends AbstractSpinnerModel {
double val = 0;
double delta;
double percentDelta = 10;
/**
* Constructor SpinnerNumberCrawlerModel
* @param initialDelta
*/
public SpinnerNumberCrawlerModel(double initialDelta) {
delta = initialDelta;
}
public Object getValue() {
return new Double(val);
}
public Object getNextValue() {
return new Double(val+delta);
}
public Object getPreviousValue() {
return new Double(val-delta);
}
public void setValue(Object value) {
if(value!=null) {
val = ((Double) value).doubleValue();
fireStateChanged();
}
}
public void setPercentDelta(double percent) {
percentDelta = percent;
}
public double getPercentDelta() {
return percentDelta;
}
// refresh delta based on current value and percent
public void refreshDelta() {
if(val!=0) {
delta = Math.abs(val*percentDelta/100);
}
}
}
/**
* A function whose value is the total deviation squared
* between a multivariable function and a set of data points.
* This is minimized by the HessianMinimize class.
*/
public class MinimizeMultiVarFunction implements MultiVarFunction {
MultiVarFunction f;
double[] x, y; // the data
double[] vars = new double[5];
// Constructor
MinimizeMultiVarFunction(MultiVarFunction f, double[] x, double[] y) {
this.f = f;
this.x = x;
this.y = y;
}
// Evaluates the function
public double evaluate(double[] params) {
System.arraycopy(params, 0, vars, 1, 4);
double sum = 0.0;
for(int i = 0, n = x.length; i<n; i++) {
vars[0] = x[i];
// evaluate the function and find deviation
double dev = y[i]-f.evaluate(vars);
// sum the squares of the deviations
sum += dev*dev;
}
return sum;
}
}
/**
* A function whose value is the total deviation squared
* between a user function and a set of data points.
* This function is minimized by the HessianMinimize class.
*/
public class MinimizeUserFunction implements MultiVarFunction {
UserFunction f;
double[] x, y; // the data
// Constructor
MinimizeUserFunction(UserFunction f, double[] x, double[] y) {
this.f = f;
this.x = x;
this.y = y;
}
// Evaluates this function
public double evaluate(double[] params) {
// set the parameter values of the user function
for(int i = 0; i<params.length; i++) {
f.setParameterValue(i, params[i]);
}
double sum = 0.0;
for(int i = 0; i<x.length; i++) {
// evaluate the user function and find deviation
double dev = y[i]-f.evaluate(x[i]);
// sum the squares of the deviations
sum += dev*dev;
}
return sum;
}
}
/**
* A JTextField that accepts only numbers.
*/
static class NumberField extends JTextField {
// instance fields
protected NumberFormat format = NumberFormat.getInstance();
protected double prevValue;
protected String pattern = "0"; //$NON-NLS-1$
protected int preferredWidth;
/**
* Constructor NumberField
* @param columns
*/
public NumberField(int columns) {
super(columns);
setForeground(Color.black);
}
public double getValue() {
if(getText().equals(format.format(prevValue))) {
return prevValue;
}
double retValue;
try {
retValue = format.parse(getText()).doubleValue();
} catch(ParseException e) {
Toolkit.getDefaultToolkit().beep();
setValue(prevValue);
return prevValue;
}
return retValue;
}
public void setValue(double value) {
if(!isVisible()) {
return;
}
setText(format.format(value));
prevValue = value;
}
public void applyPattern(String pattern) {
if (format instanceof DecimalFormat) {
try {
// catch occasional exceptions thrown when opening a trk file...
((DecimalFormat) format).applyPattern(pattern);
this.pattern = pattern;
} catch (Exception e) {
}
}
}
public String getPattern() {
return pattern;
}
public NumberFormat getFormat() {
return format;
}
protected void refreshPreferredWidth() {
// determine preferred width of field
FontRenderContext frc = new FontRenderContext(null, false, false);
Rectangle2D rect = getFont().getStringBounds(getText(), frc);
preferredWidth = (int)rect.getWidth()+8;
}
@Override
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.width = Math.max(dim.width, preferredWidth);
return dim;
}
}
//_______________________________ static methods _________________________________
/**
* Sets the default fit functions. Instances of DatasetCurveFitter instantiated AFTER
* this call will make these fits available to the user.
*
* @param functions the fit functions
*/
public static void setDefaultFitFunctions(ArrayList<KnownFunction> functions) {
if (functions != null) {
defaultFits = functions;
}
}
}
/*
* 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
*/