/*
* LineagesThroughTimeDialog.java
*
* Copyright (C) 2002-2009 Alexei Drummond and Andrew Rambaut
*
* This file is part of BEAST.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* BEAST 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package dr.app.tracer.analysis;
import dr.app.gui.components.RealNumberField;
import dr.app.gui.components.WholeNumberField;
import dr.app.gui.util.LongTask;
import dr.inference.trace.TraceDistribution;
import dr.inference.trace.TraceList;
import dr.stats.Variate;
import jam.framework.DocumentFrame;
import jam.panels.OptionsPanel;
import jebl.evolution.graphs.Node;
import jebl.evolution.io.ImportException;
import jebl.evolution.io.NewickImporter;
import jebl.evolution.io.NexusImporter;
import jebl.evolution.io.TreeImporter;
import jebl.evolution.trees.RootedTree;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.Arrays;
import java.util.List;
public class LineagesThroughTimeDialog {
private JFrame frame;
private final JButton button = new JButton("Choose File...");
private ActionListener buttonListener;
private final JTextField fileNameText = new JTextField("not selected", 16);
private File treeFile = null;
private WholeNumberField binCountField;
private JComboBox maxHeightCombo = new JComboBox(new String[]{
"Lower 95% HPD", "Median", "Mean", "Upper 95% HPD"});
private JComboBox rootHeightCombo;
private JCheckBox manualRangeCheckBox;
private RealNumberField minTimeField;
private RealNumberField maxTimeField;
private String rootHeightTrace = "None selected";
private RealNumberField ageOfYoungestField = new RealNumberField();
private OptionsPanel optionPanel;
public LineagesThroughTimeDialog(JFrame frame) {
this.frame = frame;
rootHeightCombo = new JComboBox();
binCountField = new WholeNumberField(2, 2000);
binCountField.setValue(100);
binCountField.setColumns(4);
manualRangeCheckBox = new JCheckBox("Use manual range for bins:");
maxTimeField = new RealNumberField(0.0, Double.MAX_VALUE);
maxTimeField.setColumns(12);
minTimeField = new RealNumberField(0.0, Double.MAX_VALUE);
minTimeField.setColumns(12);
ageOfYoungestField.setValue(0.0);
ageOfYoungestField.setColumns(12);
optionPanel = new OptionsPanel(12, 12);
}
private int findArgument(JComboBox comboBox, String argument) {
for (int i = 0; i < comboBox.getItemCount(); i++) {
String item = ((String) comboBox.getItemAt(i)).toLowerCase();
if (item.indexOf(argument) != -1) return i;
}
return -1;
}
public int showDialog(TraceList traceList, TemporalAnalysisFrame temporalAnalysisFrame) {
setArguments(temporalAnalysisFrame);
for (int j = 0; j < traceList.getTraceCount(); j++) {
String statistic = traceList.getTraceName(j);
rootHeightCombo.addItem(statistic);
}
int index = findArgument(rootHeightCombo, rootHeightTrace);
if (index == -1) index = findArgument(rootHeightCombo, "root");
if (index == -1) index = findArgument(rootHeightCombo, "height");
if (index == -1) index = 0;
rootHeightCombo.setSelectedIndex(index);
final JOptionPane optionPane = new JOptionPane(optionPanel,
JOptionPane.QUESTION_MESSAGE,
JOptionPane.OK_CANCEL_OPTION,
null,
null,
null);
optionPane.setBorder(new EmptyBorder(12, 12, 12, 12));
button.removeActionListener(buttonListener);
buttonListener = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
FileDialog dialog = new FileDialog(frame,
"Open Trees Log File...",
FileDialog.LOAD);
dialog.setVisible(true);
if (dialog.getFile() == null) {
// the dialog was cancelled...
return;
}
treeFile = new File(dialog.getDirectory(), dialog.getFile());
fileNameText.setText(treeFile.getName());
}
};
button.addActionListener(buttonListener);
final JDialog dialog = optionPane.createDialog(frame, "Lineages Through Time Analysis");
dialog.pack();
int result = JOptionPane.CANCEL_OPTION;
boolean done;
do {
done = true;
dialog.setVisible(true);
Integer value = (Integer) optionPane.getValue();
if (value != null && value != -1) {
result = value;
}
if (result == JOptionPane.OK_OPTION) {
if (treeFile == null) {
JOptionPane.showMessageDialog(frame, "A tree file was not selected",
"Error parsing file",
JOptionPane.ERROR_MESSAGE);
done = false;
} else {
rootHeightTrace = (String) rootHeightCombo.getSelectedItem();
}
}
} while (!done);
return result;
}
private void setArguments(TemporalAnalysisFrame temporalAnalysisFrame) {
optionPanel.removeAll();
if (treeFile != null) {
fileNameText.setText(treeFile.getName());
}
fileNameText.setEditable(false);
JPanel panel = new JPanel(new BorderLayout(0, 0));
panel.add(fileNameText, BorderLayout.CENTER);
panel.add(button, BorderLayout.EAST);
optionPanel.addComponentWithLabel("Trees Log File: ", panel);
optionPanel.addSeparator();
optionPanel.addComponentWithLabel("Maximum time is the root height's:", maxHeightCombo);
optionPanel.addComponentWithLabel("Select the trace of the root height:", rootHeightCombo);
if (temporalAnalysisFrame == null) {
optionPanel.addSeparator();
optionPanel.addComponentWithLabel("Number of bins:", binCountField);
optionPanel.addSpanningComponent(manualRangeCheckBox);
final JLabel label1 = optionPanel.addComponentWithLabel("Minimum time:", minTimeField);
final JLabel label2 = optionPanel.addComponentWithLabel("Maximum time:", maxTimeField);
if (manualRangeCheckBox.isSelected()) {
label1.setEnabled(true);
minTimeField.setEnabled(true);
label2.setEnabled(true);
maxTimeField.setEnabled(true);
} else {
label1.setEnabled(false);
minTimeField.setEnabled(false);
label2.setEnabled(false);
maxTimeField.setEnabled(false);
}
manualRangeCheckBox.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent changeEvent) {
if (manualRangeCheckBox.isSelected()) {
label1.setEnabled(true);
minTimeField.setEnabled(true);
label2.setEnabled(true);
maxTimeField.setEnabled(true);
} else {
label1.setEnabled(false);
minTimeField.setEnabled(false);
label2.setEnabled(false);
maxTimeField.setEnabled(false);
}
}
});
}
optionPanel.addSeparator();
optionPanel.addComponentWithLabel("Age of youngest tip:", ageOfYoungestField);
JLabel label3 = new JLabel(
"<html>You can set the age of sampling of the most recent tip in<br>" +
"the tree. If this is set to zero then the plot is shown going<br>" +
"backwards in time, otherwise forwards in time.</html>");
label3.setFont(label3.getFont().deriveFont(((float) label3.getFont().getSize() - 2)));
optionPanel.addSpanningComponent(label3);
}
Timer timer = null;
public void createLineagesThroughTimeFrame(TraceList traceList, DocumentFrame parent) {
TemporalAnalysisFrame frame;
int binCount = binCountField.getValue();
double minTime;
double maxTime;
boolean manualRange = manualRangeCheckBox.isSelected();
if (manualRange) {
minTime = minTimeField.getValue();
maxTime = maxTimeField.getValue();
if (minTime >= maxTime) {
JOptionPane.showMessageDialog(parent,
"The minimum time value should be less than the maximum.",
"Error creating Lineages-Through-Time plot",
JOptionPane.ERROR_MESSAGE);
return;
}
frame = new TemporalAnalysisFrame(parent, "", binCount, minTime, maxTime);
} else {
frame = new TemporalAnalysisFrame(parent, "", binCount);
}
frame.initialize();
addToTemporalAnalysis(traceList, frame);
}
public void addToTemporalAnalysis(TraceList traceList, TemporalAnalysisFrame frame) {
final AnalyseLTTTask analyseTask = new AnalyseLTTTask(traceList,
treeFile,
frame);
final ProgressMonitor progressMonitor = new ProgressMonitor(frame,
"Analysing Lineages-Through-Time",
"", 0, analyseTask.getLengthOfTask());
progressMonitor.setMillisToPopup(0);
progressMonitor.setMillisToDecideToPopup(0);
timer = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
progressMonitor.setProgress(analyseTask.getCurrent());
if (progressMonitor.isCanceled() || analyseTask.done()) {
progressMonitor.close();
analyseTask.stop();
timer.stop();
}
}
});
analyseTask.go();
timer.start();
}
class AnalyseLTTTask extends LongTask {
TraceList traceList;
TemporalAnalysisFrame frame;
File treeFile;
int binCount;
boolean rangeSet;
double minTime;
double maxTime;
double ageOfYoungest;
int stateCount;
private int lengthOfTask = 0;
private int current = 0;
public AnalyseLTTTask(TraceList traceList, File treeFile, TemporalAnalysisFrame frame) {
this.traceList = traceList;
this.frame = frame;
this.treeFile = treeFile;
this.binCount = frame.getBinCount();
this.rangeSet = frame.isRangeSet();
ageOfYoungest = ageOfYoungestField.getValue();
lengthOfTask = traceList.getStateCount() + binCount;
stateCount = traceList.getStateCount();
}
public int getCurrent() {
return current;
}
public int getLengthOfTask() {
return lengthOfTask;
}
public String getDescription() {
return "Calculating LLT...";
}
public String getMessage() {
return null;
}
public Object doWork() {
List heights = traceList.getValues(traceList.getTraceIndex(rootHeightTrace));
TraceDistribution distribution = new TraceDistribution(heights,
traceList.getTrace(traceList.getTraceIndex(rootHeightTrace)).getTraceType(), traceList.getStepSize());
double timeMean = distribution.getMean();
double timeMedian = distribution.getMedian();
double timeUpper = distribution.getUpperHPD();
double timeLower = distribution.getLowerHPD();
double maxHeight = 0.0;
switch (maxHeightCombo.getSelectedIndex()) {
// setting a timeXXXX to -1 means that it won't be displayed...
case 0:
maxHeight = timeLower;
break;
case 1:
maxHeight = timeMedian;
break;
case 2:
maxHeight = timeMean;
break;
case 3:
maxHeight = timeUpper;
break;
}
if (rangeSet) {
minTime = frame.getMinTime();
maxTime = frame.getMaxTime();
} else {
if (ageOfYoungest > 0.0) {
minTime = ageOfYoungest - maxHeight;
maxTime = ageOfYoungest;
} else {
minTime = 0.0;
maxTime = maxHeight - ageOfYoungest;
}
frame.setRange(minTime, maxTime);
}
if (ageOfYoungest > 0.0) {
// reverse them if ageOfYoungest is set positive
timeMean = ageOfYoungest - timeMean;
timeMedian = ageOfYoungest - timeMedian;
timeUpper = ageOfYoungest - timeUpper;
timeLower = ageOfYoungest - timeLower;
// setting a timeXXXX to -1 means that it won't be displayed...
if (minTime >= timeLower) timeLower = -1;
if (minTime >= timeMean) timeMean = -1;
if (minTime >= timeMedian) timeMedian = -1;
if (minTime >= timeUpper) timeUpper = -1;
} else {
// otherwise use use ageOfYoungest as an offset
timeMean = timeMean - ageOfYoungest;
timeMedian = timeMedian - ageOfYoungest;
timeUpper = timeUpper - ageOfYoungest;
timeLower = timeLower - ageOfYoungest;
// setting a timeXXXX to -1 means that it won't be displayed...
if (maxTime <= timeLower) timeLower = -1;
if (maxTime <= timeMean) timeMean = -1;
if (maxTime <= timeMedian) timeMedian = -1;
if (maxTime <= timeUpper) timeUpper = -1;
}
double delta = (maxTime - minTime) / (binCount - 1);
try {
BufferedReader reader = new BufferedReader(new FileReader(treeFile));
String line = reader.readLine();
TreeImporter importer;
if (line.toUpperCase().startsWith("#NEXUS")) {
importer = new NexusImporter(reader);
} else {
importer = new NewickImporter(reader, false);
}
int burnin = traceList.getBurnIn();
int skip = burnin / traceList.getStepSize();
int state = 0;
while (importer.hasTree() && state < skip) {
importer.importNextTree();
state += 1;
}
current = 0;
// the age of the end of this group
double[][] branchingTimes = new double[stateCount][];
//int tips = 0;
state = 0;
try {
while (importer.hasTree()) {
RootedTree tree = (RootedTree) importer.importNextTree();
branchingTimes[state] = new double[tree.getInternalNodes().size()];
int i = 0;
for (Node node : tree.getInternalNodes()) {
branchingTimes[state][i] = tree.getHeight(node);
i++;
}
Arrays.sort(branchingTimes[state]);
state += 1;
current += 1;
}
} catch (ImportException ie) {
JOptionPane.showMessageDialog(frame, "Error parsing file: " + ie.getMessage(),
"Error parsing file",
JOptionPane.ERROR_MESSAGE);
} catch (Exception ex) {
JOptionPane.showMessageDialog(frame, "Fatal exception (email the authors):" + ex.getMessage(),
"Fatal exception",
JOptionPane.ERROR_MESSAGE);
ex.printStackTrace(System.out);
}
Variate.D[] bins = new Variate.D[binCount];
double height;
if (ageOfYoungest > 0.0) {
height = ageOfYoungest - maxTime;
} else {
height = ageOfYoungest;
}
double n = branchingTimes[0].length;
for (int k = 0; k < binCount; k++) {
bins[k] = new Variate.D();
if (height >= 0.0 && height <= maxHeight) {
for (state = 0; state < stateCount; state++) {
int index = 0;
while (index < branchingTimes[state].length && branchingTimes[state][index] < height) {
index += 1;
}
double lineageCount = 1;
if (index < branchingTimes[state].length) {
lineageCount = n - index + 1;
}
bins[k].add(lineageCount);
}
}
height += delta;
current += 1;
}
Variate.D xData = new Variate.D();
Variate.D yDataMean = new Variate.D();
Variate.D yDataMedian = new Variate.D();
Variate.D yDataUpper = new Variate.D();
Variate.D yDataLower = new Variate.D();
double t;
if (ageOfYoungest > 0.0) {
t = maxTime;
} else {
t = minTime;
}
for (Variate.D bin : bins) {
xData.add(t);
if (bin.getCount() > 0) {
yDataMean.add(bin.getMean());
yDataMedian.add(bin.getQuantile(0.5));
yDataLower.add(bin.getQuantile(0.025));
yDataUpper.add(bin.getQuantile(0.975));
} else {
yDataMean.add(Double.NaN);
yDataMedian.add(Double.NaN);
yDataLower.add(Double.NaN);
yDataUpper.add(Double.NaN);
}
if (ageOfYoungest > 0.0) {
t -= delta;
} else {
t += delta;
}
}
frame.addDemographic("Lineages Through Time: " + traceList.getName(), xData,
yDataMean, yDataMedian,
yDataUpper, yDataLower,
timeMean, timeMedian,
timeUpper, timeLower);
} catch (java.io.IOException ioe) {
JOptionPane.showMessageDialog(frame, "Error reading file: " + ioe.getMessage(),
"Error reading file",
JOptionPane.ERROR_MESSAGE);
} catch (Exception ex) {
JOptionPane.showMessageDialog(frame, "Fatal exception (email the authors):" + ex.getMessage(),
"Fatal exception",
JOptionPane.ERROR_MESSAGE);
ex.printStackTrace(System.out);
}
return null;
}
}
}