PeriodAnalysis2DResultDialog.java
/**
* VStar: a statistical analysis tool for variable star data.
* Copyright (C) 2009 AAVSO (http://www.aavso.org/)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.aavso.tools.vstar.ui.dialog.period;
import java.awt.Component;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.swing.JTabbedPane;
import org.aavso.tools.vstar.plugin.PluginComponentFactory;
import org.aavso.tools.vstar.plugin.period.PeriodAnalysisComponentFactory;
import org.aavso.tools.vstar.plugin.period.PeriodAnalysisDialogBase;
import org.aavso.tools.vstar.ui.NamedComponent;
import org.aavso.tools.vstar.ui.dialog.DoubleField;
import org.aavso.tools.vstar.ui.dialog.MessageBox;
import org.aavso.tools.vstar.ui.dialog.MultiEntryComponentDialog;
import org.aavso.tools.vstar.ui.mediator.Mediator;
import org.aavso.tools.vstar.ui.mediator.message.HarmonicSearchResultMessage;
import org.aavso.tools.vstar.ui.mediator.message.PeriodAnalysisSelectionMessage;
import org.aavso.tools.vstar.ui.mediator.message.PeriodChangeMessage;
import org.aavso.tools.vstar.ui.model.list.PeriodAnalysisDataTableModel;
import org.aavso.tools.vstar.ui.model.plot.PeriodAnalysis2DPlotModel;
import org.aavso.tools.vstar.util.locale.LocaleProps;
import org.aavso.tools.vstar.util.model.Harmonic;
import org.aavso.tools.vstar.util.notification.Listener;
import org.aavso.tools.vstar.util.period.IPeriodAnalysisAlgorithm;
import org.aavso.tools.vstar.util.period.IPeriodAnalysisDatum;
import org.aavso.tools.vstar.util.period.PeriodAnalysisCoordinateType;
import org.aavso.tools.vstar.util.prefs.NumericPrecisionPrefs;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.DatasetRenderingOrder;
/**
* This dialog class is used to visualise period analysis results.
*/
@SuppressWarnings("serial")
public class PeriodAnalysis2DResultDialog extends PeriodAnalysisDialogBase {
private String seriesTitle;
private String chartTitle;
private IPeriodAnalysisAlgorithm algorithm;
private Map<PeriodAnalysisCoordinateType, List<Double>> resultDataMap;
private List<PeriodAnalysis2DPlotModel> plotModels;
private PeriodAnalysisDataTableModel dataTableModel;
private PeriodAnalysisDataTableModel topHitsTableModel;
private PeriodAnalysisDataTablePane dataTablePane;
private PeriodAnalysisTopHitsTablePane topHitsTablePane;
private List<PeriodAnalysis2DChartPane> plotPanes;
private IPeriodAnalysisDatum selectedDataPoint;
private Listener<PeriodAnalysisSelectionMessage> periodAnalysisListener;
private JTabbedPane tabbedPane;
private String findHarmonicsButtonText;
private static final double FREQUENCY_RELATIVE_TOLERANCE = 1e-3;
private double currentTolerance = FREQUENCY_RELATIVE_TOLERANCE;
DoubleField toleranceField;
/**
* Constructor
*
* @param title
* The title for the chart.
* @param seriesTitle
* The source series sub-title for the chart.
* @param algorithm
* The period analysis algorithm.
* @param dataCoordTypes
* The period analysis coordinate types to be used in this
* dialog.
* @param searchType
* The coordinate associated with the type of the search:
* frequency or period.
*/
public PeriodAnalysis2DResultDialog(String title, String seriesTitle,
IPeriodAnalysisAlgorithm algorithm,
PeriodAnalysisCoordinateType[] dataCoordTypes,
PeriodAnalysisCoordinateType searchType) {
super(title, false, true, true);
assert searchType == PeriodAnalysisCoordinateType.PERIOD
|| searchType == PeriodAnalysisCoordinateType.FREQUENCY;
findHarmonicsButtonText = LocaleProps.get("FIND_HARMONICS_BUTTON");
selectedDataPoint = null;
resultDataMap = algorithm.getResultSeries();
this.seriesTitle = seriesTitle;
this.chartTitle = title;
this.algorithm = algorithm;
dataTableModel = new PeriodAnalysisDataTableModel(dataCoordTypes,
resultDataMap);
topHitsTableModel = new PeriodAnalysisDataTableModel(dataCoordTypes,
algorithm.getTopHits());
plotModels = new ArrayList<PeriodAnalysis2DPlotModel>();
if (searchType == PeriodAnalysisCoordinateType.PERIOD) {
// Period vs Power
plotModels.add(new PeriodAnalysis2DPlotModel(resultDataMap,
PeriodAnalysisCoordinateType.PERIOD,
PeriodAnalysisCoordinateType.POWER, false));
// Period vs Amplitude
plotModels.add(new PeriodAnalysis2DPlotModel(resultDataMap,
PeriodAnalysisCoordinateType.PERIOD,
PeriodAnalysisCoordinateType.SEMI_AMPLITUDE, false));
} else if (searchType == PeriodAnalysisCoordinateType.FREQUENCY) {
// Frequency vs Power
plotModels.add(new PeriodAnalysis2DPlotModel(resultDataMap,
PeriodAnalysisCoordinateType.FREQUENCY,
PeriodAnalysisCoordinateType.POWER, false));
// Frequency vs Amplitude
plotModels.add(new PeriodAnalysis2DPlotModel(resultDataMap,
PeriodAnalysisCoordinateType.FREQUENCY,
PeriodAnalysisCoordinateType.SEMI_AMPLITUDE, false));
}
// If the window is closed in some other way than via the Dismiss
// button.
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
cleanup();
}
});
prepareDialog();
startup();
}
protected Component createContent() {
tabbedPane = createTabs();
return tabbedPane;
}
// Return the tabs containing table and plots of frequency vs one of the
// dependent variables of period, power, or amplitude. Is this what we want,
// or something different?
private JTabbedPane createTabs() {
List<NamedComponent> namedComponents = new ArrayList<NamedComponent>();
plotPanes = new ArrayList<PeriodAnalysis2DChartPane>();
// Add plots.
int n = 0;
for (PeriodAnalysis2DPlotModel model : plotModels) {
boolean permitlogarithmic = model.getRangeType() == PeriodAnalysisCoordinateType.POWER;
PeriodAnalysis2DChartPane plot = PeriodAnalysisComponentFactory
.createLinePlot(chartTitle, seriesTitle, model,
permitlogarithmic);
PeriodAnalysis2DChartPane topHitsPlot = PeriodAnalysisComponentFactory
.createScatterPlot(
chartTitle,
seriesTitle,
new PeriodAnalysis2DPlotModel(algorithm
.getTopHits(), model.getDomainType(), model
.getRangeType(), model.isLogarithmic()),
permitlogarithmic);
// Add the above line plot's model to the scatter plot.
// Render the scatter plot last so the "handles" will be
// the first items selected by the mouse.
JFreeChart chart = topHitsPlot.getChart();
chart.getXYPlot().setDataset(PeriodAnalysis2DChartPane.DATA_SERIES,
model);
chart.getXYPlot().setRenderer(
PeriodAnalysis2DChartPane.DATA_SERIES,
plot.getChart().getXYPlot().getRenderer());
chart.getXYPlot().setDatasetRenderingOrder(
DatasetRenderingOrder.REVERSE);
plot = topHitsPlot;
String tabName = model.getRangeType() + " vs "
+ model.getDomainType();
namedComponents.add(new NamedComponent(tabName, plot));
plot.setChartPaneID("PlotPane" + Integer.toString(n));
plotPanes.add(plot);
n += 1;
}
// Add data table view.
dataTablePane = new PeriodAnalysisDataTablePane(dataTableModel,
algorithm, false);
dataTablePane.setTablePaneID("DataTable");
namedComponents.add(new NamedComponent(LocaleProps.get("DATA_TAB"),
dataTablePane));
// Add top-hits table view.
topHitsTablePane = new PeriodAnalysisTopHitsTablePane(
topHitsTableModel, dataTableModel, algorithm);
topHitsTablePane.setTablePaneID("TopHitsTable");
namedComponents.add(new NamedComponent(LocaleProps.get("TOP_HITS_TAB"),
topHitsTablePane));
return PluginComponentFactory.createTabs(namedComponents);
}
// The new phase plot button will only be enabled when a period
// analysis selection messOage has been received by this class,
// so we *know* without having to ask that there is a selected
// row in the data table.
@Override
protected void newPhasePlotButtonAction() {
PeriodChangeMessage message = new PeriodChangeMessage(this,
selectedDataPoint.getPeriod());
message.setTag(this.getName());
Mediator.getInstance().getPeriodChangeNotifier()
.notifyListeners(message);
}
@Override
protected void findHarmonicsButtonAction() {
String componentID = null;
Component c = tabbedPane.getSelectedComponent();
if (c instanceof PeriodAnalysis2DChartPane) {
componentID = ((PeriodAnalysis2DChartPane)c).getChartPaneID();
} else if (c instanceof PeriodAnalysisDataTablePane) {
componentID = ((PeriodAnalysisDataTablePane)c).getTablePaneID();
}
if (componentID == null) {
MessageBox.showMessageDialog("Find Harmonic", "Not implemented for this view");
return;
}
MultiEntryComponentDialog paramDialog = createParamDialog();
if (paramDialog.isCancelled()) {
return;
}
currentTolerance = toleranceField.getValue();
//List<Double> data = algorithm.getResultSeries().get(PeriodAnalysisCoordinateType.FREQUENCY);
List<Double> data = algorithm.getTopHits().get(PeriodAnalysisCoordinateType.FREQUENCY);
List<Harmonic> harmonics = findHarmonics(selectedDataPoint.getFrequency(), data, currentTolerance);
HarmonicSearchResultMessage msg = new HarmonicSearchResultMessage(this,
harmonics, selectedDataPoint, currentTolerance);
msg.setTag(this.getName());
msg.setIDstring(componentID);
Mediator.getInstance().getHarmonicSearchNotifier().notifyListeners(msg);
}
private MultiEntryComponentDialog createParamDialog() {
toleranceField = new DoubleField("Relative Frequency Tolerance", 0.0, 1.0, currentTolerance);
return new MultiEntryComponentDialog("Find Harmonics", toleranceField);
}
// Enable the new phase plot button and store the selected
// period analysis data point.
private Listener<PeriodAnalysisSelectionMessage> createPeriodAnalysisListener() {
return new Listener<PeriodAnalysisSelectionMessage>() {
public void update(PeriodAnalysisSelectionMessage info) {
if (!Mediator.isMsgForDialog(PeriodAnalysis2DResultDialog.this, info))
return;
setNewPhasePlotButtonState(true);
setFindHarmonicsButtonState(true);
selectedDataPoint = info.getDataPoint();
// Sometimes the cross-hair does not coincide with the real selection.
// Let's indicate the selected frequency on the button...
findHarmonicsButton.setText(findHarmonicsButtonText + " [" + NumericPrecisionPrefs.formatOther(selectedDataPoint.getFrequency()) + "]");
}
public boolean canBeRemoved() {
return true;
}
};
}
@Override
public void startup() {
periodAnalysisListener = createPeriodAnalysisListener();
Mediator.getInstance().getPeriodAnalysisSelectionNotifier()
.addListener(periodAnalysisListener);
dataTablePane.startup();
topHitsTablePane.startup();
for (PeriodAnalysis2DChartPane plotPane : plotPanes) {
plotPane.startup();
}
}
@Override
public void cleanup() {
Mediator.getInstance().getPeriodAnalysisSelectionNotifier()
.removeListenerIfWilling(periodAnalysisListener);
dataTablePane.cleanup();
topHitsTablePane.cleanup();
for (PeriodAnalysis2DChartPane plotPane : plotPanes) {
plotPane.cleanup();
}
}
}