/*
* 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.frames;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.ButtonGroup;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.KeyStroke;
import javax.swing.WindowConstants;
import org.opensourcephysics.display.ComplexDataset;
import org.opensourcephysics.display.DataTable;
import org.opensourcephysics.display.DataTableFrame;
import org.opensourcephysics.display.DisplayRes;
import org.opensourcephysics.display.Drawable;
import org.opensourcephysics.display.DrawingFrame;
import org.opensourcephysics.display.PlottingPanel;
import org.opensourcephysics.numerics.FFT;
import org.opensourcephysics.numerics.Function;
/**
* FFTFrame computes the FFT and displays the result using a ComplexDataset.
*
* @author W. Christian
* @version 1.0
*/
public class FFTFrame extends DrawingFrame {
public static final int MODE = 0;
public static final int FREQ = 1;
public static final int OMEGA = 2;
public static final int WAVENUMBER = 3;
public static final int MOMENTUM = 4;
protected int domainType = FREQ;
protected ComplexDataset complexDataset = new ComplexDataset();
protected DataTable dataTable = new DataTable();
protected DataTableFrame tableFrame;
private double[] fftData = new double[1];
private FFT fft = new FFT(1);
JMenuItem ampPhaseItem, postItem, barItem;
/**
* A DrawingFrame that displays a FFT as its drawable.
*
* @param xlabel String
* @param ylabel String
* @param title String
*/
public FFTFrame(String xlabel, String ylabel, String title) {
super(new PlottingPanel(xlabel, ylabel, null));
complexDataset.setMarkerShape(ComplexDataset.PHASE_POST);
complexDataset.setXYColumnNames(xlabel, "re", "im"); //$NON-NLS-1$ //$NON-NLS-2$
drawingPanel.addDrawable(complexDataset);
setTitle(title);
dataTable.add(complexDataset);
addMenuItems();
setAnimated(true);
setAutoclear(true);
}
/**
* Adds Views menu items on the menu bar.
*/
protected void addMenuItems() {
JMenuBar menuBar = getJMenuBar();
if(menuBar==null) {
return;
}
JMenu helpMenu = this.removeMenu(DisplayRes.getString("DrawingFrame.Help_menu_item")); //$NON-NLS-1$
JMenu menu = getMenu(DisplayRes.getString("DrawingFrame.Views_menu")); //$NON-NLS-1$
if(menu==null) {
menu = new JMenu(DisplayRes.getString("DrawingFrame.Views_menu")); //$NON-NLS-1$
menuBar.add(menu);
menuBar.validate();
} else { // add a separator if tools already exists
menu.addSeparator();
}
if(helpMenu!=null) {
menuBar.add(helpMenu);
}
ButtonGroup menubarGroup = new ButtonGroup();
// post view
postItem = new JRadioButtonMenuItem(DisplayRes.getString("ComplexPlotFrame.MenuItem.PostView")); //$NON-NLS-1$
menubarGroup.add(postItem);
postItem.setSelected(true);
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
convertToPostView();
}
};
postItem.addActionListener(actionListener);
menu.add(postItem);
// bar view
barItem = new JRadioButtonMenuItem(DisplayRes.getString("ComplexPlotFrame.MenuItem.BarView")); //$NON-NLS-1$
menubarGroup.add(barItem);
actionListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
convertToPhaseBarView();
}
};
barItem.addActionListener(actionListener);
menu.add(barItem);
// amp and phase
ampPhaseItem = new JRadioButtonMenuItem(DisplayRes.getString("ComplexPlotFrame.MenuItem.AmpPhase")); //$NON-NLS-1$
menubarGroup.add(ampPhaseItem);
actionListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
convertToAmpAndPhaseView();
}
};
ampPhaseItem.addActionListener(actionListener);
menu.add(ampPhaseItem);
menu.addSeparator();
JMenuItem tableItem = new JMenuItem(DisplayRes.getString("DrawingFrame.DataTable_menu_item")); //$NON-NLS-1$
tableItem.setAccelerator(KeyStroke.getKeyStroke('T', MENU_SHORTCUT_KEY_MASK));
ActionListener tableListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
showDataTable(true);
}
};
tableItem.addActionListener(tableListener);
menu.add(tableItem);
JMenuItem item = new JMenuItem(DisplayRes.getString("DrawingFrame.DataTable_menu_item")); //$NON-NLS-1$
item.addActionListener(tableListener);
if((drawingPanel!=null)&&(drawingPanel.getPopupMenu()!=null)) {
drawingPanel.getPopupMenu().add(item);
}
// add phase legend to tool menu
menu.addSeparator();
tableItem = new JMenuItem(DisplayRes.getString("GUIUtils.PhaseLegend")); //$NON-NLS-1$
tableListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
complexDataset.showLegend();
}
};
tableItem.addActionListener(tableListener);
menu.add(tableItem);
}
/**
* Sets the labels on the data table.
* The xlabel resets the x axis label on the plot.
*
* @param xlabel String
* @param reLabel String
* @param imLabel String
*/
public void setXYColumnNames(String xlabel, String reLabel, String imLabel) {
complexDataset.setXYColumnNames(xlabel, reLabel, imLabel);
if(drawingPanel instanceof PlottingPanel) {
((PlottingPanel) drawingPanel).setXLabel(xlabel);
}
}
/**
* Sets the units for the FFT output.
* Domain types are: MODE, FREQ, OMEGA, WAVENUMBER, MOMENTUM
*
* @param type int
*/
public void setDomainType(int type) {
domainType = type;
switch(domainType) {
case MODE :
complexDataset.setXYColumnNames("mode", "re", "im"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if(drawingPanel instanceof PlottingPanel) {
((PlottingPanel) drawingPanel).setXLabel(DisplayRes.getString("FFTFrame.Plot.XLabel.Mode")); //$NON-NLS-1$
}
break;
case FREQ :
complexDataset.setXYColumnNames("f", "re", "im"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if(drawingPanel instanceof PlottingPanel) {
((PlottingPanel) drawingPanel).setXLabel(DisplayRes.getString("FFTFrame.Plot.XLabel.Frequency")); //$NON-NLS-1$
}
break;
case OMEGA :
complexDataset.setXYColumnNames("omega", "re", "im"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if(drawingPanel instanceof PlottingPanel) {
((PlottingPanel) drawingPanel).setXLabel(DisplayRes.getString("FFTFrame.Plot.XLabel.Omega")); //$NON-NLS-1$
}
break;
case WAVENUMBER :
complexDataset.setXYColumnNames("k", "re", "im"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if(drawingPanel instanceof PlottingPanel) {
((PlottingPanel) drawingPanel).setXLabel(DisplayRes.getString("FFTFrame.Plot.XLabel.WaveNumber")); //$NON-NLS-1$
}
break;
case MOMENTUM :
complexDataset.setXYColumnNames("p", "re", "im"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if(drawingPanel instanceof PlottingPanel) {
((PlottingPanel) drawingPanel).setXLabel(DisplayRes.getString("FFTFrame.Plot.XLabel.Momentum")); //$NON-NLS-1$
}
break;
}
}
public void convertToPostView() {
complexDataset.setMarkerShape(ComplexDataset.PHASE_POST);
drawingPanel.invalidateImage();
postItem.setSelected(true);
drawingPanel.repaint();
}
public void convertToAmpAndPhaseView() {
complexDataset.setMarkerShape(ComplexDataset.PHASE_CURVE);
ampPhaseItem.setSelected(true);
drawingPanel.invalidateImage();
drawingPanel.repaint();
}
public void convertToPhaseBarView() {
complexDataset.setMarkerShape(ComplexDataset.PHASE_BAR);
barItem.setSelected(true);
drawingPanel.invalidateImage();
drawingPanel.repaint();
}
/**
* Does an FFT of the given real and imaginary function.
* The function is evaulauted on the interval [xmin, xmax). Note that the endpoint, xmax, is excluded.
*
* @param reF function the real part of the function.
* @param imF function the imaginary part of the function.
* @param xmin double
* @param xmax double
* @param n int number of compelx data points
*/
public void doFFT(Function reF, Function imF, double xmin, double xmax, int n) {
if(2*n!=fftData.length) {
// complex data requires twice the storage of real data
fftData = new double[2*n];
fft = new FFT(n);
}
int off = (int) (n*xmin/(xmax-xmin));
off = Math.abs(off);
double xi = xmin, dx = (xmax-xmin)/n;
for(int i = 0; i<n; i++) {
int ii = (off+i)%n;
fftData[2*ii] = (reF==null) ? 0 : reF.evaluate(xi);
fftData[2*ii+1] = (imF==null) ? 0 : imF.evaluate(xi);
xi += dx;
}
fft.transform(fftData);
fft.toNaturalOrder(fftData);
double[] domain = null;
switch(domainType) {
case MODE :
domain = fft.getNaturalModes();
break;
case FREQ :
domain = fft.getNaturalFreq(xmin, xmax);
break;
case OMEGA :
case MOMENTUM :
case WAVENUMBER :
domain = fft.getNaturalOmega(xmin, xmax);
break;
}
complexDataset.clear();
complexDataset.append(domain, fftData);
}
/**
* Does an FFT of the given data.
*
* The array is assumed to contain real numbers.
* The given array remains unchanged.
*
* @param data double[]
* @param xmin double
* @param xmax double
*/
public void doRealFFT(double[] data, double xmin, double xmax) {
int n = fft.getN();
if(2*data.length!=fftData.length) {
n = data.length; // number of complex data points
// complex data requires twice the storage of real data
fftData = new double[2*n];
fft = new FFT(n);
}
int off = (int) (n*xmin/(xmax-xmin));
off = Math.abs(off);
for(int i = 0; i<n; i++) {
int ii = (off+i)%n;
fftData[2*ii] = data[i];
fftData[2*ii+1] = 0;
}
fft.transform(fftData);
fft.toNaturalOrder(fftData);
double[] domain = null;
switch(domainType) {
case MODE :
domain = fft.getNaturalModes();
break;
case FREQ :
domain = fft.getNaturalFreq(xmin, xmax);
break;
case OMEGA :
case MOMENTUM :
case WAVENUMBER :
domain = fft.getNaturalOmega(xmin, xmax);
break;
}
complexDataset.clear();
complexDataset.append(domain, fftData);
}
/**
* Does an FFT of the given data array.
*
* The data array is assumed to contain complex numbers stored as
* successive (re,im) pairs.
* The given array remains unchanged.
*
* @param data double[]
* @param xmin double
* @param xmax double
*/
public void doFFT(double[] data, double xmin, double xmax) {
int n = fft.getN();
if(data.length!=fftData.length) {
fftData = new double[data.length];
n = data.length/2; // number of data points
fft = new FFT(n);
}
int off = (int) (n*xmin/(xmax-xmin));
off = Math.abs(off);
for(int i = 0; i<n; i++) {
int ii = (off+i)%n;
fftData[2*ii] = data[2*i];
fftData[2*ii+1] = data[2*i+1];
}
fft.transform(fftData);
fft.toNaturalOrder(fftData);
double[] domain = null;
switch(domainType) {
case MODE :
domain = fft.getNaturalModes();
break;
case FREQ :
domain = fft.getNaturalFreq(xmin, xmax);
break;
case OMEGA :
case MOMENTUM :
case WAVENUMBER :
domain = fft.getNaturalOmega(xmin, xmax);
break;
}
complexDataset.clear();
complexDataset.append(domain, fftData);
}
/**
* Gets Drawable objects added by the user to this frame.
*
* @return the list
*/
public synchronized ArrayList<Drawable> getDrawables() {
ArrayList<Drawable> list = super.getDrawables();
list.remove(complexDataset);
return list;
}
/**
* Removes drawable objects added by the user from this frame.
*/
public void clearDrawables() {
drawingPanel.clear(); // removes all drawables
drawingPanel.addDrawable(complexDataset); // puts complex dataset back into panel
showDataTable(false);
}
/**
* Gets Drawable objects added by the user of an assignable type. The list contains
* objects that are assignable from the class or interface.
*
* @param c the type of Drawable object
*
* @return the cloned list
*
* @see #getObjectOfClass(Class c)
*/
public synchronized <T extends Drawable> ArrayList<T> getDrawables(Class<T> c) {
ArrayList<T> list = super.getDrawables(c);
list.remove(complexDataset);
return list;
}
/**
* Clears all the stored complex data.
*/
public void clearData() {
complexDataset.clear();
dataTable.refreshTable();
drawingPanel.invalidateImage();
}
/**
* Sets the axes to use a logarithmetic scale.
*/
public void setLogScale(boolean xlog, boolean ylog) {
if(drawingPanel instanceof PlottingPanel) {
((PlottingPanel) drawingPanel).setLogScale(xlog, ylog);
}
}
/**
* Shows how phase angle is mapped to color.
*/
public void showLegend() {
complexDataset.showLegend();
}
/**
* Shows or hides the data table.
*
* @param show boolean
*/
public synchronized void showDataTable(boolean show) {
if(show) {
if((tableFrame==null)||!tableFrame.isDisplayable()) {
tableFrame = new DataTableFrame(getTitle()+" "+DisplayRes.getString("TableFrame.TitleAddOn.Data"), dataTable); //$NON-NLS-1$ //$NON-NLS-2$
tableFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}
dataTable.refreshTable();
dataTable.sort(0);
tableFrame.setVisible(true);
} else {
tableFrame.setVisible(false);
tableFrame.dispose();
tableFrame = null;
}
}
}
/*
* 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
*/