/* * 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 */