/* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * DensiTree.java * Copyright Remco Bouckaert remco@cs.auckland.ac.nz (C) 2011 - 2013 */ package viz; /** * Shows sets of cluster trees represented in Newick format as dendrograms and other graphs. * There are 2 modes of viewing a tree set * 1. draw all trees in the set * 2. browsing through the set of trees/animate through trees in the set, drawing them one by one * Restriction: binary trees only * * @author Remco Bouckaert (rrb@xm.co.nz, remco@cs.auckland.ac.nz, remco@cs.waikato.ac.nz) * @version $Revision: 2.2.5 $ */ // the magic sentence to look for when releasing: //RRB: not for public release import jam.framework.DocumentFrame; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.event.*; import java.awt.image.BufferedImage; import java.awt.print.*; import java.io.*; import java.net.URL; import java.text.DecimalFormat; import java.util.*; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.imageio.ImageIO; import javax.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.filechooser.FileFilter; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import com.itextpdf.awt.PdfGraphics2D; import com.itextpdf.text.pdf.PdfContentByte; import com.itextpdf.text.pdf.PdfWriter; import viz.GridDrawer.GridMode; import viz.graphics.*; import viz.panel.BurninPanel; import viz.panel.CladePanel; import viz.panel.ColorPanel; import viz.panel.ExpandablePanel; import viz.panel.GeoPanel; import viz.panel.GridPanel; import viz.panel.LabelPanel; import viz.panel.LineWidthPanel; import viz.panel.ShowPanel; import viz.process.BranchLengthOptimiser; import viz.util.Util; public class DensiTree extends JPanel implements ComponentListener { final static String VERSION = "2.2.5"; final static String FRAME_TITLE = "DensiTree - Tree Set Visualizer"; // final static String CITATION = "Remco R. Bouckaert\n"+ // "DensiTree: making sense of sets of phylogenetic trees\n"+ // "Bioinformatics (2010) 26 (10): 1372-1373.\n"+ // "doi: 10.1093/bioinformatics/btq110"; final static String CITATION = "Remco R. Bouckaert & Joseph Heled\n"+ "DensiTree 2: Seeing Trees Through the Forest\n"+ "bioRxiv, 2014,\n" + "http://dx.doi.org/10.1101/012401\n"; static int instances = 1; /** flag for testing summary tree optimisation **/ public String m_sOptFile = null; /** number of tree in tree set to use as root canal tree **/ int m_iOptTree = -1; public Node m_optTree = null; /** user specified newick tree used for initialising the root canal tree -- lengths will be optimised **/ public String m_sOptTree = null; /** whether to optimise branch lengths on root canal tree or not **/ boolean m_bOptimiseRootCanalTree = false; static int B = 1; JFrame frame; public void setWaitCursor() { if (frame != null && frame.getCursor().getType() != Cursor.WAIT_CURSOR) { frame.setCursor(new Cursor(Cursor.WAIT_CURSOR)); } } public void setDefaultCursor() { if (frame != null && frame.getCursor().getType() != Cursor.DEFAULT_CURSOR) { frame.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } } private static final long serialVersionUID = 1L; /** path for icons */ public static final String ICONPATH = "viz/icons/"; /** * default tree branch length, used when that info is not in the Newick tree **/ final static double DEFAULT_LENGTH = 0.001f; /** same trees, but represented as Node data structure **/ public Node[] m_trees; public Node m_rootcanaltree; // contains summary trees, root canal refers to one of those public List<Node> m_summaryTree = new ArrayList<Node>(); /** * Trees represented as lines for drawing block trees Units are tree lengths * as represented in the Newick file The lines come in quartets * ((x1,y1)(x1,y2)(x3,y2),(x3,y3)) and are concatenated in a long array. The * final pair contains the line to the root. * **/ float[][] m_fLinesX; float[][] m_fLinesY; /** * Trees represented as lines for drawing triangular trees Units are tree * lengths as represented in the Newick file The lines come in triplets * ((x1,y1)(x2,y2)(x3,y3)) and are concatenated in a long array. The final * pair contains the line to the root. * **/ // float[][] m_fTLinesX; // float[][] m_fTLinesY; /** * Width of individual lines, determined by some info in the metadata (if * any) If specified, this only applies to block trees. * **/ public int[][] m_nLineColor; public float[][] m_fLineWidth; public float[][] m_fTopLineWidth; public int[][] m_nCLineColor; public float[][] m_fCLineWidth; public float[][] m_fTopCLineWidth; float[][] m_fRLinesX; float[][] m_fRLinesY; public int[][] m_nRLineColor; public float[][] m_fRLineWidth; public float[][] m_fRTopLineWidth; /** flag indicating the attribute should be interpreted as categorial **/ public boolean m_bColorByCategory = false; public Vector<String> m_sLabels; public BufferedImage[] m_LabelImages; public int m_nImageSize = 20; public boolean m_bHideLabels = false; /** labels of leafs **/ /** nr of labels in dataset **/ public int m_nNrOfLabels = 0; /** position information for the leafs (if available) **/ public Vector<Float> m_fLongitude; public Vector<Float> m_fLatitude; boolean m_bInvertLongitude = false; /** extreme values for position information **/ public float m_fMaxLong, m_fMaxLat, m_fMinLong, m_fMinLat; /** name of file containing locations **/ String m_sKMLFile = null; static float GEO_OFFSET = 3.0f; /** order of appearance of leafs, used to determine x-coordinates of leafs **/ int[] m_nOrder; /** reverse of m_nOrder, useful for reordering **/ int[] m_nRevOrder; /** Topology number of the tree, in order of appearance in tree set **/ int[] m_nTopology; /** * Topology number for particular tree in order of popularity (most popular * = 0, next most popular = 1, etc.) Useful for coloring trees. **/ int[] m_nTopologyByPopularity; /** nr of distinct topologies **/ int m_nTopologies; /** * relative weight of tree topology measured by its frequency of appearance * in the set. Adds to unity. */ float[] m_fTreeWeight; /** as m_trees, but for consensus trees **/ Node[] m_cTrees; /** as m_nLines, but for consensus trees **/ float[][] m_fCLinesX; float[][] m_fCLinesY; /** as m_nTLines, but for consensus trees **/ // float[][] m_fCTLinesX; // float[][] m_fCTLinesY; /** height of highest tree **/ public float m_fHeight = 0; /** scale factors for drawing to screen **/ float m_fScaleX = 10; float m_fScaleY = 10; float m_fScaleGX = 10; float m_fScaleGY = 10; /** global scale for zooming **/ float m_fScale = 1.0f; /** user scale, for scaling grid and clade heights **/ public float m_fUserScale = 1.0f; /** determines which part of the tree-set is shown wrt maximum tree height **/ float m_fTreeOffset = 0; float m_fTreeScale = 1; public GridDrawer m_gridDrawer; public CladeDrawer m_cladeDrawer; double m_cladeThreshold = 1e-4; /** smallest support for it to be considered a clade, default 1% **/ public double m_smallestCladeSupport = 0.01; /** flag to allow leafs to be draw and dragged around. * As a side effect, internal clades will not be positioned correctly, * so this flag can only be set with the -allowLeafsToBeMovedIKnowThisMessesUpInternalCladePositions flag **/ boolean m_bLeafCladeSelection = false; /** flag to indicate not to draw anything due to being busy initialising **/ boolean m_bInitializing; /** jitter of x-positions for x-coordinate **/ int m_nJitter = 0; /** random nr generator used for applying jitter **/ Random m_random = new Random(); /** intensity with which the trees are drawn (multiplier for alpha channel) **/ float m_fTreeIntensity = 1.0f; float m_fCTreeIntensity = 1.0f; /** width of lines used for drawing trees, etc. **/ int m_nTreeWidth = 1; int m_nCTreeWidth = 4; public int m_nGeoWidth = 1; /** width of labels, when root at left **/ public int m_nLabelWidth = 100; /** flags whether a leaf node is selected **/ public boolean[] m_bSelection; /** flag to indicate the selection was changed but image was not updated yet **/ public boolean m_bSelectionChanged; /** rectangles with on screen coordinates of labels **/ Rectangle[] m_bLabelRectangle; /** rectangles with geographic locations on screen **/ Rectangle[] m_bGeoRectangle; /** selection rectangle drawn through dragging with left mouse button */ Rectangle m_nSelectedRect = null; /** * burn in = nr of trees ignored at the start of tree file, can be set by * command line option **/ public int m_nBurnIn = 10; public boolean m_bBurnInIsPercentage = true; double m_w = 0; /** current directory for opening files **/ String m_sDir = System.getProperty("user.dir"); /** array of various colors for color coding different topologies **/ public Color[] m_color; public static int HEIGHTCOLOR = 6, CONSCOLOR = 4, LABELCOLOR = 5, BGCOLOR = 7, GEOCOLOR = 8, ROOTCANALCOLOR=9; /** image used for the background **/ public BufferedImage m_bgImage; /** * bounding box for area containted in bfImage, derived from the image name: * if the name matches (lat0,long0)x(lat1,long1) e.g. NZ(-40,140)x(-10,180) * it will assume the image covers the box 40 South 140 East to 10 South, * 180 East. NB first coordinate should contain lower values, and the second * coordinate the higher values. */ double[] m_fBGImageBox = { -180, -90, 180, 90 }; // boolean m_bSurpressMetadata = true; /** * name of output file (if any) when batch processing. Typically used to * dump a bitmap file in. **/ String m_sOutputFile = null; /** * Flag to indicate 90% HPD and median should be shown. This only makes * sense when one of the meta data based orderings is used */ boolean m_bShowBounds = false; /** * Extra indentation to labels. This is helpful for unrooted trees since it * shifts all labels with a constant amount. Units are in terms of tree * height. */ public float m_fLabelIndent = 0.0f; /** * Flag to indicate image should be recorded Frames that are drawn while * refreshing screen are saved in /tmp/frame<nr>.jpg if possible. */ boolean m_bRecord = false; int m_nFrameNr; public boolean m_bViewEditTree = false; public boolean m_bViewClades = false; public Set<Integer> m_cladeSelection = new HashSet<Integer>(); BufferedImage m_rotate; /** regular expression pattern for finding width information in metadata **/ public Pattern m_pattern; public Pattern m_patternTop; /** default regular expression **/ //final static String DEFAULT_PATTERN = "theta=([0-9\\.Ee-]+)"; // final static String DEFAULT_PATTERN = "([0-9\\.Ee-]+),([0-9\\.Ee-]+)"; //final static String DEFAULT_PATTERN = "([0-9\\.Ee-]+),"; final static String DEFAULT_PATTERN = ".*location=\"([^\"]*).*"; // final static String DEFAULT_PATTERN = .*dmv=\{(.*),(.*)\}.* // final static String DEFAULT_PATTERN = "s=([0-9\\.Ee-]+)"; // final static String DEFAULT_PATTERN = "([0-9\\.Ee-]+),y=([0-9\\.Ee-]+)"; /** string containing reg exp for position matching **/ public String m_sPattern = DEFAULT_PATTERN; public int m_iPatternForBottom = 1; public int m_iPatternForTop = 0; /** string containing reg exp for grouping taxa **/ String m_sColorPattern = null; /** index of color for a taxon **/ int[] m_iColor; /** flag to indicate that single child nodes are allowed **/ boolean m_bAllowSingleChild = false; /** flag to indicate that text should be rotated when root at top **/ public boolean m_bRotateTextWhenRootAtTop = false; /** * mode for determining the X location of an internal node 0 = centre of * nodes below 1 = centre of all taxa below */ public int m_Xmode = 0; /** * flag to indicate the X position needs to be corrected to prevent steep * angles **/ boolean m_bUseAngleCorrection = false; double m_fAngleCorrectionThresHold = 0.9; /** method used for ordering nodes **/ int m_nShuffleMode = NodeOrderer.DEFAULT; /** used to store name of tree file so that when burn-in changes, the tree set * can be reloaded */ public String m_sFileName; /** flag to indicate some meta data on the tree should be used for line widht **/ //public boolean m_bMetaDataForLineWidth = false; /** * Flag to indicate top of branch widths should be calculated from the bottom * of branch lengths by distributing weight proportional to left and right * bottom branches to top branch -- scaled to fit bottom of parent branch. */ public boolean m_bCorrectTopOfBranch = false; // /** indicator that only one group is in the pattern, so top of branch widths // * should be calculated from the bottom of branch information. // */ // boolean m_bTopWidthDiffersFromBottomWidth; /** flag indicating meta data is zero based, instead of relative * which makes minimum values at zero width. */ public boolean m_bWidthsAreZeroBased = true; /** for storing the PDF file name from CLI **/ String m_asPDF = null; /** thread for processing meta data **/ Thread thread = null; /** constructors **/ public DensiTree() { m_gridDrawer = new GridDrawer(this); m_cladeDrawer = new CladeDrawer(this); instances++; } public DensiTree(String[] args) { this(); System.out.println(banner()); m_bSelection = new boolean[0]; m_nRevOrder = new int[0]; m_cTrees = new Node[0]; m_trees = new Node[0]; initColors(); setSize(1000, 800); m_Panel = new TreeSetPanel(this); parseArgs(args); m_pattern = createPattern(); System.err.println(getSize().width + "x" + getSize().height); m_jScrollPane = new JScrollPane(m_Panel); makeToolbar(); makeMenuBar(); addComponentListener(this); this.setLayout(new BorderLayout()); this.add(m_jScrollPane, BorderLayout.CENTER); a_zoomout.setEnabled(false); a_zoomouttree.setEnabled(false); m_Panel.setPreferredSize(getSize()); java.net.URL tempURL = ClassLoader.getSystemResource(ICONPATH + "rotate.png"); if (tempURL != null) { try { m_rotate = ImageIO.read(tempURL); } catch (Exception e) { e.printStackTrace(); } } } // c'tor public Pattern createPattern() { String sPattern = ""; for (int i = 0; i < m_iPatternForBottom; i++) { sPattern += "[0-9\\.Ee-]+[^0-9]+"; } sPattern += "([0-9\\.Ee-]+)"; if (m_iPatternForTop > m_iPatternForBottom) { //sPattern += "[^0-9]+"; for (int i = m_iPatternForBottom + 1; i < m_iPatternForTop; i++) { sPattern += "[^0-9]+[0-9\\.Ee-]+"; } sPattern += "[^0-9]+([0-9\\.Ee-]+)"; } return Pattern.compile(sPattern); } void initColors() { m_color = new Color[10 + 11 + 50]; m_color[0] = Color.getColor("color.1", Color.blue); m_color[1] = Color.getColor("color.2", Color.red); m_color[2] = Color.getColor("color.3", Color.green); m_color[3] = Color.getColor("color.default", new Color(0, 100, 25)); m_color[CONSCOLOR] = Color.getColor("color.cons", Color.blue); m_color[LABELCOLOR] = Color.getColor("color.label", Color.blue); m_color[HEIGHTCOLOR] = Color.getColor("color.height", Color.gray); m_color[BGCOLOR] = Color.getColor("color.bg", Color.white); m_color[GEOCOLOR] = Color.getColor("color.bg", Color.orange); m_color[ROOTCANALCOLOR] = Color.getColor("color.rootcanal", Color.blue); int k = GEOCOLOR + 1; m_color[k++] = Color.blue; m_color[k++] = Color.green; m_color[k++] = Color.red; m_color[k++] = Color.gray; m_color[k++] = Color.orange; m_color[k++] = Color.yellow; m_color[k++] = Color.pink; m_color[k++] = Color.black; m_color[k++] = Color.cyan; m_color[k++] = Color.darkGray; m_color[k++] = Color.magenta; m_color[k++] = new Color(100, 200, 25); ; // m_color[k++] = new Color(100, 0, 25); // m_color[k++] = new Color(25, 0, 100); // m_color[k++] = new Color(0, 25, 100); // m_color[k++] = new Color(0, 100, 25); // m_color[k++] = new Color(100, 25, 100); // m_color[k++] = new Color(25, 100, 100); // m_color[k++] = new Color(100, 100, 100); for (float saturation = 0.9f; saturation >= 0.0f; saturation -= 0.2) { for (float hue = 0.0f; hue < 1.0f; hue += 0.1) { m_color[k++] = new Color(Color.HSBtoRGB(hue+saturation/10, saturation, 0.9f)); } } } // initColors /** parse command line arguments, and load file if specified **/ void parseArgs(String[] args) { // check whether there is a config file to pick up arguments from File cfgFile = new File(".densitree"); if (cfgFile.exists()) { List<String> cfgArgs = new ArrayList<String>(); try { BufferedReader fin = new BufferedReader(new FileReader(cfgFile)); //StringBuffer buf = new StringBuffer(); String sStr = null; while (fin.ready()) { sStr = fin.readLine(); if (sStr.length() > 0 && !sStr.matches("^\\s*$")) { cfgArgs.add(sStr); } } fin.close(); for (String arg : args) { cfgArgs.add(arg); } args = cfgArgs.toArray(new String[]{}); } catch (Exception e) { System.err.println(e.getMessage()); System.err.println("WARNING: could not process cfg file"); } } // process arguments int i = 0; try { while (i < args.length) { int iOld = i; if (i < args.length - 1) { if (args[i].equals("")) { i += 1; } else if (args[i].equals("-c")) { m_fCTreeIntensity = Float.parseFloat(args[i + 1]); i += 2; } else if (args[i].equals("-i")) { m_fTreeIntensity = Float.parseFloat(args[i + 1]); i += 2; } else if (args[i].equals("-j")) { m_nJitter = (int) Float.parseFloat(args[i + 1]); i += 2; } else if (args[i].equals("-w")) { m_nCTreeWidth = (int) Float.parseFloat(args[i + 1]); i += 2; } else if (args[i].equals("-v")) { m_nTreeWidth = (int) Float.parseFloat(args[i + 1]); i += 2; } else if (args[i].equals("-f")) { m_nAnimationDelay = (int) Float.parseFloat(args[i + 1]); i += 2; } else if (args[i].equals("-t")) { m_Panel.m_nDrawThreads = (int) Float.parseFloat(args[i + 1]); if (m_Panel.m_nDrawThreads < 1) { m_Panel.m_nDrawThreads = 1; } i += 2; } else if (args[i].equals("-b")) { m_nBurnIn = (int) Float.parseFloat(args[i + 1]); i += 2; } else if (args[i].equals("-geo")) { String[] sStrs = args[i + 1].split("x"); int nWidth = Integer.parseInt(sStrs[0]); int nHeight = Integer.parseInt(sStrs[1]); setSize(nWidth, nHeight); i += 2; } else if (args[i].equals("-geooffset")) { GEO_OFFSET = Float.parseFloat(args[i + 1]); i += 2; } else if (args[i].equals("-invertLongitude")) { m_bInvertLongitude = true; i += 1; } else if (args[i].equals("-scalemode")) { String sMode = args[i+1].toLowerCase(); if (sMode.equals("none")) { m_gridDrawer.m_nGridMode = GridMode.NONE; } else if (sMode.equals("short")) { m_gridDrawer.m_nGridMode = GridMode.SHORT; } else if (sMode.equals("full")) { m_gridDrawer.m_nGridMode = GridMode.FULL; } else throw new Exception("expected scalemode to be NONE, SHORT or FULL"); i += 2; } else if (args[i].equals("-li")) { m_fLabelIndent = Float.parseFloat(args[i + 1]); i += 2; } else if (args[i].equals("-o")) { m_sOutputFile = args[i + 1]; i += 2; } else if (args[i].equals("-kml")) { m_sKMLFile = args[i + 1]; //loadKML(args[i + 1]); i += 2; } else if (args[i].equals("-geowidth")) { m_nGeoWidth = Integer.parseInt(args[i + 1]); i += 2; } else if (args[i].equals("-geocolor")) { m_color[GEOCOLOR] = Color.decode(args[i + 1]); i += 2; } else if (args[i].equals("-bg")) { try { loadBGImage(args[i + 1]); // m_bgImage = ImageIO.read(new File(args[i+1])); } catch (Exception e) { System.err.println("Error loading file: " + e.getMessage()); return; } i += 2; } else if (args[i].equals("-bd")) { BranchDrawer bd = (BranchDrawer) Class.forName(args[i + 1]).newInstance(); m_treeDrawer.setBranchDrawer(bd); i += 2; } else if (args[i].equals("-pattern")) { m_sPattern = args[i + 1]; i += 2; } else if (args[i].equals("-colorpattern")) { m_sColorPattern = args[i + 1]; i += 2; } else if (args[i].equals("-linecolortag")) { m_lineColorTag = args[i + 1]; m_lineColorMode = LineColorMode.COLOR_BY_METADATA_TAG; i += 2; } else if (args[i].equals("-linecolorlegend")) { m_showLegend = true; i++; } else if (args[i].equals("-singlechild")) { m_bAllowSingleChild = Boolean.parseBoolean(args[i + 1]); i += 2; } else if (args[i].equals("-rotatetext")) { m_bRotateTextWhenRootAtTop = true; i++; } else if (args[i].equals("-transform")) { m_bUseLogScale = true; m_fExponent = Double.parseDouble(args[i+1]); i += 2; } else if (args[i].equals("-allowLeafsToBeMovedIKnowThisMessesUpInternalCladePositions")) { m_bLeafCladeSelection = true; i += 1; } else if (args[i].equals("-optfile")) { m_sOptFile = args[i+1]; i += 2; } else if (args[i].equals("-rootcanaltree")) { try { m_iOptTree = Integer.parseInt(args[i+1]); } catch (NumberFormatException e) { m_sOptTree = args[i+1]; } i += 2; } else if (args[i].equals("-rawrootcanaltree")) { m_sOptTree = args[i+1]; m_bOptimiseRootCanalTree = false; i += 2; } else if (args[i].equals("-asPDF")) { m_asPDF = args[i+1]; i += 2; } else if (args[i].equals("-cladeThreshold")) { m_cladeThreshold = Double.parseDouble(args[i+1]); i += 2; } if (i == iOld) { if (new File(args[i]).exists()) { init(args[i++]); calcLines(); if (i != args.length) { String [] args2 = new String[args.length - 1]; for (int k = 0; k < i - 1; k++) { args2[k] = args[k]; } for (int k = i; k < args.length; k++) { args2[k-1] = args[k]; } startNew(args2); } return; } throw new Exception("Wrong argument"); } } else { init(args[i++]); calcLines(); } } if (m_asPDF != null) { new Thread() { @Override public void run() { try { Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } while (!m_bMetaDataReady) { try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } } exportPDF(m_asPDF); System.exit(0); }; }.start(); } } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "Error parsing command line arguments: " + Arrays.toString(args) + "\nArguments ignored\n\n" + getStatus(), "Argument Parsing Error", JOptionPane.PLAIN_MESSAGE); } } // parseArgs /** print some useful info to stdout **/ String banner() { return "DensiTree - Tree Set Visualizer\nVersion " + VERSION + "\n\n" + "Remco Bouckaert\n" + "remco@cs.auckland.ac.nz\nrrb@xm.co.nz\n" + "(c) 2010-2015\n\n\n" + "Key shortcuts:\n" + "c/Ctrl-c decrease/increase consensus tree intensity\n" + "i/Ctrl-i decrease/increase tree intensity\n" + "j/Ctrl-j decrease/increase jitter on trees (not consensus trees)\n" + "w/Ctrl-w decrease/increase consensus tree line width\n" + "v/Ctrl-v decrease/increase tree line width\n" + "f/Ctrl-f decrease/increase animation time delay - shorter delay = faster animation\n" + "t/Ctrl-t decrease/increase number of drawing threads for drawing tree set\n\n" + "Arrow keys & Page-Up/Down to scroll\n"; } // banner String formatColor(int iColor) { return " 0x" + Integer.toHexString(m_color[iColor].getRGB()).substring(2) + ' '; } /** get status of internal settings **/ String getStatus() { return "\n\nCurrent status:\n" + m_trees.length + " trees with " + m_cTrees.length + " topologies\n" + "Tree intensity: " + m_fTreeIntensity + "\n" + "Consensus Tree intensity: " + m_fCTreeIntensity + "\n" + "Tree width: " + m_nTreeWidth + "\n" + "Consensus Tree width: " + m_nCTreeWidth + "\n" + "Jitter: " + m_nJitter + "\n" + "Animation delay: " + m_nAnimationDelay + "\n" + "Height: " + m_fHeight + "\n" + "Zoom: " + m_fScale + "\n" + "Number of drawing threads: " + m_Panel.m_nDrawThreads + "\n" + "Burn in: " + m_nBurnIn + "\n\nColor 1:" + formatColor(0) + "\tColor 2:" + formatColor(1) + "\tColor 3:" + formatColor(2) + "\tDefault Color:" + formatColor(3) + "\nConsensus Color:" + formatColor(CONSCOLOR) + "\tLabel color:" + formatColor(LABELCOLOR) + "\tBackground color:" + formatColor(BGCOLOR) + "\tHeight color:" + formatColor(HEIGHTCOLOR); } /** * read trees from file, and process them into a set of lines This may take * a while... sFile: name of Nexus or Newick tree list file or to read * * @throws Exception **/ @SuppressWarnings("deprecation") public void init(String sFile) throws Exception { if (m_Panel != null) { setWaitCursor(); //m_Panel.setCursor(new Cursor(Cursor.WAIT_CURSOR)); } if (m_jStatusBar != null) { m_jStatusBar.setText("Initializing..."); m_jStatusBar.repaint(); } m_sFileName = sFile; m_bInitializing = true; m_viewMode = ViewMode.DRAW; a_animateStart.setIcon("start"); m_prevLineColorMode = null; LineColorMode orgLineColorMode = m_lineColorMode; m_lineColorMode = LineColorMode.DEFAULT; m_prevLineWidthMode = null; m_lineWidthMode = LineWidthMode.DEFAULT; System.err.print("Initializing..."); m_iAnimateTree = 0; m_fHeight = 0; m_fScaleX = 10; m_fScaleY = 10; m_fScale = 1; m_fTreeScale = 1; m_fTreeOffset = 0; m_doActions = new Vector<DoAction>(); m_iUndo = 0; m_random = new Random(); m_Panel.m_drawThread = new Thread[m_Panel.m_nDrawThreads]; //m_gridDrawer.m_bReverseGrid = false; //m_gridDrawer.m_fGridOffset = 0; m_rootcanaltree = null; try { if (thread != null) { try { thread.stop(); } catch (Exception e) { // ignore } } /** contains strings with tree in Newick format **/ // Vector<String> sNewickTrees; m_sLabels = new Vector<String>(); m_fLongitude = new Vector<Float>(); m_fLatitude = new Vector<Float>(); m_fMinLat = 360; m_fMinLong = 360; m_fMaxLat = 0; m_fMaxLong = 0; m_nOrder = null; // parseFile(sFile); TreeFileParser parser = new TreeFileParser(this); m_trees = parser.parseFile(sFile); m_nBurnIn = parser.m_nBurnIn; if (m_iOptTree >= 0) { m_optTree = m_trees[m_iOptTree - m_nBurnIn]; } a_loadkml.setEnabled(true); // m_nOffset = parser.m_nOffset; float fOffset = GEO_OFFSET; m_fMaxLong = parser.m_fMaxLong + fOffset; m_fMaxLat = parser.m_fMaxLat + fOffset; m_fMinLong = parser.m_fMinLong - fOffset; m_fMinLat = parser.m_fMinLat - fOffset; m_nNrOfLabels = parser.m_nNrOfLabels; if (m_trees.length == 0) { m_sLabels = null; JOptionPane.showMessageDialog(null, "No trees found in file\nMaybe burn in is too large?", "Help Message", JOptionPane.PLAIN_MESSAGE); return; } // set up selection m_bSelection = new boolean[m_sLabels.size()]; m_bLabelRectangle = new Rectangle[m_sLabels.size()]; m_bGeoRectangle = new Rectangle[m_sLabels.size()]; for (int i = 0; i < m_bSelection.length; i++) { m_bSelection[i] = true; m_bLabelRectangle[i] = new Rectangle(); m_bGeoRectangle[i] = new Rectangle(); } m_bSelectionChanged = false; // chop off root branch, if any double fMinRootLength = Double.MAX_VALUE; for (int i = 0; i < m_trees.length; i++) { fMinRootLength = Math.min(fMinRootLength, m_trees[i].m_fLength); } for (int i = 0; i < m_trees.length; i++) { m_trees[i].m_fLength -= fMinRootLength; } // reserve memory for nodes of m_trees float[] fHeights = new float[m_trees.length]; for (int i = 0; i < m_trees.length; i++) { fHeights[i] = positionHeight(m_trees[i], 0); m_fHeight = Math.max(m_fHeight, fHeights[i]); } for (int i = 0; i < m_trees.length; i++) { offsetHeight(m_trees[i], m_fHeight - fHeights[i]); } // count tree topologies // first step is find how many different topologies are present m_nTopology = new int[m_trees.length]; HashMap<String, Integer> map = new HashMap<String, Integer>(); for (int i = 0; i < m_trees.length; i++) { Node tree = m_trees[i]; String sNewick = tree.toShortNewick(); if (map.containsKey(sNewick)) { m_nTopology[i] = map.get(sNewick).intValue(); } else { m_nTopology[i] = map.size(); map.put(sNewick, map.size()); } } // second step is find how many different tree have a particular // topology m_nTopologies = map.size(); int[] nTopologies = new int[m_nTopologies]; for (int i = 0; i < m_trees.length; i++) { nTopologies[m_nTopology[i]]++; } // sort the trees so that frequently occurring topologies go first // in // the ordering for (int i = 0; i < m_trees.length; i++) { for (int j = i + 1; j < m_trees.length; j++) { if (nTopologies[m_nTopology[i]] < nTopologies[m_nTopology[j]] || (nTopologies[m_nTopology[i]] == nTopologies[m_nTopology[j]] && m_nTopology[i] > m_nTopology[j])) { int h = m_nTopology[j]; m_nTopology[j] = m_nTopology[i]; m_nTopology[i] = h; Node tree = m_trees[j]; m_trees[j] = m_trees[i]; m_trees[i] = tree; } } } // initialise drawing order of x-axis according to most prevalent // tree Node tree = m_trees[0]; // over sized, too lazy to figure out exact number of labels m_nOrder = new int[m_sLabels.size()]; m_nRevOrder = new int[m_sLabels.size()]; initOrder(tree, 0); // sanity check int nSum = 0; for (int i = 0; i < m_nOrder.length; i++) { nSum += m_nOrder[i]; } if (nSum != m_nNrOfLabels * (m_nNrOfLabels - 1) / 2) { JOptionPane.showMessageDialog(this, "The tree set possibly contains non-binary trees. Expect that not all nodes are shown."); } // reserve memory for nodes of m_cTrees // reserveMemory(m_nTopologies * (m_nNrOfLabels*2-1)); // calculate consensus trees int i = 0; int iOld = 0; int iConsTree = 0; m_fTreeWeight = new float[m_nTopologies]; m_cTrees = new Node[m_nTopologies]; while (i < m_trees.length) { tree = m_trees[i].copy(); Node consensusTree = tree; i++; while (i < m_trees.length && m_nTopology[i] == m_nTopology[i - 1]) { tree = m_trees[i]; addLength(tree, consensusTree); i++; } divideLength(consensusTree, i - iOld); m_fTreeWeight[iConsTree] = (float) (i - iOld + 0.0) / m_trees.length; // position nodes of consensus trees // positionLeafs(consensusTree); // positionRest(consensusTree); float fHeight = positionHeight(consensusTree, 0); offsetHeight(consensusTree, m_fHeight - fHeight); m_cTrees[iConsTree] = consensusTree; iConsTree++; iOld = i; } m_nTopologyByPopularity = new int[m_trees.length]; int nColor = 0; m_nTopologyByPopularity[0] = 0; for (i = 1; i < m_trees.length; i++) { if (m_nTopology[i] != m_nTopology[i - 1]) { nColor++; } m_nTopologyByPopularity[i] = nColor; } // calculate lines for drawing trees & consensus trees m_fLinesX = new float[m_trees.length][]; m_fLinesY = new float[m_trees.length][]; // m_fTLinesX = new float[m_trees.length][]; // m_fTLinesY = new float[m_trees.length][]; m_fCLinesX = new float[m_nTopologies][]; m_fCLinesY = new float[m_nTopologies][]; // m_fCTLinesX = new float[m_nTopologies][]; // m_fCTLinesY = new float[m_nTopologies][]; // calcLines(); m_bCladesReady = false; // new Thread() { // public void run() { // calcClades(); // m_bCladesReady = true; // reshuffle((m_bAllowSingleChild ? NodeOrderer.DEFAULT: NodeOrderer.OPTIMISE)); // calcPositions(); // makeDirty(); // }; // }.start(); //reshuffle((m_bAllowSingleChild ? NodeOrderer.DEFAULT: NodeOrderer.OPTIMISE)); reshuffle(NodeOrderer.DEFAULT); // calculate y-position for tree set calcPositions(); m_bMetaDataReady = false; thread = new Thread() { @Override public void run() { m_jStatusBar.setText("Calculating clades"); calcClades(); m_bCladesReady = true; m_jStatusBar.setText("Optimising node order"); int [] oldOrder = m_nOrder.clone(); if (!m_bAllowSingleChild) { reshuffle(NodeOrderer.SORT_BY_ROOT_CANAL_LENGTH); calcPositions(); calcLines(); notifyChangeListeners(); if (orderChanged(oldOrder)) { System.err.println("Node order changed"); makeDirty(); } } String statusMsg = "Parsing metadata"; for (int k = 0; k < m_trees.length; k++) { parseMetaData(m_trees[k]); if (k % 100 == 0) { statusMsg += "."; m_jStatusBar.setText(statusMsg); setWaitCursor(); // if (getCursor().getType() != Cursor.WAIT_CURSOR) { // setCursor(new Cursor(Cursor.WAIT_CURSOR)); // } } } m_metaDataTags = new ArrayList<String>(); m_metaDataTypes = new ArrayList<MetaDataType>(); collectMetaDataTags(m_trees[0]); if (m_metaDataTags.size() > 0) { calcPositions(); calcLines(); makeDirty(); } m_bMetaDataReady = true; notifyChangeListeners(); m_jStatusBar.setText("Done parsing metadata"); thread = null; } private void parseMetaData(Node node) { node.parseMetaData(); if (!node.isLeaf()) { parseMetaData(node.m_left); if (node.m_right != null) { parseMetaData(node.m_right); } } }; }; thread.start(); m_metaDataTags = new ArrayList<String>(); m_metaDataTypes = new ArrayList<MetaDataType>(); collectMetaDataTags(m_trees[0]); notifyChangeListeners(); if (orgLineColorMode != LineColorMode.DEFAULT) { while (!m_bMetaDataReady) { Thread.sleep(100); } m_lineColorMode = orgLineColorMode; calcColors(false); makeDirty(); } } catch (OutOfMemoryError e) { clear(); JOptionPane.showMessageDialog(null, "Not enough memory is reserved for java to process this tree. " + "Try starting DensiTree with more memory\n\n(for example " + "use:\njava -Xmx3g DensiTree.jar\nfrom " + "the command line) where DensiTree is in the path\n" + "or subsample your tree set to create a smaller tree file."); setDefaultCursor(); //m_Panel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); throw e; } catch (Exception e) { e.printStackTrace(); clear(); setDefaultCursor(); //m_Panel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); throw e; } m_bInitializing = false; if (m_sColorPattern != null) { calcColorPattern(); } addAction(new DoAction()); if (frame != null) { frame.setTitle(FRAME_TITLE + " " + sFile); } if (m_sKMLFile != null) { loadKML(); } System.err.println("Done"); } // init void notifyChangeListeners() { for (ChangeListener listener : m_changeListeners) { listener.stateChanged(null); } } private boolean orderChanged(int[] oldOrder) { for (int i = 0; i < oldOrder.length; i++) { if (oldOrder[i] != m_nOrder[i]) { return true; } } return false; } private void collectMetaDataTags(Node node) { Map<String, Object> metaDataMap = node.getMetaDataSet(); if (metaDataMap != null) { for (String key : metaDataMap.keySet()) { if (!m_metaDataTags.contains(key)) { m_metaDataTags.add(key); Object o = metaDataMap.get(key); if (o instanceof Double) { m_metaDataTypes.add(MetaDataType.NUMERIC); } else { String s = o.toString(); if (s.length() > 0 && s.charAt(0)=='{') { m_metaDataTypes.add(MetaDataType.SET); } else { m_metaDataTypes.add(MetaDataType.STRING); } } } } } if (!node.isLeaf()) { collectMetaDataTags(node.m_left); if (node.m_right != null) { collectMetaDataTags(node.m_right); } } } boolean m_bCladesReady; public boolean m_bMetaDataReady; /** represent clade as arrays of leaf indices **/ List<int[]> m_clades; /** proportion of trees containing the clade **/ List<Double> m_cladeWeight; /** average height of a clade **/ List<Double> m_cladeHeight; List<Double> m_cladeHeight95HPDup; List<Double> m_cladeHeight95HPDdown; public List<List<Double>> m_cladeHeightSetBottom; public List<List<Double>> m_cladeHeightSetTop; /** UI component for manipulating clade selection **/ JList<String> m_cladelist; DefaultListModel<String> m_cladelistmodel = new DefaultListModel<String>(); public Map<String, Integer> mapCladeToIndex; public Integer [] reverseindex; Comparator<Float> floatComparator = new Comparator<Float>() { @Override public int compare(Float o1, Float o2) { return Float.compare(Math.abs(o1), Math.abs(o2)); } }; /** represent clade as arrays of leaf indices **/ Map<Integer, Double> m_cladePairs; List<List<ChildClade>> m_cladeChildren; /** X-position of the clade **/ float[] m_cladePosition; /** index of consensus tree with highest product of clade probabilities **/ public //int m_iMaxCladeProbTopology; boolean m_bShowRootCanalTopology = false; /** Each clade has a list of pairs of child clades **/ class ChildClade { int m_iLeft; int m_iRight; double m_fWeight; @Override public String toString() { return "(" + m_iLeft + "," + m_iRight + ")" + m_fWeight + " "; } } private void calcClades() { if (m_bAllowSingleChild) { return; } m_clades = new ArrayList<int[]>(); m_cladeWeight = new ArrayList<Double>(); m_cladeHeight = new ArrayList<Double>(); m_cladeHeight95HPDup = new ArrayList<Double>(); m_cladeHeight95HPDdown = new ArrayList<Double>(); m_cladeHeightSetBottom = new ArrayList<List<Double>>(); m_cladeHeightSetTop = new ArrayList<List<Double>>(); m_cladeChildren = new ArrayList<List<ChildClade>>(); mapCladeToIndex = new HashMap<String, Integer>(); // add leafs as clades for (int i = 0; i < m_nNrOfLabels; i++) { int[] clade = new int[1]; clade[0] = i; m_clades.add(clade); m_cladeWeight.add(1.0); m_cladeHeight.add(0.0); m_cladeHeight95HPDup.add(0.0); m_cladeHeight95HPDdown.add(0.0); m_cladeHeightSetBottom.add(new ArrayList<Double>()); m_cladeHeightSetTop.add(new ArrayList<Double>()); m_cladeChildren.add(new ArrayList<ChildClade>()); mapCladeToIndex.put(Arrays.toString(clade), mapCladeToIndex.size()); } // collect clades for (int i = 0; i < m_cTrees.length; i++) { calcCladeForNode(m_cTrees[i], mapCladeToIndex, m_fTreeWeight[i], m_cTrees[i].m_fPosY); } for (int i = 0; i < m_trees.length; i++) { calcCladeForNode2(m_trees[i], mapCladeToIndex, 1.0 / m_trees.length, m_trees[i].m_fPosY); } // normalise clade heights, so m_cladeHeight represent average clade // height for (int i = 0; i < m_cladeHeight.size(); i++) { m_cladeHeight.set(i, m_cladeHeight.get(i) / m_cladeWeight.get(i)); } for (int i = 0; i < m_cladeHeight.size(); i++) { List<Double> heights = new ArrayList<Double>(); heights.addAll(m_cladeHeightSetBottom.get(i)); Collections.sort(heights); int upIndex = heights.size() * 190 / 200; int downIndex = heights.size() * 5 / 200; m_cladeHeight95HPDup.set(i, heights.get(upIndex)); m_cladeHeight95HPDdown.set(i, heights.get(downIndex)); } double fHeight0 = m_fHeight; for (int i = 0; i < m_cladeHeight.size(); i++) { fHeight0 = Math.min(fHeight0, m_cladeHeight.get(i)); } // for (int i = 0; i < m_cladeHeight.size(); i++) { // m_cladeHeight.set(i, m_cladeHeight.get(i) + fHeight0); // } m_cladePosition = new float[m_clades.size()]; // sort clades by weight Integer [] index = new Integer[m_cladePosition.length]; for (int i = 0; i < m_cladePosition.length; i++) { index[i] = i; } Arrays.sort(index, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { if (Math.abs(m_cladeWeight.get(o1) - m_cladeWeight.get(o2)) < m_cladeThreshold) { return (int) Math.signum(m_clades.get(o1).length- m_clades.get(o2).length); } return -Double.compare(m_cladeWeight.get(o1), m_cladeWeight.get(o2)); } }); List<int[]> clades = new ArrayList<int[]>(); List<Double> cladeWeight = new ArrayList<Double>(); List<Double> cladeHeight = new ArrayList<Double>(); List<Double> cladeHeight95HPDup = new ArrayList<Double>(); List<Double> cladeHeight95HPDdown = new ArrayList<Double>(); List<List<Double>> cladeHeightSetBottom = new ArrayList<List<Double>>(); List<List<Double>> cladeHeightSetTop = new ArrayList<List<Double>>(); List<List<ChildClade>> cladeChildren = new ArrayList<List<ChildClade>>(); for (int i = 0; i < m_cladePosition.length; i++) { clades.add(m_clades.get(index[i])); cladeWeight.add(m_cladeWeight.get(index[i])); cladeHeight.add(m_cladeHeight.get(index[i])); cladeHeight95HPDdown.add(m_cladeHeight95HPDdown.get(index[i])); cladeHeight95HPDup.add(m_cladeHeight95HPDup.get(index[i])); cladeChildren.add(m_cladeChildren.get(index[i])); cladeHeightSetBottom.add(m_cladeHeightSetBottom.get(index[i])); cladeHeightSetTop.add(m_cladeHeightSetTop.get(index[i])); } m_clades = clades; m_cladeWeight = cladeWeight; m_cladeHeight = cladeHeight; m_cladeHeight95HPDdown = cladeHeight95HPDdown; m_cladeHeight95HPDup = cladeHeight95HPDup; m_cladeChildren = cladeChildren; m_cladeHeightSetBottom = cladeHeightSetBottom; m_cladeHeightSetTop = cladeHeightSetTop; reverseindex = new Integer[m_cladePosition.length]; for (int i = 0; i < m_cladePosition.length; i++) { reverseindex[index[i]] = i; } for (int i = 0; i < m_cladePosition.length; i++) { List<ChildClade> list = m_cladeChildren.get(i); for (ChildClade childClade : list) { childClade.m_iLeft = reverseindex[childClade.m_iLeft]; childClade.m_iRight = reverseindex[childClade.m_iRight]; } } // reassign clade nr (after sorting) in consensus trees for (int i = 0; i < m_cTrees.length; i++) { resetCladeNr(m_cTrees[i], reverseindex); } // set clade nr for all trees, from clade nr in topology for (int i = 0; i < m_trees.length; i++) { setCladeNr(m_trees[i], m_cTrees[m_nTopologyByPopularity[i]]); } m_cladePairs = new HashMap<Integer, Double>(); for (int i = 0; i < m_cTrees.length; i++) { calcCladePairs(m_cTrees[i], m_fTreeWeight[i]); } // find tree topology with highest product of clade support of all its clades int iMaxCladeProbTopology = 0; //int iMaxMinCladeProbTopology = 0; double fMaxCladeProb = cladeProb(m_cTrees[0], true); double fMaxMinCladeProb = cladeProb(m_cTrees[0], false); //int iMaxCCDProbTopology = 0; double fMaxCCDProb = CCDProb(m_cTrees[0]);//, index); for (int i = 1; i < m_cTrees.length; i++) { double fCladeProb = cladeProb(m_cTrees[i], true); if (fCladeProb > fMaxCladeProb) { iMaxCladeProbTopology = i; fMaxCladeProb = fCladeProb; } } for (int i = 1; i < m_cTrees.length; i++) { double fMinCladeProb = cladeProb(m_cTrees[i], false); if (fMinCladeProb > fMaxMinCladeProb) { //iMaxMinCladeProbTopology = i; fMaxMinCladeProb = fMinCladeProb; } } for (int i = 1; i < m_cTrees.length; i++) { double fCCDProb = CCDProb(m_cTrees[i]);//, index); if (fCCDProb > fMaxCCDProb) { //iMaxCCDProbTopology = i; fMaxCCDProb = fCCDProb; } } m_summaryTree = new ArrayList<Node>(); m_summaryTree.add(m_cTrees[iMaxCladeProbTopology].copy()); cleanUpSummaryTree(m_summaryTree.get(0)); if (m_bOptimiseRootCanalTree) { BranchLengthOptimiser optimiser = new BranchLengthOptimiser(this); optimiser.optimiseScore(m_summaryTree.get(0)); } float fHeight = positionHeight(m_summaryTree.get(0), 0); offsetHeight(m_summaryTree.get(0), m_fHeight - fHeight); // m_summaryTree.add(m_cTrees[iMaxMinCladeProbTopology].copy()); // cleanUpSummaryTree(m_summaryTree.get(1)); // // m_summaryTree.add(m_cTrees[iMaxCCDProbTopology].copy()); // cleanUpSummaryTree(m_summaryTree.get(2)); // // // construct max. clade weight tree // List<Node> nodes = new ArrayList<Node>(); // List<int[]> cladeIDs = new ArrayList<int[]>(); // for (int i = 0; i < m_sLabels.size(); i++) { // int [] cladeID = new int[1]; // cladeID[0] = i; // cladeIDs.add(cladeID); // Node node = new Node(); // node.m_iLabel = i; // node.m_iClade = i; // nodes.add(node); // } // m_summaryTree.add(constructMaxCladeTree(cladeIDs, mapCladeToIndex, nodes, false)); // m_summaryTree.get(3).sort(); // resetCladeNr(m_summaryTree.get(3), reverseindex); // m_summaryTree.add(m_summaryTree.get(3).copy()); // cleanUpSummaryTree(m_summaryTree.get(4)); // // m_summaryTree.add(constructMaxCladeTree(cladeIDs, mapCladeToIndex, nodes, true)); // m_summaryTree.get(5).sort(); // resetCladeNr(m_summaryTree.get(5), reverseindex); // cleanUpSummaryTree(m_summaryTree.get(5)); // setHeightByClade(m_summaryTree.get(5)); // add clades to GUI component updateCladeModel(); // m_summaryTree[5] = m_cTrees[iMaxCladeProbTopology].copy(); if (m_sOptTree != null) { TreeFileParser parser = new TreeFileParser(m_sLabels, null, null, 0); try { Node tree = parser.parseNewick(m_sOptTree); tree.sort(); tree.labelInternalNodes(m_nNrOfLabels); float fTreeHeight = positionHeight(tree, 0); offsetHeight(tree, m_fHeight - fTreeHeight); calcCladeIDForNode(tree, mapCladeToIndex); resetCladeNr(tree, reverseindex); m_summaryTree.add(tree); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } if (m_optTree != null) { m_summaryTree.add(m_optTree.copy()); } m_rootcanaltree = m_summaryTree.get(0); // save memory m_cladeHeightSetBottom = null; m_cladeHeightSetTop = null; } public void updateCladeModel() { m_cladelistmodel.clear(); List<String> list = cladesToString(); for (int i = 0; i < list.size(); i++) { m_cladelistmodel.add(i, list.get(i)); } } private void calcCladePairs(Node node, double fWeight) { if (!node.isLeaf()) { calcCladePairs(node.m_left, fWeight); calcCladePairs(node.m_right, fWeight); int iCladeLeft = Math.min(node.m_left.m_iClade, node.m_right.m_iClade); int iCladeRight = Math.max(node.m_left.m_iClade, node.m_right.m_iClade);; Integer i = (iCladeRight << 16) + iCladeLeft; if (!m_cladePairs.containsKey(i)) { m_cladePairs.put(i, fWeight); } else { m_cladePairs.put(i, m_cladePairs.get(i) + fWeight); } } } /* private void removeNegBranches(Node node) { if (!node.isLeaf()) { removeNegBranches(node.m_left); removeNegBranches(node.m_right); if (node.m_left.m_fLength < 0) { node.m_right.m_fLength += -node.m_left.m_fLength; node.m_fLength -= -node.m_left.m_fLength; node.m_left.m_fLength = 0; } if (node.m_right.m_fLength < 0) { node.m_left.m_fLength += -node.m_right.m_fLength; node.m_fLength -= -node.m_right.m_fLength; node.m_right.m_fLength = 0; } } } */ private void cleanUpSummaryTree(Node summaryTree) { setHeightByClade(summaryTree); summaryTree.m_fLength = (float) (m_fHeight - m_cladeHeight.get(summaryTree.m_iClade)); float fHeight = positionHeight(summaryTree, 0); offsetHeight(summaryTree, m_fHeight - fHeight); } /* private Node constructMaxCladeTree(List<int[]> cladeIDs, Map<String, Integer> mapCladeToIndex, List<Node> nodes, boolean useCCD) { int k = nodes.size(); while (cladeIDs.size() > 1) { double maxWeight = -1; int maxLeft = -1; int maxRight = -1; for (int i = 0; i < cladeIDs.size(); i++) { int [] cladeLeft = cladeIDs.get(i); for (int j = i+1; j < cladeIDs.size(); j++) { int [] cladeRight = cladeIDs.get(j); int [] clade = mergeClades(cladeLeft, cladeRight); String sClade = Arrays.toString(clade); if (mapCladeToIndex.containsKey(sClade)) { int iClade = mapCladeToIndex.get(sClade); double weight = 0; if (useCCD) { String sCladeLeft = Arrays.toString(cladeLeft); String sCladeRight = Arrays.toString(cladeRight); int iClade1 = mapCladeToIndex.get(sCladeLeft); int iClade2 = mapCladeToIndex.get(sCladeRight); int iCladeLeft = Math.min(iClade1, iClade2); int iCladeRight = Math.max(iClade1, iClade2); Integer hash = (iCladeRight << 16) + iCladeLeft; weight = m_cladePairs.get(hash); } else { weight = m_cladeWeight.get(iClade); } if (weight > maxWeight) { maxWeight = weight; maxLeft = i; maxRight = j; } } } } // update clades int [] cladeLeft = cladeIDs.get(maxLeft); int [] cladeRight = cladeIDs.get(maxRight); int [] clade = mergeClades(cladeLeft, cladeRight); cladeIDs.remove(maxRight); cladeIDs.remove(maxLeft); cladeIDs.add(clade); // create new Node Node node = new Node(); node.m_iLabel = k++; node.m_iClade = mapCladeToIndex.get(Arrays.toString(clade)); node.m_left = nodes.get(maxLeft); nodes.get(maxLeft).m_Parent = node; node.m_right = nodes.get(maxRight); nodes.get(maxRight).m_Parent = node; nodes.remove(maxRight); nodes.remove(maxLeft); nodes.add(node); } return nodes.get(0); } */ // merge clades, keep in sorted order private int[] mergeClades(int[] cladeLeft, int[] cladeRight) { int [] clade = new int[cladeLeft.length + cladeRight.length]; int iLeft = 0; int iRight = 0; for (int i = 0; i < clade.length; i++) { if (iLeft == cladeLeft.length) { clade[i] = cladeRight[iRight++]; } else if (iRight == cladeRight.length) { clade[i] = cladeLeft[iLeft++]; } else if (cladeRight[iRight] > cladeLeft[iLeft]) { clade[i] = cladeLeft[iLeft++]; } else { clade[i] = cladeRight[iRight++]; } } return clade; } private void setHeightByClade(Node node) { if (!node.isRoot()) { //node.m_fLength = (float)Math.abs(m_cladeHeight.get(node.getParent().m_iClade) - m_cladeHeight.get(node.m_iClade)); node.m_fLength = (float)(m_cladeHeight.get(node.m_iClade) - m_cladeHeight.get(node.getParent().m_iClade)); } if (!node.isLeaf()) { setHeightByClade(node.m_left); setHeightByClade(node.m_right); } } boolean m_bAllowCladeSelection = true; void resetCladeSelection() { m_bAllowCladeSelection = false; m_cladelist.clearSelection(); for (int i : m_cladeSelection) { m_cladelist.addSelectionInterval(i, i); if (m_cladeSelection.size() == 1) { m_cladelist.ensureIndexIsVisible(i); } } if (m_cladeSelection.size() > 0) { Arrays.fill(m_bSelection, false); for (int i : m_cladeSelection) { for (int j = 0; j < m_clades.get(i).length; j++) { m_bSelection[m_clades.get(i)[j]] = true; } } } m_bAllowCladeSelection = true; } public void resetCladeNr(Node node, Integer[] reverseindex) { node.m_iClade = reverseindex[node.m_iClade]; if (!node.isLeaf()) { resetCladeNr(node.m_left, reverseindex); resetCladeNr(node.m_right, reverseindex); } } List<String> cladesToString() { List<String> list = new ArrayList<String>(); DecimalFormat format = new DecimalFormat("###.##"); for (int i = 0; i < m_cladePosition.length; i++) { if (m_cladeWeight.get(i) >= m_smallestCladeSupport) { String sStr = ""; //if (m_clades.get(i).length > 1) { sStr += format.format(m_cladeWeight.get(i) * 100) + "% "; sStr += format.format((m_fHeight - m_cladeHeight95HPDup.get(i)) * m_fUserScale) + " "; sStr += format.format((m_fHeight - m_cladeHeight95HPDdown.get(i)) * m_fUserScale) + " "; sStr += "["; int j = 0; for (j = 0; j < m_clades.get(i).length - 1; j++) { sStr += (m_sLabels.get(m_clades.get(i)[j]) + ","); } sStr += (m_sLabels.get(m_clades.get(i)[j]) + "]\n"); list.add(sStr); //} } } return list; } private double cladeProb(Node node, final boolean useProduct) { if (node.isLeaf()) { return 1.0; } else { double fCladeProb = m_cladeWeight.get(node.m_iClade); if (useProduct) { fCladeProb *= cladeProb(node.m_left, useProduct); fCladeProb *= cladeProb(node.m_right, useProduct); } else { fCladeProb = Math.min(fCladeProb, cladeProb(node.m_left, useProduct)); fCladeProb = Math.min(fCladeProb, cladeProb(node.m_right, useProduct)); } return fCladeProb; } } private double CCDProb(Node node) { //, Integer [] index) { if (node.isLeaf()) { return 1.0; } else { // int iClade = node.m_iClade; // iClade = index[iClade]; // int iCladeLeft = Math.min(index[node.m_left.m_iClade], index[node.m_right.m_iClade]); // iCladeLeft = index[iCladeLeft]; int iCladeLeft = Math.min(node.m_left.m_iClade, node.m_right.m_iClade); int iCladeRight = Math.max(node.m_left.m_iClade, node.m_right.m_iClade);; Integer i = (iCladeRight << 16) + iCladeLeft; Double f = m_cladePairs.get(i); if (f == null) { f = m_cladePairs.get(i); } double fCladeProb = f;// / m_cladeWeight.get(node.m_iClade); fCladeProb *= CCDProb(node.m_left);//, index); fCladeProb *= CCDProb(node.m_right);//, index); return fCladeProb; } } private void setCladeNr(Node node, Node node2) { if (node2 == null) { throw new RuntimeException("node2 cannot be null"); } if (!node.isLeaf()) { node.m_iClade = node2.m_iClade; setCladeNr(node.m_left, node2.m_left); setCladeNr(node.m_right, node2.m_right); } } private int[] calcCladeForNode(Node node, Map<String, Integer> mapCladeToIndex, double fWeight, double fHeight) { if (node.isLeaf()) { int[] clade = new int[1]; clade[0] = node.getNr(); node.m_iClade = node.getNr(); m_cladeHeight.set(node.m_iClade, m_cladeHeight.get(node.m_iClade) + fWeight * fHeight); //m_cladeHeightSet.get(node.m_iClade).add(fHeight); return clade; } else { int[] cladeLeft = calcCladeForNode(node.m_left, mapCladeToIndex, fWeight, fHeight + node.m_left.m_fLength); int[] cladeRight = calcCladeForNode(node.m_right, mapCladeToIndex, fWeight, fHeight + node.m_right.m_fLength); int[] clade = mergeClades(cladeLeft, cladeRight); // merge clades, keep in sorted order // int[] clade = new int[cladeLeft.length + cladeRight.length]; // int iLeft = 0; // int iRight = 0; // for (int i = 0; i < clade.length; i++) { // if (iLeft == cladeLeft.length) { // clade[i] = cladeRight[iRight++]; // } else if (iRight == cladeRight.length) { // clade[i] = cladeLeft[iLeft++]; // } else if (cladeRight[iRight] > cladeLeft[iLeft]) { // clade[i] = cladeLeft[iLeft++]; // } else { // clade[i] = cladeRight[iRight++]; // } // } // update clade weights String sClade = Arrays.toString(clade); if (!mapCladeToIndex.containsKey(sClade)) { mapCladeToIndex.put(sClade, mapCladeToIndex.size()); m_clades.add(clade); m_cladeWeight.add(0.0); m_cladeHeight.add(0.0); m_cladeHeight95HPDup.add(0.0); m_cladeHeight95HPDdown.add(0.0); m_cladeHeightSetBottom.add(new ArrayList<Double>()); m_cladeHeightSetTop.add(new ArrayList<Double>()); m_cladeChildren.add(new ArrayList<ChildClade>()); } int iClade = mapCladeToIndex.get(sClade); m_cladeWeight.set(iClade, m_cladeWeight.get(iClade) + fWeight); m_cladeHeight.set(iClade, m_cladeHeight.get(iClade) + fWeight * fHeight); //m_cladeHeightSet.get(iClade).add(fHeight); node.m_iClade = iClade; // update child clades int iCladeLeft = Math.min(node.m_left.m_iClade, node.m_right.m_iClade); int iCladeRight = Math.max(node.m_left.m_iClade, node.m_right.m_iClade); List<ChildClade> children = m_cladeChildren.get(iClade); boolean bFound = false; for (ChildClade child : children) { if (child.m_iLeft == iCladeLeft && child.m_iRight == iCladeRight) { child.m_fWeight += fWeight; bFound = true; break; } } if (!bFound) { ChildClade child = new ChildClade(); child.m_iLeft = iCladeLeft; child.m_iRight = iCladeRight; child.m_fWeight = fWeight; m_cladeChildren.get(iClade).add(child); } // Integer [] cladePair = new Integer[2]; // cladePair[0] = iClade; // cladePair[1] = iCladeLeft; return clade; } } private int[] calcCladeForNode2(Node node, Map<String, Integer> mapCladeToIndex, double fWeight, double fHeight) { if (node.isLeaf()) { int[] clade = new int[1]; clade[0] = node.getNr(); node.m_iClade = node.getNr(); m_cladeHeightSetBottom.get(node.m_iClade).add(fHeight); m_cladeHeightSetTop.get(node.m_iClade).add(fHeight - node.m_fLength); return clade; } else { int[] cladeLeft = calcCladeForNode2(node.m_left, mapCladeToIndex, fWeight, fHeight + node.m_left.m_fLength); int[] cladeRight = calcCladeForNode2(node.m_right, mapCladeToIndex, fWeight, fHeight + node.m_right.m_fLength); // merge clades, keep in sorted order int[] clade = new int[cladeLeft.length + cladeRight.length]; int iLeft = 0; int iRight = 0; for (int i = 0; i < clade.length; i++) { if (iLeft == cladeLeft.length) { clade[i] = cladeRight[iRight++]; } else if (iRight == cladeRight.length) { clade[i] = cladeLeft[iLeft++]; } else if (cladeRight[iRight] > cladeLeft[iLeft]) { clade[i] = cladeLeft[iLeft++]; } else { clade[i] = cladeRight[iRight++]; } } // update clade weights String sClade = Arrays.toString(clade); // if (!mapCladeToIndex.containsKey(sClade)) { // mapCladeToIndex.put(sClade, mapCladeToIndex.size()); // m_cladeHeight95HPDup.add(0.0); // m_cladeHeight95HPDdown.add(0.0); // m_cladeHeightSet.add(new ArrayList<Double>()); // } int iClade = mapCladeToIndex.get(sClade); m_cladeHeightSetBottom.get(iClade).add(fHeight); m_cladeHeightSetTop.get(iClade).add(fHeight - node.m_fLength); node.m_iClade = iClade; // update child clades int iCladeLeft = Math.min(node.m_left.m_iClade, node.m_right.m_iClade); int iCladeRight = Math.max(node.m_left.m_iClade, node.m_right.m_iClade); List<ChildClade> children = m_cladeChildren.get(iClade); boolean bFound = false; for (ChildClade child : children) { if (child.m_iLeft == iCladeLeft && child.m_iRight == iCladeRight) { child.m_fWeight += fWeight; bFound = true; break; } } if (!bFound) { ChildClade child = new ChildClade(); child.m_iLeft = iCladeLeft; child.m_iRight = iCladeRight; child.m_fWeight = fWeight; m_cladeChildren.get(iClade).add(child); } return clade; } } public int[] calcCladeIDForNode(Node node, Map<String, Integer> mapCladeToIndex) { if (node.isLeaf()) { int[] clade = new int[1]; clade[0] = node.getNr(); node.m_iClade = node.getNr(); return clade; } else { int[] cladeLeft = calcCladeIDForNode(node.m_left, mapCladeToIndex); int[] cladeRight = calcCladeIDForNode(node.m_right, mapCladeToIndex); int[] clade = mergeClades(cladeLeft, cladeRight); String sClade = Arrays.toString(clade); try { int iClade = mapCladeToIndex.get(sClade); node.m_iClade = iClade; } catch (Exception e) { // ignore node.m_iClade = 0; } return clade; } } void calcColorPattern() { m_iColor = new int[m_sLabels.size()]; Pattern pattern = Pattern.compile(".*" + m_sColorPattern + ".*"); List<String> sPatterns = new ArrayList<String>(); for (int i = 0; i < m_sLabels.size(); i++) { String sLabel = m_sLabels.get(i); Matcher matcher = pattern.matcher(sLabel); if (matcher.find()) { String sMatch = matcher.group(1); if (sPatterns.indexOf(sMatch) < 0) { sPatterns.add(sMatch); } m_iColor[i] = sPatterns.indexOf(sMatch); } } } void loadKML() { String sFileName = m_sKMLFile; HashMap<String, Vector<Double>> mapLabel2X = new HashMap<String, Vector<Double>>(); HashMap<String, Vector<Double>> mapLabel2Y = new HashMap<String, Vector<Double>>(); // sanity check if (!(new File(sFileName)).exists()) { JOptionPane.showMessageDialog(this, "Tried to read goe info, but could not find file " + sFileName ); return; } try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(false); org.w3c.dom.Document doc = null; if (sFileName.toLowerCase().endsWith(".kmz")) { ZipFile zf = new ZipFile(sFileName); Enumeration<?> entries = zf.entries(); while (entries.hasMoreElements()) { ZipEntry ze = (ZipEntry) entries.nextElement(); if (ze.getName().toLowerCase().equals("doc.kml")) { doc = factory.newDocumentBuilder().parse(zf.getInputStream(ze)); } } zf.close(); } else { doc = factory.newDocumentBuilder().parse(new File(sFileName)); } doc.normalize(); // grab styles out of the KML file HashMap<String, Integer> mapStyleToColor = new HashMap<String, Integer>(); org.w3c.dom.NodeList oStyles = doc.getElementsByTagName("Style"); for (int iNode = 0; iNode < oStyles.getLength(); iNode++) { org.w3c.dom.Node oStyle = oStyles.item(iNode); String sID = oStyle.getAttributes().getNamedItem("id").getTextContent(); XPath xpath = XPathFactory.newInstance().newXPath(); String expression = ".//PolyStyle/color"; org.w3c.dom.Node oColor = (org.w3c.dom.Node) xpath.evaluate(expression, oStyles.item(iNode), XPathConstants.NODE); if (oColor != null) { String sColor = oColor.getTextContent(); sColor = sColor.substring(2); Integer nColor = Integer.parseInt(sColor, 16); mapStyleToColor.put(sID, nColor); } } // grab polygon info from placemarks //List<Integer> iDistrictCenter = new ArrayList<Integer>(); org.w3c.dom.NodeList oPlacemarks = doc.getElementsByTagName("Placemark"); for (int iNode = 0; iNode < oPlacemarks.getLength(); iNode++) { String sPlacemarkName = ""; Vector<Double> nX = new Vector<Double>(); Vector<Double> nY = new Vector<Double>(); org.w3c.dom.Node node = oPlacemarks.item(iNode); org.w3c.dom.NodeList oChildren = node.getChildNodes(); // int color = 0x808080; for (int iChild = 0; iChild < oChildren.getLength(); iChild++) { org.w3c.dom.Node oChild = oChildren.item(iChild); if (oChild.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { String sName = oChild.getNodeName(); if (sName.equals("name")) { sPlacemarkName = oChild.getTextContent().trim(); } else if (sName.equals("Style")) { String expression = ".//PolyStyle/color"; XPath xpath = XPathFactory.newInstance().newXPath(); org.w3c.dom.Node oColor = (org.w3c.dom.Node) xpath.evaluate(expression, oStyles.item(iNode), XPathConstants.NODE); if (oColor != null) { String sColor = oColor.getTextContent(); sColor = sColor.substring(2); // color = Integer.parseInt(sColor, 16); } } else if (sName.equals("styleUrl")) { String sID = oChild.getTextContent(); sID = sID.substring(1); if (mapStyleToColor.containsKey(sID)) { // color = mapStyleToColor.get(sID); } // } else if (sName.equals("description")) { // sDescription = oChild.getTextContent(); } else if (sName.equals("Polygon") || sName.equals("Point") || sName.equals("LineString")) { XPath xpath = XPathFactory.newInstance().newXPath(); String expression = ".//coordinates"; org.w3c.dom.Node oCoords = (org.w3c.dom.Node) xpath.evaluate(expression, oChild, XPathConstants.NODE); String sCoord = oCoords.getTextContent(); String[] sCoords = sCoord.split("\\s+"); for (int i = 0; i < sCoords.length; i++) { String sStr = sCoords[i]; String[] sStrs = sStr.split(","); if (sStrs.length > 1) { // Point point = new Point(); nX.add(Double.parseDouble(sStrs[0]));// * // Parser.MAX_LATITUDE_INT_UNITS // / // 360)); nY.add(Double.parseDouble(sStrs[1]));// /180f) // * // Parser.MAX_LONGITUDE_INT_UNITS)); } } } } } if (nX.size() > 0) { mapLabel2X.put(sPlacemarkName.toLowerCase(), nX); mapLabel2Y.put(sPlacemarkName.toLowerCase(), nY); sPlacemarkName = sPlacemarkName.replaceAll("-", ""); sPlacemarkName = sPlacemarkName.replaceAll("_", ""); if (!mapLabel2X.containsKey(sPlacemarkName)) { mapLabel2X.put(sPlacemarkName.toLowerCase(), nX); mapLabel2Y.put(sPlacemarkName.toLowerCase(), nY); } } } } catch (Exception e) { // try to process as tab-delimited txt file try { m_fMinLat = 90; m_fMinLong = 180; m_fMaxLat = -90; m_fMaxLong = -180; BufferedReader fin = new BufferedReader(new FileReader(sFileName)); String sStr = null; // skip header line sStr = fin.readLine(); while (fin.ready()) { sStr = fin.readLine(); String [] sStrs = sStr.split("\\s+"); if (sStrs.length >= 3) { String sPlacemarkName = sStrs[0]; Vector<Double> nX = new Vector<Double>(); Vector<Double> nY = new Vector<Double>(); nX.add(Double.parseDouble(sStrs[2])); nY.add(Double.parseDouble(sStrs[1])); mapLabel2X.put(sPlacemarkName.toLowerCase(), nX); mapLabel2Y.put(sPlacemarkName.toLowerCase(), nY); sPlacemarkName = sPlacemarkName.replaceAll("-", ""); sPlacemarkName = sPlacemarkName.replaceAll("_", ""); if (!mapLabel2X.containsKey(sPlacemarkName)) { mapLabel2X.put(sPlacemarkName.toLowerCase(), nX); mapLabel2Y.put(sPlacemarkName.toLowerCase(), nY); } } } fin.close(); } catch (Exception e2) { e2.printStackTrace(); } } try { // grab Taxa From Objects m_fMinLat = 90; m_fMinLong = 180; m_fMaxLat = -90; m_fMaxLong = -180; for (int iLabel = 0; iLabel < m_nNrOfLabels; iLabel++) { String sTaxon = m_sLabels.get(iLabel).toLowerCase(); String sTaxon2 = sTaxon.replaceAll("[-_]", ""); if (mapLabel2X.containsKey(sTaxon) || mapLabel2X.containsKey(sTaxon2)) { if (!mapLabel2X.containsKey(sTaxon)) { sTaxon = sTaxon2; } Vector<Double> nX = mapLabel2X.get(sTaxon); Vector<Double> nY = mapLabel2Y.get(sTaxon); double fX = 0; double fY = 0; for (Double f : nX) { fX += f; } fX /= nX.size(); for (Double f : nY) { fY += f; } fY /= nY.size(); while (m_fLatitude.size() <= iLabel) { m_fLatitude.add(0f); m_fLongitude.add(0f); } m_fLatitude.set(iLabel, (float) fY); m_fLongitude.set(iLabel, (float) fX); m_fMinLat = Math.min(m_fMinLat, (float) fY); m_fMaxLat = Math.max(m_fMaxLat, (float) fY); m_fMinLong = Math.min(m_fMinLong, (float) fX); m_fMaxLong = Math.max(m_fMaxLong, (float) fX); } else { System.err.println("No geo info for " + sTaxon + " found (probably because taxon is missing or spelling error)"); while (m_fLatitude.size() <= iLabel) { m_fLatitude.add(0f); m_fLongitude.add(0f); } m_fLatitude.set(iLabel, 0f); m_fLongitude.set(iLabel, 0f); } } float fOffset = GEO_OFFSET; m_fMaxLong = m_fMaxLong + fOffset; m_fMaxLat = m_fMaxLat + fOffset; m_fMinLong = m_fMinLong - fOffset; m_fMinLat = m_fMinLat - fOffset; System.err.println("geo range (" +m_fMinLat + "," + m_fMinLong+ ")x(" + m_fMaxLat+","+ m_fMaxLong+")"); } catch (Exception e) { e.printStackTrace(); } } // loadKMLFile /* remove all data from memory */ void clear() { m_trees = new Node[0]; m_cTrees = new Node[0]; m_fLinesX = null; m_fLinesY = null; // m_fTLinesX = null; // m_fTLinesY = null; m_fCLinesX = null; m_fCLinesY = null; // m_fCTLinesX = null; // m_fCTLinesY = null; m_bInitializing = false; } // clear /** * try to reorder the leaf nodes so that the tree layout allows * investigation of some of the tree set features */ void reshuffle(int nMethod) { int [] oldOrder = m_nOrder.clone(); m_nShuffleMode = nMethod; setWaitCursor(); //m_Panel.setCursor(new Cursor(Cursor.WAIT_CURSOR)); try { switch (nMethod) { case NodeOrderer.DEFAULT: // use order of most frequently occurring tree initOrder(m_trees[0], 0); break; case NodeOrderer.MANUAL: { // use order given by user for (int i = 0; i < m_sLabels.size(); i++) { System.out.print(m_sLabels.elementAt(i) + " "); } String sOrder = JOptionPane.showInputDialog("New node order:", ""); if (sOrder == null) { return; } String[] sIndex = sOrder.split(" "); if (sIndex.length != m_nNrOfLabels) { System.err.println("Number of labels/taxa differs from given labels"); return; } int[] nOrder = new int[m_nOrder.length]; int[] nRevOrder = new int[m_nRevOrder.length]; for (int i = 0; i < sIndex.length; i++) { int j = 0; String sTarget = sIndex[i]; while ((j < m_sLabels.size()) && !(m_sLabels.elementAt(j).equals(sTarget))) { j++; } if (j == m_sLabels.size()) { System.err.println("Label \"" + sTarget + "\" not found among labels"); return; } nOrder[j] = i; nRevOrder[i] = j; } m_nOrder = nOrder; m_nRevOrder = nRevOrder; } break; case NodeOrderer.META_ALL: break; case NodeOrderer.META_SUM: break; case NodeOrderer.META_AVERAGE: break; case NodeOrderer.GEOINFO: break; default: // otherwise, use one of the distance based methods NodeOrderer h = new NodeOrderer(nMethod); int[] nOrder = h.calcOrder(m_nNrOfLabels, m_trees, m_cTrees, m_rootcanaltree, m_fTreeWeight/* * , * m_nOrder */, m_clades, m_cladeWeight); m_nOrder = nOrder; for (int i = 0; i < m_nNrOfLabels; i++) { m_nRevOrder[m_nOrder[i]] = i; } System.err.println(); for (int i = 0; i < m_nNrOfLabels; i++) { System.out.print(m_sLabels.elementAt(m_nRevOrder[i]) + " "); } System.out.println(); } } catch (Exception e) { e.printStackTrace(); } int nNodes = getNrOfNodes(m_trees[0]); if (nMethod < NodeOrderer.META_ALL) { m_bShowBounds = false; calcPositions(); calcLines(); makeDirty(); addAction(new DoAction()); } else { m_bShowBounds = true; m_pattern = Pattern.compile(m_sPattern); switch (nMethod) { case NodeOrderer.META_ALL: { double fMaxX = 0; for (int i = 0; i < m_trees.length; i++) { double fX = positionMetaAll(m_trees[i]); fMaxX = Math.max(fMaxX, fX); } for (int i = 0; i < m_cTrees.length; i++) { double fX = positionMetaAll(m_cTrees[i]); fMaxX = Math.max(fMaxX, fX); } fMaxX = m_nNrOfLabels / fMaxX; for (int i = 0; i < m_trees.length; i++) { scaleX(m_trees[i], fMaxX); } for (int i = 0; i < m_cTrees.length; i++) { scaleX(m_cTrees[i], fMaxX); } calcLines(); } break; case NodeOrderer.META_SUM: case NodeOrderer.META_AVERAGE: { for (int i = 0; i < m_trees.length; i++) { float[] fHeights = new float[m_nNrOfLabels * 2 - 1]; float[] fMetas = new float[m_nNrOfLabels * 2 - 1]; int[] nCounts = new int[m_nNrOfLabels * 2 - 1]; collectHeights(m_trees[i], fHeights, 0); Arrays.sort(fHeights); collectMetaData(m_trees[i], fHeights, 0.0f, 0, fMetas, nCounts); m_fLinesX[i] = new float[nNodes * 2 + 2]; m_fLinesY[i] = new float[nNodes * 2 + 2]; for (int j = 0; j < fMetas.length - 1; j++) { m_fLinesX[i][j * 2] = fMetas[j]; m_fLinesY[i][j * 2] = (fHeights[j] - m_fTreeOffset) * m_fTreeScale; m_fLinesX[i][j * 2 + 1] = fMetas[j + 1]; m_fLinesY[i][j * 2 + 1] = (fHeights[j + 1] - m_fTreeOffset) * m_fTreeScale; } if (nMethod == NodeOrderer.META_AVERAGE) { for (int j = 0; j < fMetas.length - 1; j++) { if (nCounts[j] > 0) { m_fLinesX[i][j * 2] = fMetas[j] / nCounts[j]; } if (nCounts[j + 1] > 0) { m_fLinesX[i][j * 2 + 1] = fMetas[j + 1] / nCounts[j + 1]; } } } } for (int i = 0; i < m_cTrees.length; i++) { float[] fHeights = new float[m_nNrOfLabels * 2 - 1]; float[] fMetas = new float[m_nNrOfLabels * 2 - 1]; int[] nCounts = new int[m_nNrOfLabels * 2 - 1]; collectHeights(m_cTrees[i], fHeights, 0); Arrays.sort(fHeights); collectMetaData(m_cTrees[i], fHeights, 0.0f, 0, fMetas, nCounts); m_fCLinesX[i] = new float[nNodes * 2 + 2]; m_fCLinesY[i] = new float[nNodes * 2 + 2]; for (int j = 0; j < fMetas.length - 1; j++) { m_fCLinesX[i][j * 2] = fMetas[j]; m_fCLinesY[i][j * 2] = (fHeights[j] - m_fTreeOffset) * m_fTreeScale; m_fCLinesX[i][j * 2 + 1] = fMetas[j + 1]; m_fCLinesY[i][j * 2 + 1] = (fHeights[j + 1] - m_fTreeOffset) * m_fTreeScale; } if (nMethod == NodeOrderer.META_AVERAGE) { for (int j = 0; j < fMetas.length - 1; j++) { if (nCounts[j] > 0) { m_fLinesX[i][j * 2] = fMetas[j] / nCounts[j]; } if (nCounts[j + 1] > 0) { m_fLinesX[i][j * 2 + 1] = fMetas[j + 1] / nCounts[j + 1]; } } } } // determine scale float fMaxX = 0; for (float[] fXs : m_fLinesX) { for (float f : fXs) { fMaxX = Math.max(f, fMaxX); } } for (float[] fXs : m_fCLinesX) { for (float f : fXs) { fMaxX = Math.max(f, fMaxX); } } float fScale = m_nNrOfLabels / fMaxX; for (float[] fXs : m_fCLinesX) { for (int i = 0; i < fXs.length; i++) { fXs[i] *= fScale; } } for (float[] fXs : m_fLinesX) { for (int i = 0; i < fXs.length; i++) { fXs[i] *= fScale; } } } break; } if (orderChanged(oldOrder)) { makeDirty(); } // addAction(new DoAction()); } } // reshuffle /** * Reorder leafs by rotating around internal node associated with * iRotationPoint */ void rotateAround(int iRotationPoint) { Vector<Integer> iLeafs = new Vector<Integer>(); getRotationLeafs(m_cTrees[0], -1, iLeafs, iRotationPoint); System.err.println("Rotating " + iRotationPoint + " " + iLeafs); // find rotation range int iMin = m_nOrder.length; int iMax = 0; for (Integer i : iLeafs) { int j = m_nOrder[i]; iMin = Math.min(j, iMin); iMax = Math.max(j, iMax); } for (int i = 0; i < (iMax - iMin) / 2 + 1; i++) { int nTmp = m_nRevOrder[iMin + i]; m_nRevOrder[iMin + i] = m_nRevOrder[iMax - i]; m_nRevOrder[iMax - i] = nTmp; } for (int i = 0; i < m_sLabels.size(); i++) { m_nOrder[m_nRevOrder[i]] = i; } calcPositions(); calcLines(); makeDirty(); addAction(new DoAction()); } // rotateAround void moveRotationPoint(int iRotationPoint, float fdH) { Vector<Integer> iLeafs = new Vector<Integer>(); getRotationLeafs(m_cTrees[0], -1, iLeafs, iRotationPoint); boolean[] bSelection = m_bSelection; m_bSelection = new boolean[m_sLabels.size()]; for (int i : iLeafs) { m_bSelection[i] = true; } for (int i = 0; i < m_trees.length; i++) { moveInternalNode(fdH, m_trees[i], iLeafs.size()); } for (int i = 0; i < m_cTrees.length; i++) { moveInternalNode(fdH, m_cTrees[i], iLeafs.size()); } m_bSelection = bSelection; calcLines(); makeDirty(); } int moveInternalNode(float fdH, Node node, int nSelected) { if (node.isLeaf()) { return (m_bSelection[node.getNr()] ? 1 : 0); } else { int i = moveInternalNode(fdH, node.m_left, nSelected); i += moveInternalNode(fdH, node.m_right, nSelected); if (i == nSelected) { node.m_fPosX += fdH; i++; } return i; } } /** * Determine set of leafs that are under rotation point iRotationPoint. * Results stored in iLeafs as node numbers. */ int getRotationLeafs(Node node, int iPos, Vector<Integer> iLeafs, int iRotationPoint) { if (node.isLeaf()) { iLeafs.add(node.getNr()); } else { iPos = getRotationLeafs(node.m_left, iPos, iLeafs, iRotationPoint); if (iPos == iRotationPoint) { return iPos; } Vector<Integer> iLeafsR = new Vector<Integer>(); if (node.m_right != null) { iPos = getRotationLeafs(node.m_right, iPos, iLeafsR, iRotationPoint); if (iPos == iRotationPoint) { iLeafs.removeAllElements(); iLeafs.addAll(iLeafsR); return iPos; } } iPos++; iLeafs.addAll(iLeafsR); } return iPos; } // getRotationLeafs int getNrOfNodes(Node node) { if (node.isLeaf()) { return 1; } else { int nNodes = getNrOfNodes(node.m_left); if (node.m_right != null) { nNodes += getNrOfNodes(node.m_right); } else { // count one for the dummy node on the right nNodes++; } return nNodes + 1; } } /** * calculate coordinates for lines in real coordinates This initialises the * m_nLines,m_nTLines, m_nCLines and m_nCTLines arrays **/ public void calcLines() { checkSelection(); if (m_trees.length == 0) { return; } setWaitCursor(); // calculate coordinates of lines for drawing trees int nNodes = getNrOfNodes(m_trees[0]); boolean[] b = new boolean[1]; for (int i = 0; i < m_trees.length; i++) { // m_fLinesX[i] = new float[nNodes * 2 + 2]; // m_fLinesY[i] = new float[nNodes * 2 + 2]; if (m_bAllowSingleChild) { nNodes = getNrOfNodes(m_trees[i]); m_fLinesX[i] = new float[nNodes * 2 + 2]; m_fLinesY[i] = new float[nNodes * 2 + 2]; m_trees[i].drawDryWithSingleChild(m_fLinesX[i], m_fLinesY[i], 0, b, m_bSelection, m_fTreeOffset, m_fTreeScale); } else { m_fLinesX[i] = new float[nNodes * 2 + 2]; m_fLinesY[i] = new float[nNodes * 2 + 2]; calcLinesForNode(m_trees[i], m_fLinesX[i], m_fLinesY[i]); } } // calculate coordinates of lines for drawing consensus trees for (int i = 0; i < m_cTrees.length; i++) { // m_fCLinesX[i] = new float[nNodes * 2 + 2]; // m_fCLinesY[i] = new float[nNodes * 2 + 2]; if (m_bAllowSingleChild) { nNodes = getNrOfNodes(m_cTrees[i]); m_fCLinesX[i] = new float[nNodes * 2 + 2]; m_fCLinesY[i] = new float[nNodes * 2 + 2]; m_cTrees[i].drawDryWithSingleChild(m_fCLinesX[i], m_fCLinesY[i], 0, b, m_bSelection, m_fTreeOffset, m_fTreeScale); } else { m_fCLinesX[i] = new float[nNodes * 2 + 2]; m_fCLinesY[i] = new float[nNodes * 2 + 2]; calcLinesForNode(m_cTrees[i], m_fCLinesX[i], m_fCLinesY[i]); } } m_fRLinesX = new float[1][nNodes * 2 + 2]; m_fRLinesY = new float[1][nNodes * 2 + 2]; if (!m_bAllowSingleChild && m_bCladesReady) { calcLinesForNode(m_rootcanaltree, m_fRLinesX[0], m_fRLinesY[0]); } if (m_bUseLogScale) { System.err.println("Use log scaling"); //float f = (float) Math.log(m_fHeight + 1.0); float fNormaliser = (float) (m_fHeight / Math.pow(m_fHeight, m_fExponent)); for (int i = 0; i < m_trees.length; i++) { for (int j = 0; j < m_fLinesY[i].length; j++) { m_fLinesY[i][j] = ((float) Math.pow(m_fHeight - m_fLinesY[i][j], m_fExponent)/fNormaliser); } } for (int i = 0; i < m_cTrees.length; i++) { for (int j = 0; j < m_fCLinesY[i].length; j++) { m_fCLinesY[i][j] = (float) Math.pow(m_fHeight - m_fCLinesY[i][j], m_fExponent)/fNormaliser; } } for (int j = 0; j < m_fRLinesY[0].length; j++) { m_fRLinesY[0][j] = (float) Math.pow(m_fHeight - m_fRLinesY[0][j], m_fExponent)/fNormaliser; } } m_w = 0; for (int i = 0; i < m_cTrees.length; i++) { float[] fCLines = m_fCLinesX[i]; float fWeight = m_fTreeWeight[i]; for (int j = 0; j < fCLines.length - 3; j += 4) { m_w += Math.abs(fCLines[j + 1] - fCLines[j + 2]) * fWeight; } } calcColors(false); calcLineWidths(false); } // calcLines void calcLinesForNode(Node node, float [] fLinesX, float [] fLinesY) { boolean[] b = new boolean[1]; if (m_bAllowSingleChild) { node.drawDryWithSingleChild(fLinesX, fLinesY, 0, b, m_bSelection, m_fTreeOffset, m_fTreeScale); } else { switch (m_Xmode) { case 0: node.drawDry(fLinesX, fLinesY, 0, b, m_bSelection, m_fTreeOffset, m_fTreeScale); break; case 1: node.drawDryCentralised(fLinesX, fLinesY, 0, b, m_bSelection, m_fTreeOffset, m_fTreeScale, new float[2], new float[m_nNrOfLabels * 2 - 1], new float[m_nNrOfLabels * 2 - 1], m_cladePosition); break; case 2: float[] fCladeCenterX = new float[m_nNrOfLabels * 2 - 1]; float[] fCladeCenterY = new float[m_nNrOfLabels * 2 - 1]; float[] fPosX = new float[m_nNrOfLabels * 2 - 1]; float[] fPosY = new float[m_nNrOfLabels * 2 - 1]; node.getStarTreeCladeCenters(fCladeCenterX, fCladeCenterY, m_fTreeOffset, m_fTreeScale, m_cladePosition, m_sLabels.size()); node.drawStarTree(fLinesX, fLinesY, fPosX, fPosY, fCladeCenterX, fCladeCenterY, m_bSelection, m_fTreeOffset, m_fTreeScale); break; } } } /** * calculate coordinates for lines in real coordinates This initialises the * m_nCLines and m_nCTLines (but not m_nLines,m_nTLines), arrays **/ public void calcLineWidths(boolean forceRecalc) { if (!forceRecalc) { if (m_lineWidthMode == m_prevLineWidthMode && m_lineWidthTag != null && m_lineWidthTag.equals(m_prevLineWidthTag) && m_sLineWidthPattern.equals(m_sPrevLineWidthPattern)) { return; } } else { calcPositions(); calcLines(); } m_prevLineWidthMode = m_lineWidthMode; m_prevLineWidthTag = m_lineWidthTag; m_sPrevLineWidthPattern = m_sLineWidthPattern; setWaitCursor(); if (m_sLabels == null) { // no trees loaded return; } if (m_lineWidthMode == LineWidthMode.DEFAULT) { m_fLineWidth = null; m_fCLineWidth = null; m_fTopLineWidth = null; m_fTopCLineWidth = null; m_fRLineWidth = null; m_fRTopLineWidth = null; return; } m_fLineWidth = new float[m_trees.length][]; m_fCLineWidth = new float[m_cTrees.length][]; m_fTopLineWidth = new float[m_trees.length][]; m_fTopCLineWidth = new float[m_cTrees.length][]; m_fRLineWidth = new float[1][]; m_fRTopLineWidth = new float[1][]; checkSelection(); int nNodes = getNrOfNodes(m_trees[0]); if (m_lineWidthMode == LineWidthMode.BY_METADATA_PATTERN) { m_pattern = Pattern.compile(m_sLineWidthPattern); } if (m_lineWidthModeTop == LineWidthMode.BY_METADATA_PATTERN) { m_patternTop = Pattern.compile(m_sLineWidthPatternTop); } // if (m_lineWidthMode == LineWidthMode.BY_METADATA_NUMBER) { // m_pattern = createPattern(); // } // calculate coordinates of lines for drawing trees boolean[] b = new boolean[1]; for (int i = 0; i < m_trees.length; i++) { //m_fLinesX[i] = new float[nNodes * 2 + 2]; //m_fLinesY[i] = new float[nNodes * 2 + 2]; m_fLineWidth[i] = new float[nNodes * 2 + 2]; m_fTopLineWidth[i] = new float[nNodes * 2 + 2]; drawTreeS(m_trees[i], m_fLinesX[i], m_fLinesY[i], m_fLineWidth[i], m_fTopLineWidth[i], 0, b); } // calculate coordinates of lines for drawing consensus trees for (int i = 0; i < m_cTrees.length; i++) { //m_fCLinesX[i] = new float[nNodes * 2 + 2]; //m_fCLinesY[i] = new float[nNodes * 2 + 2]; m_fCLineWidth[i] = new float[nNodes * 2 + 2]; m_fTopCLineWidth[i] = new float[nNodes * 2 + 2]; drawTreeS(m_cTrees[i], m_fCLinesX[i], m_fCLinesY[i], m_fCLineWidth[i], m_fTopCLineWidth[i], 0, b); int nTopologies = 0; float [] fCLineWidth = new float[nNodes * 2 + 2]; float [] fTopCLineWidth = new float[nNodes * 2 + 2]; for (int j = 0; j < m_trees.length; j++) { if (m_nTopologyByPopularity[j] == i) { for (int k = 0; k < fCLineWidth.length; k++) { fCLineWidth[k] += m_fLineWidth[j][k]; fTopCLineWidth[k] += m_fTopLineWidth[j][k]; } nTopologies++; } } for (int k = 0; k < fCLineWidth.length; k++) { fCLineWidth[k] /= nTopologies; fTopCLineWidth[k] /= nTopologies; } m_fCLineWidth[i] = fCLineWidth; m_fTopCLineWidth[i] = fTopCLineWidth; } // TODO: don't know how to set line width of root canal tree, so keep it unspecified m_fRLineWidth[0] = new float[nNodes * 2 + 2]; m_fRTopLineWidth[0] = new float[nNodes * 2 + 2]; drawTreeS(m_rootcanaltree, m_fRLinesX[0], m_fRLinesY[0], m_fRLineWidth[0], m_fRTopLineWidth[0], 0, b); m_fRLineWidth = null; m_fRTopLineWidth = null; } // calcLinesWidths /** variables that deal with width of lines **/ public enum LineWidthMode {BY_METADATA_PATTERN, BY_METADATA_NUMBER, DEFAULT, BY_METADATA_TAG}; public LineWidthMode m_lineWidthMode = LineWidthMode.DEFAULT; public LineWidthMode m_lineWidthModeTop = LineWidthMode.DEFAULT; LineWidthMode m_prevLineWidthMode = null; public String m_sLineWidthPattern = DEFAULT_PATTERN; public String m_sLineWidthPatternTop = DEFAULT_PATTERN; String m_sPrevLineWidthPattern = null; public String m_lineWidthTag; public String m_lineWidthTagTop; String m_prevLineWidthTag; /** variables that deal with coloring of lines **/ public enum LineColorMode {COLOR_BY_CLADE, BY_METADATA_PATTERN, DEFAULT, COLOR_BY_METADATA_TAG}; public enum MetaDataType {NUMERIC, STRING, SET}; public LineColorMode m_lineColorMode = LineColorMode.DEFAULT; public LineColorMode m_prevLineColorMode = null; public String m_sLineColorPattern = DEFAULT_PATTERN; String m_sPrevLineColorPattern = null; //List<String> m_colorMetaDataCategories = new ArrayList<String>(); Map<String,Integer> m_colorMetaDataCategories = new HashMap<String, Integer>(); public List<String> m_metaDataTags = new ArrayList<String>(); public List<MetaDataType> m_metaDataTypes = new ArrayList<MetaDataType>(); public String m_lineColorTag; String m_prevLineColorTag; public boolean m_showLegend = false; public void calcColors(boolean forceRecalc) { if (!forceRecalc) { if (m_lineColorMode == m_prevLineColorMode && m_lineColorTag != null && m_lineColorTag.equals(m_prevLineColorTag) && m_sLineColorPattern.equals(m_sPrevLineColorPattern)) { return; } } if (m_sLabels == null) { // no trees loaded return; } setWaitCursor(); m_prevLineColorMode = m_lineColorMode; m_prevLineColorTag = m_lineColorTag; m_sPrevLineColorPattern = m_sLineColorPattern; int nNodes = getNrOfNodes(m_trees[0]); switch (m_lineColorMode) { case COLOR_BY_CLADE: m_nLineColor = new int[m_trees.length][]; m_nCLineColor = new int[m_cTrees.length][]; m_nRLineColor = new int[1][]; for (int i = 0; i < m_trees.length; i++) { if (m_bAllowSingleChild) { nNodes = getNrOfNodes(m_trees[i]); } m_nLineColor[i] = new int[nNodes * 2 + 2]; colorTree(m_trees[i], m_nLineColor[i], 0); } if (m_bAllowSingleChild) { break; } // calculate coordinates of lines for drawing consensus trees for (int i = 0; i < m_cTrees.length; i++) { int nTopologies = 0; //if (m_bAllowSingleChild) { // nNodes = getNrOfNodes(m_cTrees[i]); //} m_nCLineColor[i] = new int[nNodes * 2 + 2]; int [] nCLineColor = m_nCLineColor[i]; for (int j = 0; j < m_trees.length; j++) { for (int k = 0; k < nCLineColor.length; k++) { nCLineColor[k] += m_nLineColor[j][k]; } nTopologies++; } for (int k = 0; k < nCLineColor.length; k++) { nCLineColor[k] /= nTopologies; } } //if (m_bAllowSingleChild) { // break; //} m_nRLineColor[0] = new int[nNodes * 2 + 2]; Arrays.fill(m_nRLineColor[0], m_color[ROOTCANALCOLOR].getRGB()); break; case BY_METADATA_PATTERN: m_pattern = Pattern.compile(m_sLineColorPattern); m_nLineColor = new int[m_trees.length][]; m_nCLineColor = new int[m_cTrees.length][]; m_nRLineColor = new int[1][]; //m_colorMetaDataCategories = new ArrayList<String>(); m_colorMetaDataCategories = new HashMap<String, Integer>(); for (int i = 0; i < m_trees.length; i++) { if (m_bAllowSingleChild) { nNodes = getNrOfNodes(m_trees[i]); } m_nLineColor[i] = new int[nNodes * 2 + 2]; colorTreeByMetaData(m_trees[i], m_nLineColor[i], 0); } if (m_bAllowSingleChild) { break; } // calculate coordinates of lines for drawing consensus trees for (int i = 0; i < m_cTrees.length; i++) { int nTopologies = 0; //if (m_bAllowSingleChild) { // nNodes = getNrOfNodes(m_cTrees[i]); //} m_nCLineColor[i] = new int[nNodes * 2 + 2]; int [] nCLineColor = m_nCLineColor[i]; for (int j = 0; j < m_trees.length; j++) { for (int k = 0; k < nCLineColor.length; k++) { nCLineColor[k] += m_nLineColor[j][k]; } nTopologies++; } for (int k = 0; k < nCLineColor.length; k++) { nCLineColor[k] /= nTopologies; } } //if (m_bAllowSingleChild) { // break; //} m_nRLineColor[0] = new int[nNodes * 2 + 2]; Arrays.fill(m_nRLineColor[0], m_color[ROOTCANALCOLOR].getRGB()); break; case COLOR_BY_METADATA_TAG: m_pattern = Pattern.compile(m_sPattern); m_nLineColor = new int[m_trees.length][]; m_nCLineColor = new int[m_cTrees.length][]; m_nRLineColor = new int[1][]; //m_colorMetaDataCategories = new ArrayList<String>(); m_colorMetaDataCategories = new HashMap<String, Integer>(); boolean colorByCategory = false; for (int i = 0; i < m_metaDataTags.size(); i++) { if (m_metaDataTags.get(i).equals(m_lineColorTag)) { if (m_metaDataTypes.get(i).equals(MetaDataType.STRING)) { colorByCategory = true; } break; } } for (int i = 0; i < m_trees.length; i++) { if (m_bAllowSingleChild) { nNodes = getNrOfNodes(m_trees[i]); } m_nLineColor[i] = new int[nNodes * 2 + 2]; colorTreeByMetaDataTag(m_trees[i], m_nLineColor[i], 0, colorByCategory); } if (m_bAllowSingleChild) { break; } // calculate coordinates of lines for drawing consensus trees for (int i = 0; i < m_cTrees.length; i++) { int nTopologies = 0; // it is known m_bAllowSingleChild = false at this point //if (m_bAllowSingleChild) { // nNodes = getNrOfNodes(m_cTrees[i]); //} m_nCLineColor[i] = new int[nNodes * 2 + 2]; int [] nCLineColor = m_nCLineColor[i]; for (int j = 0; j < m_trees.length; j++) { for (int k = 0; k < nCLineColor.length; k++) { nCLineColor[k] += m_nLineColor[j][k]; } nTopologies++; } for (int k = 0; k < nCLineColor.length; k++) { nCLineColor[k] /= nTopologies; } } m_nRLineColor[0] = new int[nNodes * 2 + 2]; Arrays.fill(m_nRLineColor[0], m_color[ROOTCANALCOLOR].getRGB()); break; case DEFAULT: m_nLineColor = new int[m_trees.length][]; m_nCLineColor = new int[m_cTrees.length][]; m_nRLineColor = new int[1][]; for (int i = 0; i < m_trees.length; i++) { if (m_bAllowSingleChild) { nNodes = getNrOfNodes(m_trees[i]); } m_nLineColor[i] = new int[nNodes * 2 + 2]; int color = 0; switch (m_nTopologyByPopularity[i]) { case 0: color = m_color[0].getRGB(); break; case 1: color = m_color[1].getRGB(); break; case 2: color = m_color[2].getRGB(); break; default: color = m_color[3].getRGB(); } Arrays.fill(m_nLineColor[i], color); } for (int i = 0; i < m_cTrees.length; i++) { int color = m_color[CONSCOLOR].getRGB(); if (m_bViewMultiColor) { color = m_color[9 + (i % (m_color.length - 9))].getRGB(); } if (m_bAllowSingleChild) { nNodes = getNrOfNodes(m_cTrees[i]); } m_nCLineColor[i] = new int[nNodes * 2 + 2]; Arrays.fill(m_nCLineColor[i], color); } if (m_bAllowSingleChild) { break; } m_nRLineColor[0] = new int[nNodes * 2 + 2]; Arrays.fill(m_nRLineColor[0], m_color[ROOTCANALCOLOR].getRGB()); break; } } // calcColors private int colorTreeByMetaData(Node node, int[] nLineColor, int iPos) { if (!node.isLeaf()) { iPos = colorTreeByMetaData(node.m_left, nLineColor, iPos); if (node.m_right != null) { iPos = colorTreeByMetaData(node.m_right, nLineColor, iPos); } int color = m_color[9 + getMetaDataCategory(node.m_left) % (m_color.length - 9)].getRGB(); nLineColor[iPos++] = color; nLineColor[iPos++] = color; if (node.m_right != null) { color = m_color[9 + getMetaDataCategory(node.m_right) % (m_color.length - 9)].getRGB(); } nLineColor[iPos++] = color; nLineColor[iPos++] = color; if (node.isRoot()) { nLineColor[iPos++] = color; nLineColor[iPos++] = color; } } return iPos; } private int colorTreeByMetaDataTag(Node node, int[] nLineColor, int iPos, boolean colorByCategory) { if (!node.isLeaf()) { iPos = colorTreeByMetaDataTag(node.m_left, nLineColor, iPos, colorByCategory); if (node.m_right != null) { iPos = colorTreeByMetaDataTag(node.m_right, nLineColor, iPos, colorByCategory); } int color = colorForNode(node.m_left, colorByCategory); nLineColor[iPos++] = color; nLineColor[iPos++] = color; if (node.m_right != null) { color = colorForNode(node.m_right, colorByCategory); } nLineColor[iPos++] = color; nLineColor[iPos++] = color; if (node.isRoot()) { nLineColor[iPos++] = color; nLineColor[iPos++] = color; } } return iPos; } int colorForNode(Node node, boolean colorByCategory) { int color = 0; Object o = node.getMetaDataSet().get(m_lineColorTag); if (colorByCategory || m_bColorByCategory) { if (o != null) { if (m_colorMetaDataCategories.get(o.toString()) == null) { m_colorMetaDataCategories.put(o.toString(), m_colorMetaDataCategories.size()); } // if (!m_colorMetaDataCategories.contains(o)) { // m_colorMetaDataCategories.add(o.toString()); // } // color = m_color[9 + m_colorMetaDataCategories.indexOf(o.toString()) % (m_color.length - 9)].getRGB(); int i = m_colorMetaDataCategories.get(o.toString()); System.err.println(i + " " + (9 + i % (m_color.length - 9)) + " " + m_color.length); color = m_color[9 + i % (m_color.length - 9)].getRGB(); } } else { if (o != null) { double frac = (((Double) o) - Node.g_minValue.get(m_lineColorTag)) / (Node.g_maxValue.get(m_lineColorTag) - Node.g_minValue.get(m_lineColorTag)); color = Color.HSBtoRGB((float) frac, 0.5f, 0.8f); } } return color; } private int colorTree(Node node, int[] nLineColor, int iPos) { if (node != null && !node.isLeaf()) { iPos = colorTree(node.m_left, nLineColor, iPos); iPos = colorTree(node.m_right, nLineColor, iPos); int color = m_color[9+node.m_iClade%9].getRGB(); nLineColor[iPos++] = color; nLineColor[iPos++] = color; nLineColor[iPos++] = color; nLineColor[iPos++] = color; if (node.isRoot()) { nLineColor[iPos++] = color; nLineColor[iPos++] = color; } } return iPos; } /** * 'draw' tree into an array of x & positions. This draws the tree as * block diagram. * * @param nX * @param nY * @param iPos * @return */ int drawTreeS(Node node, float[] nX, float[] nY, float[] fWidth, float[] fWidthTop, int iPos, boolean[] bNeedsDrawing) { if (node.isLeaf()) { bNeedsDrawing[0] = m_bSelection[node.m_iLabel]; } else { boolean[] bChildNeedsDrawing = new boolean[2]; iPos = drawTreeS(node.m_left, nX, nY, fWidth, fWidthTop, iPos, bNeedsDrawing); bChildNeedsDrawing[0] = bNeedsDrawing[0]; if (node.m_right != null) { iPos = drawTreeS(node.m_right, nX, nY, fWidth, fWidthTop, iPos, bNeedsDrawing); bChildNeedsDrawing[1] = bNeedsDrawing[0]; } else { bChildNeedsDrawing[1] = false; } bNeedsDrawing[0] = false; if (bChildNeedsDrawing[0]) { // nX[iPos] = node.m_left.m_fPosX; // nY[iPos] = node.m_left.m_fPosY; fWidth[iPos] = getGamma(node.m_left, 1, m_lineWidthMode, m_lineWidthTag, m_pattern); if (m_lineWidthModeTop == LineWidthMode.DEFAULT) { fWidthTop[iPos] = fWidth[iPos]; } else { fWidthTop[iPos] = getGamma(node.m_left, 2, m_lineWidthModeTop, m_lineWidthTagTop, m_patternTop); } iPos++; // nX[iPos] = nX[iPos - 1]; // nY[iPos] = node.m_fPosY; bNeedsDrawing[0] = true; } else { fWidth[iPos] = m_nTreeWidth; // nX[iPos] = node.m_fPosX; // nY[iPos] = node.m_fPosY; iPos++; // nX[iPos] = node.m_fPosX; // nY[iPos] = node.m_fPosY; } fWidth[iPos] = fWidth[iPos-1]; fWidthTop[iPos] = fWidthTop[iPos-1]; iPos++; if (bChildNeedsDrawing[1]) { // nX[iPos] = node.m_right.m_fPosX; // nY[iPos] = nY[iPos - 1]; fWidth[iPos] = getGamma(node.m_right, 1, m_lineWidthMode, m_lineWidthTag, m_pattern); if (m_lineWidthModeTop == LineWidthMode.DEFAULT) { fWidthTop[iPos] = fWidth[iPos]; } else { fWidthTop[iPos] = getGamma(node.m_right, 2, m_lineWidthModeTop, m_lineWidthTagTop, m_patternTop); } iPos++; // nX[iPos] = nX[iPos - 1]; // nY[iPos] = node.m_right.m_fPosY; bNeedsDrawing[0] = true; } else { // nX[iPos] = node.m_fPosX; // nY[iPos] = node.m_fPosY; fWidth[iPos] = m_nTreeWidth; iPos++; // nX[iPos] = node.m_fPosX; // nY[iPos] = node.m_fPosY; } fWidth[iPos] = fWidth[iPos-1]; fWidthTop[iPos] = fWidthTop[iPos-1]; iPos++; if (m_lineWidthModeTop == LineWidthMode.DEFAULT && m_bCorrectTopOfBranch) { float fCurrentWidth = getGamma(node, 1, m_lineWidthMode, m_lineWidthTag, m_pattern); float fSumWidth = fWidth[iPos-2] + fWidth[iPos-4]; fWidthTop[iPos-2] = fCurrentWidth * fWidth[iPos-2]/fSumWidth; fWidthTop[iPos-4] = fCurrentWidth * fWidth[iPos-4]/fSumWidth; } if (node.isRoot()) { // nX[iPos] = node.m_fPosX; // nY[iPos] = node.m_fPosY; fWidth[iPos] = 0;//getGamma(node, 1); fWidthTop[iPos] = 0;//getGamma(node, 1); iPos++; // nX[iPos] = node.m_fPosX; // nY[iPos] = node.m_fPosY - node.m_fLength; iPos++; } } return iPos; } float getGamma(Node node , int nGroup, LineWidthMode mode, String tag, Pattern pattern) { try { if (mode == LineWidthMode.BY_METADATA_PATTERN) { String sMetaData = node.getMetaData(); try { Matcher matcher = pattern.matcher(sMetaData); matcher.find(); int nGroups = matcher.groupCount(); if (nGroup > nGroups) { nGroup = 1; } String sMatch = matcher.group(nGroup); float f = Float.parseFloat(sMatch); return f; } catch (Exception e) { } } else if (mode == LineWidthMode.BY_METADATA_NUMBER) { int index = 0; if (nGroup == 1) { index = m_iPatternForBottom - 1; } else { index = m_iPatternForTop - 1; if (index < 0) { index = m_iPatternForBottom - 1; } } if (index < 0) { index = 0; } return (float) (node.getMetaDataList().get(index)/Node.g_maxListValue.get(index)); } else { Map<String,Object> map = node.getMetaDataSet(); if (map != null) { Object o = map.get(tag); if (o != null) { try { if (m_bWidthsAreZeroBased) { double frac = ((Double) o) /Node.g_maxValue.get(m_lineWidthTag); return (float) frac; } else { double frac = (((Double) o) - Node.g_minValue.get(m_lineWidthTag)) / (Node.g_maxValue.get(m_lineWidthTag) - Node.g_minValue.get(m_lineWidthTag)); return (float) frac; } } catch (Exception e) { // ignore } } } } } catch (Exception e) { // ignore } return 1f; } /** initialise order of leafs **/ int initOrder(Node node, int iNr) throws Exception { if (node.isLeaf()) { m_nOrder[node.m_iLabel] = iNr; m_nRevOrder[iNr] = node.m_iLabel; return iNr + 1; } else { iNr = initOrder(node.m_left, iNr); if (node.m_right != null) { iNr = initOrder(node.m_right, iNr); } } return iNr; } /** number of leafs in selection, handy for sanity checks **/ int selectionSize() { int nSelected = 0; for (int i = 0; i < m_nRevOrder.length; i++) { if (m_bSelection[m_nRevOrder[i]]) { nSelected++; } } return nSelected; } /** check the selection is empty, and ask user whether this is desirable **/ void checkSelection() { if (m_bSelection.length > 0 && selectionSize() == 0) { for (int i = 0; i < m_bSelection.length; i++) { m_bSelection[i] = true; } } } /** check at least one, but not all labels are selected **/ boolean moveSanityChek() { int nSelected = selectionSize(); if (nSelected > 0 && nSelected < m_nRevOrder.length - 1) { return true; } JOptionPane.showMessageDialog(null, "To move labels, select at least one, but not all of the labels", "Move error", JOptionPane.PLAIN_MESSAGE); return false; } /** move labels in selection down in ordering **/ void moveSelectedLabelsDown() { for (int i = 1; i < m_nRevOrder.length; i++) { if (m_bSelection[m_nRevOrder[i]] && !m_bSelection[m_nRevOrder[i - 1]]) { int h = m_nRevOrder[i]; m_nRevOrder[i] = m_nRevOrder[i - 1]; m_nRevOrder[i - 1] = h; } } for (int i = 0; i < m_nRevOrder.length; i++) { m_nOrder[m_nRevOrder[i]] = i; } calcPositions(); addAction(new DoAction()); } /** move labels in selection up in ordering **/ void moveSelectedLabelsUp() { for (int i = m_nRevOrder.length - 2; i >= 0; i--) { if (m_bSelection[m_nRevOrder[i]] && !m_bSelection[m_nRevOrder[i + 1]]) { int h = m_nRevOrder[i]; m_nRevOrder[i] = m_nRevOrder[i + 1]; m_nRevOrder[i + 1] = h; } } for (int i = 0; i < m_nRevOrder.length; i++) { m_nOrder[m_nRevOrder[i]] = i; } calcPositions(); addAction(new DoAction()); } public void calcPositions() { if (m_sLabels == null) { // no trees loaded yet return; } setWaitCursor(); if (!m_bAllowSingleChild && m_bCladesReady) { Arrays.fill(m_cladePosition, -1); boolean bProgress = true; do { bProgress = false; for (int i = 0; i < m_clades.size(); i++) { if (m_cladePosition[i] < 0) { m_cladePosition[i] = positionClades(i); if (m_cladePosition[i] >= 0) { bProgress = true; } } } } while (bProgress); if (m_bUseAngleCorrection) { for (int i = 0; i < m_clades.size(); i++) { if (m_cladeWeight.get(i) > m_fAngleCorrectionThresHold) { for (ChildClade child : m_cladeChildren.get(i)) { if (m_cladeWeight.get(child.m_iLeft) < m_fAngleCorrectionThresHold) { m_cladePosition[child.m_iLeft] = m_cladePosition[i]; } if (m_cladeWeight.get(child.m_iRight) < m_fAngleCorrectionThresHold) { m_cladePosition[child.m_iRight] = m_cladePosition[i]; } } } } } } for (int i = 0; i < m_trees.length; i++) { if (m_nShuffleMode == NodeOrderer.GEOINFO) { positionLeafsGeo(m_trees[i]); } else { positionLeafs(m_trees[i]); } positionRest(m_trees[i]); } for (int i = 0; i < m_cTrees.length; i++) { if (m_nShuffleMode == NodeOrderer.GEOINFO) { positionLeafsGeo(m_cTrees[i]); } else { positionLeafs(m_cTrees[i]); } positionRest(m_cTrees[i]); } if (!m_bAllowSingleChild && m_bCladesReady) { for (Node tree : m_summaryTree) { positionLeafs(tree); positionRest(tree); } } } private float positionClades(int iClade) { float fMin = m_nNrOfLabels; float fMax = 0; for (int i : m_clades.get(iClade)) { fMin = Math.min(fMin, m_nOrder[i]); fMax = Math.max(fMax, m_nOrder[i]); } return (fMin + fMax) / 2.0f + 0.5f; } /** * Position leafs in a tree so that x-coordinate of the leafs is fixed for * all trees in the set * **/ void positionLeafs(Node node) { if (node.isLeaf()) { node.m_fPosX = m_nOrder[node.m_iLabel] + 0.5f; if (m_cladePosition != null) { //node.m_fPosX += m_cladePosition[node.m_iLabel]; } // node.m_fPosX = _posX[m_nOrder[node.m_iLabel]] + 0.5f; } else { positionLeafs(node.m_left); if (node.m_right != null) { positionLeafs(node.m_right); } } } /** * Position leafs in a tree so that x-coordinate of the leafs coincides with * the geographical position associated with the node * **/ void positionLeafsGeo(Node node) { if (node.isLeaf()) { if (m_treeDrawer.m_bRootAtTop) { node.m_fPosX = m_nNrOfLabels * (m_fLongitude.elementAt(node.m_iLabel) - m_fMinLong) / (m_fMaxLong - m_fMinLong); } else { node.m_fPosX = m_nNrOfLabels * (m_fMaxLat - m_fLatitude.elementAt(node.m_iLabel)) / (m_fMaxLat - m_fMinLat); } } else { positionLeafsGeo(node.m_left); positionLeafsGeo(node.m_right); } } /** * Position internal nodes to take position in between child nodes Should be * called after positionLeafs to ensure leaf positions are initialized * * @param node * @return */ float positionRest(Node node) { if (node.isLeaf()) { return node.m_fPosX; } else { // node.m_fPosX = m_cladePosition[node.m_iClade]; float fPosX = 0; fPosX += positionRest(node.m_left); if (node.m_right != null) { fPosX += positionRest(node.m_right); fPosX /= 2.0; } node.m_fPosX = fPosX; return fPosX; } } /** * return meta data value of a node as defined by the pattern (m_sPattern & * m_pattern), or 1 if parsing fails. */ // int [] m_nCurrentPosition; float getMetaData(Node node) { try { Matcher matcher = m_pattern.matcher(node.getMetaData()); matcher.find(); int nGroup = 1; int nGroups = matcher.groupCount(); if (nGroup > nGroups) { nGroup = 1; } return Float.parseFloat(matcher.group(nGroup)); } catch (Exception e) { } return 1f; } // getMetaData int getMetaDataCategory(Node node) { try { Matcher matcher = m_pattern.matcher(node.getMetaData()); matcher.find(); int nGroup = 1; int nGroups = matcher.groupCount(); if (nGroup > nGroups) { nGroup = 1; } String match = matcher.group(nGroup); if (m_colorMetaDataCategories.get(match) == null) { m_colorMetaDataCategories.put(match, m_colorMetaDataCategories.size()); } return m_colorMetaDataCategories.get(match); // if (!m_colorMetaDataCategories.contains(match)) { // m_colorMetaDataCategories.add(match); // } // //System.err.println(node.m_sMetaData + ": " + match + " = " + m_metaDataCategories.indexOf(match)); // return m_colorMetaDataCategories.indexOf(match); } catch (Exception e) { //e.printStackTrace(); } return 0; } // getMetaData double positionMetaAll(Node node) { node.m_fPosX = getMetaData(node); if (!node.isLeaf()) { double fX1 = positionMetaAll(node.m_left); double fX2 = positionMetaAll(node.m_right); return Math.max(fX1, Math.max(fX2, node.m_fPosX)); } return node.m_fPosX; } // positionMetaAll void scaleX(Node node, double fScale) { node.m_fPosX = (float) (m_sLabels.size() - node.m_fPosX * fScale); if (!node.isLeaf()) { scaleX(node.m_left, fScale); scaleX(node.m_right, fScale); } } // scaleX int collectHeights(Node node, float[] fHeights, int iPos) { fHeights[iPos++] = node.m_fPosY;// fLengthToRoot + node.m_fLength; if (!node.isLeaf()) { iPos = collectHeights(node.m_left, fHeights, iPos); iPos = collectHeights(node.m_right, fHeights, iPos); } return iPos; } // collectHeights int collectMetaData(Node node, float[] fHeights, float fLengthToRoot, int iPos, float[] fMetas, int[] nCounts) { float fHeight = node.m_fPosY;// fLengthToRoot + node.m_fLength; float fMeta = getMetaData(node); int i = Arrays.binarySearch(fHeights, fHeight); while (i >= 0 && fHeights[i] > fLengthToRoot) { fMetas[i] += fMeta; nCounts[i]++; i--; } if (!node.isLeaf()) { iPos = collectMetaData(node.m_left, fHeights, fHeight/* * fLengthToRoot * + * node.m_fLength */, iPos, fMetas, nCounts); iPos = collectMetaData(node.m_right, fHeights, fHeight/* * fLengthToRoot * + * node.m_fLength */, iPos, fMetas, nCounts); } return iPos; } // collectMetaData /** * record position information in position array (fPosX) used for undo/redo **/ void getPosition(Node node, float[] fPosX) { if (node.isLeaf()) { fPosX[m_nOrder[node.m_iLabel]] = node.m_fPosX; } else { getPosition(node.m_left, fPosX); if (node.m_right != null) { getPosition(node.m_right, fPosX); } } } /** * set position information based on position array (fPosX) used for * undo/redo **/ void setPosition(Node node, float[] fPosX) { if (node.isLeaf()) { node.m_fPosX = fPosX[m_nOrder[node.m_iLabel]]; } else { setPosition(node.m_left, fPosX); if (node.m_right != null) { setPosition(node.m_right, fPosX); } } } /** * position nodes so that root node is at fOffset (initially 0) and rest * according to lengths of the branches */ public float positionHeight(Node node, float fOffSet) { if (node.isLeaf()) { node.m_fPosY = fOffSet + node.m_fLength; return node.m_fPosY; } else { float fPosY = fOffSet + node.m_fLength; float fYMax = 0; fYMax = Math.max(fYMax, positionHeight(node.m_left, fPosY)); if (node.m_right != null) { fYMax = Math.max(fYMax, positionHeight(node.m_right, fPosY)); } node.m_fPosY = fPosY; return fYMax; } } float height(Node node) { if (node.isLeaf()) { return node.m_fLength; } else { return node.m_fLength + Math.max(height(node.m_left), height(node.m_right)); } } /** move y-position of a tree with offset f **/ public void offsetHeight(Node node, float f) { if (!node.isLeaf()) { offsetHeight(node.m_left, f); if (node.m_right != null) { offsetHeight(node.m_right, f); } } node.m_fPosY += f; } /** * move divide y-position of a tree with factor f. Useful to calculate * consensus trees. **/ void divideLength(Node node, float f) { if (!node.isLeaf()) { divideLength(node.m_left, f); if (node.m_right != null) { divideLength(node.m_right, f); } } node.m_fLength /= f; } /** * add length of branches in src to that of target Useful to calculate * consensus trees. Assumes src and target share same topology */ void addLength(Node src, Node target) { // assumes same topologies for src and target if (!src.isLeaf()) { addLength(src.m_left, target.m_left); if (src.m_right != null) { addLength(src.m_right, target.m_right); } } target.m_fLength += src.m_fLength; } /** draw only labels of a tree, not the branches **/ void drawLabels(Node node, Graphics2D g) { if (m_bHideLabels) { return; } g.setFont(m_font); if (m_bShowBounds) { return; } if (node.isLeaf()) { if (m_bSelection[node.m_iLabel]) { if (m_iColor == null) { g.setColor(m_color[LABELCOLOR]); } else { g.setColor(m_color[GEOCOLOR + m_iColor[node.m_iLabel] % (m_color.length - GEOCOLOR)]); } } else { g.setColor(Color.GRAY); } if (m_treeDrawer.m_bRootAtTop) { if (m_bRotateTextWhenRootAtTop) { int x = (int) (node.m_fPosX * m_fScaleX /* m_fScale */) - g.getFontMetrics().getHeight() / 3; int y = getPosY(((m_bAlignLabels ? m_fHeight:node.m_fPosY) + m_fLabelIndent - m_fTreeOffset) * m_fTreeScale) + 2;// ; g.rotate(Math.PI / 2.0); g.translate(y, -x); g.drawString(m_sLabels.elementAt(node.m_iLabel), 0, 0); g.translate(-y, x); g.rotate(-Math.PI / 2.0); Rectangle r = m_bLabelRectangle[node.m_iLabel]; r.x = x; r.y = y - 10; r.height = 10; r.width = m_nLabelWidth; drawImage(g, x, y, node.m_iLabel); } else { String sLabel = m_sLabels.elementAt(node.m_iLabel); int x = (int) (node.m_fPosX * m_fScaleX /* m_fScale */) - g.getFontMetrics().stringWidth(sLabel) / 2; int y = getPosY(((m_bAlignLabels ?m_fHeight:node.m_fPosY) + m_fLabelIndent - m_fTreeOffset) * m_fTreeScale) + g.getFontMetrics().getHeight() + 2; g.drawString(sLabel, x, y); Rectangle r = m_bLabelRectangle[node.m_iLabel]; r.x = x; r.y = y - 10; r.height = 10; r.width = m_nLabelWidth; drawImage(g, x, y, node.m_iLabel); } } else { int y = (int) (node.m_fPosX * m_fScaleY/* m_fScale */) + g.getFontMetrics().getHeight() / 3; int x = getPosX(((m_bAlignLabels ?m_fHeight:node.m_fPosY) + m_fLabelIndent - m_fTreeOffset) * m_fTreeScale) + 1; g.drawString(m_sLabels.elementAt(node.m_iLabel), x, y); Rectangle r = m_bLabelRectangle[node.m_iLabel]; r.x = x; r.y = y - 10; r.height = 10; r.width = m_nLabelWidth; drawImage(g, x, y, node.m_iLabel); } } else { drawLabels(node.m_left, g); if (node.m_right != null) { drawLabels(node.m_right, g); } } } private void drawImage(Graphics g, int x, int y, int iLabel) { if (m_LabelImages != null && m_LabelImages[iLabel] != null) { BufferedImage img = m_LabelImages[iLabel]; g.drawImage(img, x, y-m_nImageSize, x+m_nImageSize, y, 0, 0, img.getWidth(), img.getHeight(), null); } } /** draw lines from labels of a tree to corresponding geographic point **/ void drawGeo(Node node, Graphics g) { if (node.isLeaf()) { if (m_bSelection[node.m_iLabel]) { if (m_fLongitude.elementAt(node.m_iLabel) == 0 && m_fLatitude.elementAt(node.m_iLabel) == 0) { return; } int gx = (int) ((m_fLongitude.elementAt(node.m_iLabel) - m_fMinLong) * m_fScaleGX * m_fScale); int gy = (int) ((m_fMaxLat - m_fLatitude.elementAt(node.m_iLabel)) * m_fScaleGY * m_fScale); g.setColor(m_color[GEOCOLOR]); if (m_treeDrawer.m_bRootAtTop) { int x = (int) (node.m_fPosX * m_fScaleX * m_fScale) + 10; int y = getPosY(node.m_fPosY * m_fScale); g.drawLine(x, y, gx, gy); g.setColor(Color.BLACK); g.drawOval(gx - 1, gy - 1, 3, 3); } else { int y = (int) (node.m_fPosX * m_fScaleY * m_fScale); int x = getPosX(node.m_fPosY * m_fScale); if (m_bInvertLongitude) { x = 0; } g.drawLine(x, y, gx, gy); g.setColor(Color.BLACK); g.drawOval(gx - 1, gy - 1, 3, 3); } Rectangle r = m_bGeoRectangle[node.m_iLabel]; r.x = gx - 2; r.y = gy - 2; r.width = 5; r.height = 5; } } else { drawGeo(node.m_left, g); drawGeo(node.m_right, g); } } /** * convert height info in tree to y position on screen -- use when root at * top **/ int getPosY(float fHeight) { if (m_bUseLogScale) { return (int) (m_fHeight / Math.log(m_fHeight + 1.0) * m_fScaleY * (Math.log(m_fHeight + 1.0) - Math.log(m_fHeight - fHeight + 1.0))); } return (int) (fHeight * m_fScaleY); } float screenPosToHeight(int nX, int nY) { if (m_bUseLogScale) { return Float.NaN; } if (m_treeDrawer.m_bRootAtTop) { return (m_fHeight - ((nY/ m_fScaleY) + m_fTreeOffset)) * m_fUserScale; } else { return (m_fHeight - ((nX/ m_fScaleX) + m_fTreeOffset)) * m_fUserScale; } } /** * convert height info in tree to x position on screen -- use when root not * at top **/ int getPosX(float fHeight) { if (m_bUseLogScale) { return (int) ((m_fHeight / Math.log(m_fHeight + 1.0) * m_fScaleX * (Math.log(m_fHeight + 1.0) - Math .log(m_fHeight - fHeight + 1.0)))); } return (int) (fHeight * m_fScaleX); } /** * Fits the tree to the current screen size. Call this after window has been * created to get the entire tree to be in view upon launch. */ public void fitToScreen() { if (m_sLabels == null) { // no trees loaded yet return; } m_fScaleX = 10; m_fScaleY = 10; int nW = (int) (getWidth() / m_fScale) - 24; int nH = (int) (getHeight() / m_fScale) - 24; nW = getWidth() - 24; nH = getHeight() - 24; if (m_treeDrawer.m_bRootAtTop) { m_fScaleX = (nW + 0.0f) / m_sLabels.size(); m_fScaleGX = (nW + 0.0f) / (m_fMaxLong - m_fMinLong); if (m_fHeight > 0) { if (m_bRotateTextWhenRootAtTop) { m_fScaleY = (nH - m_nLabelWidth - 0.0f) / m_fHeight; m_fScaleGY = (nH - m_nLabelWidth - 0.0f) / (m_fMaxLat - m_fMinLat); } else { m_fScaleY = (nH - 10.0f) / m_fHeight; m_fScaleGY = (nH - 10.0f) / (m_fMaxLat - m_fMinLat); } } } else { if (m_sLabels != null && m_sLabels.size() > 0) { m_fScaleY = (nH + 0.0f) / m_sLabels.size(); m_fScaleGY = (nH + 0.0f) / (m_fMaxLat - m_fMinLat); } if (m_fHeight > 0) { m_fScaleX = (nW - m_nLabelWidth + 0.0f) / m_fHeight; m_fScaleGX = (nW - m_nLabelWidth + 0.0f) / (m_fMaxLong - m_fMinLong); } } m_Panel.setPreferredSize(new Dimension((int) (nW * m_fScale), (int) (nH * m_fScale))); m_fScaleX *= m_fScale; m_fScaleY *= m_fScale; m_jScrollPane.revalidate(); makeDirty(); // System.err.println("Scale " + m_fScaleX + " " + m_fScaleY); } @Override public void componentHidden(ComponentEvent e) { } @Override public void componentMoved(ComponentEvent e) { } @Override public void componentResized(ComponentEvent e) { fitToScreen(); // makeDirty(); } @Override public void componentShown(ComponentEvent e) { } /** object that draws a single tree on an image **/ public TreeDrawer m_treeDrawer = new TreeDrawer(); void selectMode(int nXmode) { switch (nXmode) { case 0: // default m_Xmode = 0; m_bUseAngleCorrection = false; m_treeDrawer.m_bViewBlockTree = false; m_viewClades.setEnabled(false); m_viewEditTree.setEnabled(true); break; case 1: // star tree m_Xmode = 2; m_bUseAngleCorrection = false; m_treeDrawer.m_bViewBlockTree = false; m_viewClades.setEnabled(true); m_viewEditTree.setEnabled(false); break; case 2: // centralised m_Xmode = 1; m_bUseAngleCorrection = false; m_treeDrawer.m_bViewBlockTree = false; m_viewClades.setEnabled(true); m_viewEditTree.setEnabled(false); break; case 3: // centralised + angle corrected m_Xmode = 1; m_bUseAngleCorrection = true; m_treeDrawer.m_bViewBlockTree = false; m_viewClades.setEnabled(true); m_viewEditTree.setEnabled(false); break; } // Enumeration<AbstractButton> enumeration = m_modeGroup.getElements(); // for (int i = 0; i < nXmode; i++) { // enumeration.nextElement(); // } // m_modeGroup.setSelected(enumeration.nextElement().getModel(), true); calcPositions(); calcLines(); makeDirty(); for (ChangeListener listener : m_changeListeners) { listener.stateChanged(null); } } int m_nStyle = 0; public void resetStyle() { setStyle(m_nStyle); } void setStyle(int nStyle) { m_nStyle = nStyle; BranchDrawer bd = null; switch (nStyle) { case 0: if (m_lineWidthMode != LineWidthMode.DEFAULT) { bd = new TrapeziumBranchDrawer(); } else { bd = new BranchDrawer(); } m_treeDrawer.m_bViewBlockTree = false; break; case 1: if (m_lineWidthMode != LineWidthMode.DEFAULT) { bd = new TrapeziumBranchDrawer(); } else { bd = new BranchDrawer(); } m_treeDrawer.m_bViewBlockTree = true; break; case 2: //if (m_lineWidthMode == LineWidthMode.DEFAULT) { bd = new ArcBranchDrawer(); m_treeDrawer.m_bViewBlockTree = false; //} break; // case 2: bd = new KoruBranchDrawer();break; // case 3: bd = new TrapeziumBranchDrawer();break; // case 3: bd = new BrownianBridgeBranchDrawer();break; case 3: //if (m_lineWidthMode == LineWidthMode.DEFAULT) { bd = new SteepArcBranchDrawer(); m_treeDrawer.m_bViewBlockTree = false; //} break; } if (bd != null) { m_treeDrawer.setBranchDrawer(bd); makeDirty(); } } Icon getIcon(String sIcon) { java.net.URL tempURL = ClassLoader.getSystemResource(ICONPATH + sIcon + ".png"); if (tempURL != null) { return new ImageIcon(tempURL); } return null; } /** this contains the TreeSetPanel */ JScrollPane m_jScrollPane; /** panel for drawing the trees **/ public TreeSetPanel m_Panel = null; /** the menu bar for this application. */ JMenuBar m_menuBar; /** status bar at bottom of window */ final JLabel m_jStatusBar = new JLabel("Status bar");; /** toolbar containing buttons at top of window */ final JToolBar m_jTbTools = new JToolBar(); final JPanel m_jTbTools2 = new JPanel(); final JToolBar m_jTbCladeTools = new JToolBar(); /** font for all text being printed (e.g. labels, height info) **/ public Font m_font = new Font("sansserif", Font.PLAIN, 12); public boolean m_bAlignLabels = false; /** flag to indicate consensus trees should be shown **/ public boolean m_bViewCTrees = false; /** flag to indicate all individual trees should be shown **/ public boolean m_bViewAllTrees = true; /** use log scaling for drawing height **/ boolean m_bUseLogScale = false; double m_fExponent = 1.0; /** show consensus tree in multiple colours, or just main colour */ public boolean m_bViewMultiColor = false; /** show geographical info if available **/ public boolean m_bDrawGeo = true; /** * flag to indicate animation should overwrite trees instead of clearing * screen every time **/ boolean m_bAnimateOverwrite = false; /** tree currently being drawn **/ int m_iAnimateTree; /** delay between drawing of two trees in animation **/ int m_nAnimationDelay = 100; /** automatically refresh screen when settings are changed **/ boolean m_bAutoRefresh = true; /** flag to indicate screen is out of sync with settings **/ boolean m_bIsDirty = true; /** * mode for viewing DRAW = draw all trees ANIMATE = animate through trees * BROWSE = browse individual trees */ public enum ViewMode { DRAW, ANIMATE, BROWSE }; ViewMode m_viewMode = ViewMode.DRAW; public void makeDirty() { m_Panel.m_rotationPoints = null; if (m_bAutoRefresh) { m_Panel.clearImage(); } else { m_bIsDirty = true; } repaint(); } public JMenuBar getMenuBar() { return m_menuBar; } /** * Base class used for defining actions with a name, tool tip text, possibly * an icon and accelerator key. * */ class MyAction extends AbstractAction { /** for serialization */ private static final long serialVersionUID = -2038911111935517L; public MyAction(String sName, String sToolTipText, String sIcon, int acceleratorKey) { super(sName); KeyStroke acceleratorKeystroke = KeyStroke.getKeyStroke(acceleratorKey, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()); if ((acceleratorKey & InputEvent.ALT_DOWN_MASK) > 0) { acceleratorKeystroke = KeyStroke.getKeyStroke(acceleratorKey - InputEvent.ALT_DOWN_MASK, InputEvent.ALT_DOWN_MASK); } // setToolTipText(sToolTipText); putValue(Action.SHORT_DESCRIPTION, sToolTipText); putValue(Action.LONG_DESCRIPTION, sToolTipText); if (acceleratorKeystroke != null && acceleratorKeystroke.getKeyCode() >= 0) { putValue(Action.ACCELERATOR_KEY, acceleratorKeystroke); } putValue(Action.MNEMONIC_KEY, new Integer(sName.charAt(0))); java.net.URL tempURL = ClassLoader.getSystemResource("viz/icons/" + sIcon + ".png"); //if (true || !viz.util.Util.isMac()) { if (tempURL != null) { putValue(Action.SMALL_ICON, new ImageIcon(tempURL)); } else { putValue(Action.SMALL_ICON, new ImageIcon(new BufferedImage(20, 20, BufferedImage.TYPE_4BYTE_ABGR))); } //} } // c'tor // public MyAction(String sName, String sToolTipText, String sIcon, String sAcceleratorKey) { // this(sName, sToolTipText, sIcon, KeyStroke.getKeyStroke(sAcceleratorKey)); // } // c'tor public MyAction(String sName, String sToolTipText, String sIcon, KeyStroke acceleratorKeystroke) { super(sName); // setToolTipText(sToolTipText); putValue(Action.SHORT_DESCRIPTION, sToolTipText); putValue(Action.LONG_DESCRIPTION, sToolTipText); if (acceleratorKeystroke != null && acceleratorKeystroke.getKeyCode() >= 0) { putValue(Action.ACCELERATOR_KEY, acceleratorKeystroke); } putValue(Action.MNEMONIC_KEY, new Integer(sName.charAt(0))); java.net.URL tempURL = ClassLoader.getSystemResource("viz/icons/" + sIcon + ".png"); if (!viz.util.Util.isMac()) { if (tempURL != null) { putValue(Action.SMALL_ICON, new ImageIcon(tempURL)); } else { putValue(Action.SMALL_ICON, new ImageIcon(new BufferedImage(20, 20, BufferedImage.TYPE_4BYTE_ABGR))); } } } // c'tor void setIcon(String sIcon) { java.net.URL tempURL = ClassLoader.getSystemResource(ICONPATH + sIcon + ".png"); if (tempURL != null) { putValue(Action.SMALL_ICON, new ImageIcon(tempURL)); } else { putValue(Action.SMALL_ICON, new ImageIcon(new BufferedImage(20, 20, BufferedImage.TYPE_4BYTE_ABGR))); } } /** * Place holder. Should be implemented by derived classes. (non-Javadoc) */ @Override public void actionPerformed(ActionEvent ae) { } } // class MyAction /** base class for actions that allow customisation of a color **/ class ColorAction extends MyAction { private static final long serialVersionUID = 1L; int m_iColor; public ColorAction(String sName, String sToolTipText, String sIcon, int nAcceleratorKey, int iColor) { super(sName, sToolTipText, sIcon, nAcceleratorKey); m_iColor = iColor; } @Override public void actionPerformed(ActionEvent ae) { Color newColor = JColorChooser.showDialog(m_Panel, getName(), m_color[m_iColor]); if (newColor != null) { m_color[m_iColor] = newColor; makeDirty(); } repaint(); } } // class ColorAction class ShuffleAction extends MyAction { private static final long serialVersionUID = 1L; int m_nMode; public ShuffleAction(String sName, String sToolTipText, String sIcon, int nAcceleratorKey, int nMode) { super(sName, sToolTipText, sIcon, nAcceleratorKey); m_nMode = nMode; } @Override public void actionPerformed(ActionEvent ae) { setWaitCursor(); //m_Panel.setCursor(new Cursor(Cursor.WAIT_CURSOR)); reshuffle(m_nMode); setDefaultCursor(); //m_Panel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } }; // class ActionReshuffle /** base class dealing with update of internal state **/ class SettingAction extends MyAction { private static final long serialVersionUID = 1L; String m_sName; public SettingAction(String sName, String sToolTipText, String sIcon, int nAcceleratorKey) { super(sName, sToolTipText, sIcon, nAcceleratorKey); m_sName = sName; } @Override public void actionPerformed(ActionEvent ae) { if (m_sName.equals("Jitter+")) { m_nJitter++; if (m_nJitter >= 0) { makeDirty(); } } if (m_sName.equals("Jitter-")) { m_nJitter--; if (m_nJitter >= 0) { makeDirty(); } } if (m_sName.equals("Intensity+")) { m_fTreeIntensity *= 1.1; makeDirty(); } if (m_sName.equals("Intensity-")) { m_fTreeIntensity /= 1.1; makeDirty(); } if (m_sName.equals("Consensus Intensity+")) { m_fCTreeIntensity *= 1.1; makeDirty(); } if (m_sName.equals("Consensus Intensity-")) { m_fCTreeIntensity /= 1.1; makeDirty(); } if (m_sName.equals("Consensus Tree Width+")) { m_nCTreeWidth++; makeDirty(); } if (m_sName.equals("Consensus Tree Width-")) { m_nCTreeWidth--; if (m_nCTreeWidth <= 1) { m_nCTreeWidth = 1; } makeDirty(); } if (m_sName.equals("Tree Width+")) { m_nTreeWidth++; makeDirty(); } if (m_sName.equals("Tree Width-")) { m_nTreeWidth--; if (m_nTreeWidth <= 1) { m_nTreeWidth = 1; } makeDirty(); } if (m_sName.equals("Drawing Threads+")) { m_Panel.stopDrawThreads(); m_Panel.m_nDrawThreads++; m_Panel.m_drawThread = new Thread[m_Panel.m_nDrawThreads]; } if (m_sName.equals("Drawing Threads-")) { if (m_Panel.m_nDrawThreads > 1) { m_Panel.stopDrawThreads(); m_Panel.m_nDrawThreads--; m_Panel.m_drawThread = new Thread[m_Panel.m_nDrawThreads]; } } if (m_sName.equals("Animation Speed-")) { m_nAnimationDelay += 1 + m_nAnimationDelay / 10; } if (m_sName.equals("Animation Speed+")) { if (m_nAnimationDelay > 0) { m_nAnimationDelay -= 1 + m_nAnimationDelay / 10; } } if (m_sName.equals("Angle Correction+")) { m_fAngleCorrectionThresHold *= 1.1; if (m_fAngleCorrectionThresHold > 0.999) { m_fAngleCorrectionThresHold = 0.999; } System.err.println("Angle Correction ThresHold = " + m_fAngleCorrectionThresHold); calcPositions(); calcLines(); makeDirty(); } if (m_sName.equals("Angle Correction-")) { m_fAngleCorrectionThresHold /= 1.1; System.err.println("Angle Correction ThresHold = " + m_fAngleCorrectionThresHold); calcPositions(); calcLines(); makeDirty(); } repaint(); System.err.print(getStatus()); } // actionPerformed } // class SettingAction /** actions triggered by GUI events */ public Action a_quit = new MyAction("Exit", "Exit Program", "exit", -1) { private static final long serialVersionUID = -10; @Override public void actionPerformed(ActionEvent ae) { System.exit(0); } }; // class ActionQuit Action a_paste = new MyAction("Paste", "Paste tree(s) from clipboard", "paste", KeyEvent.VK_V) { private static final long serialVersionUID = -10; @Override public void actionPerformed(ActionEvent ae) { Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); Transferable contents = clipboard.getContents(null); boolean hasTransferableText = (contents != null) && contents.isDataFlavorSupported(DataFlavor.stringFlavor); if (hasTransferableText) { try { String sResult = (String) contents.getTransferData(DataFlavor.stringFlavor); String sFileName = "tmp.clipboard"; PrintStream out = new PrintStream(sFileName); out.print(sResult); out.close(); init(sFileName); calcLines(); m_jStatusBar.setText("Loaded from clipboard"); fitToScreen(); } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "Error pasting from clipboard: " + e.getMessage(), "File paste error", JOptionPane.PLAIN_MESSAGE); } } } }; // class ActionPaste abstract class MyFileFilter extends FileFilter { @Override public boolean accept(File f) { return f.isDirectory() || f.getName().toLowerCase().endsWith(getExtention()); } abstract public String getExtention(); } Action a_export = new MyAction("Export", "Export", "export", -1) { private static final long serialVersionUID = -1; @Override public void actionPerformed(ActionEvent ae) { JFileChooser fc = new JFileChooser(m_sDir); fc.addChoosableFileFilter(new MyFileFilter() { @Override public String getExtention() { return ".bmp"; } @Override public String getDescription() { return "Bitmap files (*.bmp)"; } }); fc.addChoosableFileFilter(new MyFileFilter() { @Override public String getExtention() { return ".jpg"; } @Override public String getDescription() { return "JPEG bitmap files (*.jpg)"; } }); fc.addChoosableFileFilter(new MyFileFilter() { @Override public String getExtention() { return ".png"; } @Override public String getDescription() { return "PNG bitmap files (*.png)"; } }); fc.addChoosableFileFilter(new MyFileFilter() { @Override public String getExtention() { return ".pdf"; } @Override public String getDescription() { return "PDF files (*.pdf)"; } }); fc.addChoosableFileFilter(new MyFileFilter() { public String getExtention() { return ".svg"; } public String getDescription() { return "Standard Vector Graphics files"; } }); fc.setDialogTitle("Export DensiTree As"); int rval = fc.showSaveDialog(m_Panel); if (rval == JFileChooser.APPROVE_OPTION) { // System.out.println("Saving to file \""+ // f.getAbsoluteFile().toString()+"\""); String sFileName = fc.getSelectedFile().toString(); if (sFileName.lastIndexOf('/') > 0) { m_sDir = sFileName.substring(0, sFileName.lastIndexOf('/')); } if (sFileName != null && !sFileName.equals("")) { if (!(sFileName.toLowerCase().endsWith(".png") || sFileName.toLowerCase().endsWith(".jpg") || sFileName.toLowerCase().endsWith(".pdf") || sFileName.toLowerCase().endsWith(".bmp") || sFileName.toLowerCase().endsWith(".svg"))) { sFileName += ((MyFileFilter) fc.getFileFilter()).getExtention(); } if (sFileName.toLowerCase().endsWith(".pdf")) { exportPDF(sFileName); repaint(); return; } else if (sFileName.toLowerCase().endsWith(".png") || sFileName.toLowerCase().endsWith(".jpg") || sFileName.toLowerCase().endsWith(".bmp")) { BufferedImage bi; Graphics g; bi = new BufferedImage(m_Panel.getWidth(), m_Panel.getHeight(), BufferedImage.TYPE_INT_RGB); g = bi.getGraphics(); g.setPaintMode(); g.setColor(getBackground()); g.fillRect(0, 0, m_Panel.getWidth(), m_Panel.getHeight()); m_Panel.printAll(g); try { if (sFileName.toLowerCase().endsWith(".png")) { ImageIO.write(bi, "png", new File(sFileName)); } else if (sFileName.toLowerCase().endsWith(".jpg")) { ImageIO.write(bi, "jpg", new File(sFileName)); } else if (sFileName.toLowerCase().endsWith(".bmp")) { ImageIO.write(bi, "bmp", new File(sFileName)); } } catch (Exception e) { JOptionPane.showMessageDialog(null, sFileName + " was not written properly: " + e.getMessage()); e.printStackTrace(); } return; } if (sFileName.toLowerCase().endsWith(".svg")) { m_Panel.toSVG(sFileName); return; } JOptionPane.showMessageDialog(null, "Extention of file " + sFileName + " not recognized as png,bmp,jpg or svg file"); } } } }; // class ActionExport void exportPDF(String sFileName) { try { com.itextpdf.text.Document doc = new com.itextpdf.text.Document(); PdfWriter writer = PdfWriter.getInstance(doc, new FileOutputStream(sFileName)); doc.setPageSize(new com.itextpdf.text.Rectangle(m_Panel.getWidth(), m_Panel.getHeight())); doc.open(); PdfContentByte cb = writer.getDirectContent(); Graphics2D g = new PdfGraphics2D(cb, m_Panel.getWidth(), m_Panel.getHeight()); //BufferedImage bi; //bi = new BufferedImage(m_Panel.getWidth(), m_Panel.getHeight(), BufferedImage.TYPE_INT_RGB); //g = bi.getGraphics(); g.setPaintMode(); g.setColor(getBackground()); g.fillRect(0, 0, m_Panel.getWidth(), m_Panel.getHeight()); m_Panel.paint(g); //m_Panel.printAll(g); g.dispose(); doc.close(); } catch (Exception e) { JOptionPane.showMessageDialog(m_Panel, "Export may have failed: " + e.getMessage()); } } Action a_print = new MyAction("Print", "Print Graph", "print", KeyEvent.VK_P) { private static final long serialVersionUID = -20389001859354L; // boolean m_bIsPrinting = false; @Override public void actionPerformed(ActionEvent ae) { PrinterJob printJob = PrinterJob.getPrinterJob(); printJob.setPrintable(m_Panel); if (printJob.printDialog()) try { // m_bIsPrinting = true; printJob.print(); // m_bIsPrinting = false; } catch (PrinterException pe) { // m_bIsPrinting = false; } } // actionPerformed }; // class ActionPrint Action a_new = new MyAction("New", "New instance of DensiTree", "new", KeyEvent.VK_N) { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent ae) { startNew(new String[]{}); } }; Action a_load = new MyAction("Load", "Load tree set", "open", KeyEvent.VK_O) { private static final long serialVersionUID = -2038911085935515L; @Override public void actionPerformed(ActionEvent ae) { File [] files = Util.getFile("Load Tree Set", true, new File(m_sDir), false, "Nexus trees files", "trees","tre","nex","t"); if (files != null && files.length > 0) { doOpen(files[0].getPath()); } // JFileChooser fc = new JFileChooser(m_sDir); // fc.addChoosableFileFilter(new FileFilter() { // public boolean accept(File f) { // if (f.isDirectory()) { // return true; // } // String name = f.getName().toLowerCase(); // if (name.endsWith(".trees")) { // return true; // } // if (name.endsWith(".tre")) { // return true; // } // if (name.endsWith(".nex")) { // return true; // } // if (name.endsWith(".t")) { // return true; // } // return false; // } // // // The description of this filter // public String getDescription() { // return "Nexus trees files"; // } // }); // // fc.setDialogTitle("Load Tree Set"); // int rval = fc.showOpenDialog(m_Panel); // // if (rval == JFileChooser.APPROVE_OPTION) { // doOpen(fc.getSelectedFile().toString()); // // makeDirty(); // } } }; // class ActionLoad public void doOpen(String sFileName) { if (sFileName.lastIndexOf('/') > 0) { m_sDir = sFileName.substring(0, sFileName.lastIndexOf('/')); } try { //m_sKMLFile = null; init(sFileName); calcLines(); } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "Error loading file: " + e.getMessage(), "File load error", JOptionPane.PLAIN_MESSAGE); return; } m_jStatusBar.setText("Loaded " + sFileName); fitToScreen(); } public Action a_loadkml = new MyAction("Load locations", "Load geographic locations of taxa", "geo", -1) { private static final long serialVersionUID = -1L; @Override public void actionPerformed(ActionEvent ae) { JFileChooser fc = new JFileChooser(m_sDir); fc.addChoosableFileFilter(new FileFilter() { @Override public boolean accept(File f) { if (f.isDirectory()) { return true; } String name = f.getName().toLowerCase(); if (name.endsWith(".kml") || name.endsWith(".kmz")) { return true; } return false; } // The description of this filter @Override public String getDescription() { return "KML file with taxon locations"; } }); fc.addChoosableFileFilter(new FileFilter() { @Override public boolean accept(File f) { if (f.isDirectory()) { return true; } String name = f.getName().toLowerCase(); if (name.endsWith(".txt") || name.endsWith(".dat")) { return true; } return false; } // The description of this filter @Override public String getDescription() { return "text file with taxon locations, tab delimited"; } }); fc.setDialogTitle("Load Geographic Locations"); int rval = fc.showOpenDialog(m_Panel); if (rval == JFileChooser.APPROVE_OPTION) { String sFileName = fc.getSelectedFile().toString(); if (sFileName.lastIndexOf('/') > 0) { m_sDir = sFileName.substring(0, sFileName.lastIndexOf('/')); } m_sKMLFile = sFileName; loadKML(); m_jStatusBar.setText("Loaded " + sFileName); fitToScreen(); // makeDirty(); } } }; // class ActionLoadKML Action a_saveas = new MyAction("Save as", "Save as", "save", KeyEvent.VK_S) { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent ae) { JFileChooser fc = new JFileChooser(m_sDir); fc.addChoosableFileFilter(new FileFilter() { @Override public boolean accept(File f) { if (f.isDirectory()) { return true; } String name = f.getName().toLowerCase(); if (name.endsWith(".trees")) { return true; } if (name.endsWith(".tre")) { return true; } if (name.endsWith(".nex")) { return true; } if (name.endsWith(".t")) { return true; } return false; } // The description of this filter @Override public String getDescription() { return "Nexus trees files"; } }); fc.setDialogTitle("Save Graph"); int rval = fc.showSaveDialog(m_Panel); if (rval == JFileChooser.APPROVE_OPTION) { String sFileName = fc.getSelectedFile().toString(); if (sFileName.lastIndexOf('/') > 0) { m_sDir = sFileName.substring(0, sFileName.lastIndexOf('/')); } try { FileWriter outfile = new FileWriter(sFileName); StringBuffer buf = new StringBuffer(); buf.append("#NEXUS\n"); buf.append("Begin trees\n"); buf.append("\tTranslate\n"); for (int i = 0; i < m_sLabels.size(); i++) { buf.append("\t\t" + i + " " + m_sLabels.get(i)); if (i < m_sLabels.size() - 1) { buf.append(","); } buf.append("\n"); } buf.append(";\n"); outfile.write(buf.toString()); for (int i = 0; i < m_trees.length; i++) { outfile.write("tree STATE_" + i + " = " + m_trees[i].toString() + ";\n"); System.out.println(m_trees[i].toString(m_sLabels, false)); } outfile.write("End;\n"); outfile.close(); } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "Error writing file: " + e.getMessage(), "File save error", JOptionPane.PLAIN_MESSAGE); return; } m_jStatusBar.setText("Saved " + sFileName); fitToScreen(); } } }; // class ActionSaveAs public void loadImages() { JFileChooser fc = new JFileChooser(m_sDir); fc.setDialogTitle("Load Image Map (text file mapping taxon names on image files)"); int rval = fc.showOpenDialog(m_Panel); if (rval == JFileChooser.APPROVE_OPTION) { String sFileName = fc.getSelectedFile().toString(); if (sFileName.lastIndexOf('/') > 0) { m_sDir = sFileName.substring(0, sFileName.lastIndexOf('/')); } try { m_LabelImages = new BufferedImage[m_sLabels.size()]; BufferedReader fin = new BufferedReader(new FileReader(sFileName)); //StringBuffer buf = new StringBuffer(); String sStr = null; // eat up the header fin.readLine(); while (fin.ready()) { sStr = fin.readLine(); if (!sStr.trim().equals("")) { String [] sStrs = sStr.split("\\s+"); if (sStrs.length != 2) { JOptionPane.showMessageDialog(m_Panel, "Found \"" + sStr + "\" but expected only two words on a line"); m_LabelImages = null; fin.close(); return; } String sLabel = sStrs[0].toLowerCase(); String imageFile = sStrs[1]; int k = 0; while (k < m_sLabels.size() && !m_sLabels.get(k).toLowerCase().equals(sLabel)) { k++; } if (k == m_sLabels.size()) { JOptionPane.showMessageDialog(m_Panel, "Taxon \"" + sLabel + "\" could not be found"); m_LabelImages = null; fin.close(); return; } System.err.println("Loading " + imageFile); File file = new File(imageFile); if (file.exists()) { m_LabelImages[k] = ImageIO.read(file); } else { System.err.println("File " + imageFile + " does not exist"); } } } fin.close(); } catch (OutOfMemoryError e) { JOptionPane.showMessageDialog(null, "Error loading file: " + e.getMessage(), "File load error", JOptionPane.PLAIN_MESSAGE); m_LabelImages = null; return; } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "Error loading file: " + e.getMessage(), "File load error", JOptionPane.PLAIN_MESSAGE); m_LabelImages = null; return; } makeDirty(); } } Action a_loadimage = new MyAction("Background image ", "Load background image", "bgimage", -1) { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent ae) { JFileChooser fc = new JFileChooser(m_sDir); fc.addChoosableFileFilter(new FileFilter() { @Override public boolean accept(File f) { if (f.isDirectory()) { return true; } String name = f.getName().toLowerCase(); if (name.endsWith(".jpg") || name.endsWith(".png") || name.endsWith(".gif")) { return true; } return false; } // The description of this filter @Override public String getDescription() { return "Image files"; } }); fc.setDialogTitle("Load Background Image"); int rval = fc.showOpenDialog(m_Panel); if (rval == JFileChooser.APPROVE_OPTION) { String sFileName = fc.getSelectedFile().toString(); if (sFileName.lastIndexOf('/') > 0) { m_sDir = sFileName.substring(0, sFileName.lastIndexOf('/')); } try { loadBGImage(sFileName); } catch (OutOfMemoryError e) { JOptionPane.showMessageDialog(null, "Error loading file: " + e.getMessage(), "File load error", JOptionPane.PLAIN_MESSAGE); return; } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, "Error loading file: " + e.getMessage(), "File load error", JOptionPane.PLAIN_MESSAGE); return; } makeDirty(); } } }; // class ActionLoadImage void loadBGImage(String sFileName) throws Exception { m_bgImage = ImageIO.read(new File(sFileName)); try { Pattern pattern = Pattern .compile(".*\\(([0-9\\.Ee-]+),([0-9\\.Ee-]+)\\)x\\(([0-9\\.Ee-]+),([0-9\\.Ee-]+)\\).*"); Matcher matcher = pattern.matcher(sFileName); matcher.find(); m_fBGImageBox[1] = Float.parseFloat(matcher.group(1)); m_fBGImageBox[0] = Float.parseFloat(matcher.group(2)); m_fBGImageBox[3] = Float.parseFloat(matcher.group(3)); m_fBGImageBox[2] = Float.parseFloat(matcher.group(4)); } catch (Exception e) { final double[] fBGImageBox = { -180, -90, 180, 90 }; m_fBGImageBox = fBGImageBox; } } // loadBGImage Action a_viewClades = new MyAction("View clades", "List clades and their densities", "viewclades", -1) { private static final long serialVersionUID = -1; @Override public void actionPerformed(ActionEvent ae) { JDialog dlg = new JDialog(); dlg.setModal(true); dlg.setSize(400, 400); setWaitCursor(); //m_Panel.setCursor(new Cursor(Cursor.WAIT_CURSOR)); String sCladeText = ""; for (String s : cladesToString()) { sCladeText += s; } setDefaultCursor(); //m_Panel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); JTextArea textArea = new JTextArea(sCladeText); JScrollPane scrollPane = new JScrollPane(textArea); dlg.add(scrollPane); dlg.setTitle("Clades and their probabilities"); dlg.setVisible(true); } }; // ActionViewClades Action a_help = new MyAction("Help", "DensiTree - Tree Set Visualization Help", "help", -1) { private static final long serialVersionUID = -20389110859354L; @Override public void actionPerformed(ActionEvent ae) { String sStatus = getStatus(); String sCmdLineOptions = "\n\nTo start with the same settings, use the following command:\njava -jar DensiTree.jar -c " + m_fCTreeIntensity + " -i " + m_fTreeIntensity + " -j " + m_nJitter + " -w " + m_nCTreeWidth + " -v " + m_nTreeWidth + " -f " + m_nAnimationDelay + " -t " + m_Panel.m_nDrawThreads + " -b " + m_nBurnIn; System.out.println(sCmdLineOptions); JOptionPane.showMessageDialog(null, banner() + sStatus + sCmdLineOptions, "Help Message", JOptionPane.PLAIN_MESSAGE); } }; // class ActionHelp public Action a_about = new MyAction("About", "Help about", "about", -1) { private static final long serialVersionUID = -20389110859353L; @Override public void actionPerformed(ActionEvent ae) { if (JOptionPane.showOptionDialog(null, "DensiTree - Tree Set Visualization\nVersion: " + VERSION + "\n\nRemco Bouckaert\nremco@cs.waikato.ac.nz\nremco@cs.auckland.ac.nz\n(c) 2010-2014\n\n" + "Citation:\n" + CITATION, "About Message", JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE, getIcon("DensiTree"), new String[]{"Copy citation to clipboard","Close"},"Close") == 0) { Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(CITATION), null); } } }; // class ActionAbout Action a_labelwidth = new MyAction("Label width", "Label width when root at left", "labelwidth", -1) { private static final long serialVersionUID = -2L; @Override public void actionPerformed(ActionEvent ae) { String sLabeWidth = JOptionPane.showInputDialog("Labe Width:", m_nLabelWidth + ""); if (sLabeWidth != null) { try { m_nLabelWidth = Integer.parseInt(sLabeWidth); } catch (Exception e) { } fitToScreen(); } } }; // class ActionLabelWidth Action a_burnin = new MyAction("Burn in", "Burn in", "burnin", -1) { private static final long serialVersionUID = -2L; @Override public void actionPerformed(ActionEvent ae) { String sBurnIn = JOptionPane.showInputDialog("Burn in:", m_nBurnIn + ""); if (sBurnIn != null) { try { m_nBurnIn = Integer.parseInt(sBurnIn); init(m_sFileName); calcLines(); fitToScreen(); } catch (Exception e) { } } } }; // class ActionBurnin Action a_geolinewidth = new MyAction("Geo line width", "Geographical line width", "geolinewidth", -1) { private static final long serialVersionUID = -2L; @Override public void actionPerformed(ActionEvent ae) { String sGeoWidth = JOptionPane.showInputDialog("Geographical line width:", m_nGeoWidth + ""); if (sGeoWidth != null) { try { m_nGeoWidth = Integer.parseInt(sGeoWidth); m_Panel.clearImage(); repaint(); } catch (Exception e) { } } } }; // class ActionGeoWidth Action a_viewstatusbar = new MyAction("View statusbar", "View statusbar", "statusbar", -1) { private static final long serialVersionUID = -20389330812354L; @Override public void actionPerformed(ActionEvent ae) { m_jStatusBar.setVisible(!m_jStatusBar.isVisible()); } // actionPerformed }; // class ActionViewStatusbar Action a_viewtoolbar = new MyAction("View toolbar", "View toolbar", "toolbar", -1) { private static final long serialVersionUID = -20389110812354L; @Override public void actionPerformed(ActionEvent ae) { m_jTbTools.setVisible(!m_jTbTools.isVisible()); } // actionPerformed }; // class ActionViewToolbar Action a_viewtoolbar2 = new MyAction("View Sidebar", "View Sidebar", "sidebar", -1) { private static final long serialVersionUID = -20389110812354L; @Override public void actionPerformed(ActionEvent ae) { m_jTbTools2.setVisible(!m_jTbTools2.isVisible()); } // actionPerformed }; // class ActionViewToolbar Action a_viewcladetoolbar = new MyAction("View clade toolbar", "View clade toolbar", "cladetoolbar", -1) { private static final long serialVersionUID = -1; @Override public void actionPerformed(ActionEvent ae) { m_jTbCladeTools.setVisible(!m_jTbCladeTools.isVisible()); if (m_jTbCladeTools.isVisible()) { JSplitPane pane = (JSplitPane) m_Panel.getParent().getParent().getParent().getParent(); // int loc = pane.getDividerLocation(); // Set a proportional location pane.setDividerLocation(0.8); } } // actionPerformed }; // class ActionViewToolbar Action a_zoomin = new MyAction("Zoom in", "Zoom in", "zoomin", KeyEvent.VK_EQUALS) { private static final long serialVersionUID = -2038911085935515L; @Override public void actionPerformed(ActionEvent ae) { m_fScale *= 1.2; a_zoomout.setEnabled(true); fitToScreen(); m_jStatusBar.setText("Zooming in"); } }; // class ActionZoomIn Action a_zoomout = new MyAction("Zoom out", "Zoom out", "zoomout", KeyEvent.VK_MINUS) { private static final long serialVersionUID = -203891108593551L; @Override public void actionPerformed(ActionEvent ae) { m_fScale /= 1.2; if (m_fScale <= 1.000001) { m_fScale = 1.0f; a_zoomout.setEnabled(false); } fitToScreen(); m_jStatusBar.setText("Zooming out"); } }; // class ActionZoomOut Action a_zoomintree = new MyAction("Zoom in height", "Zoom in tree height", "zoominh", KeyEvent.VK_X) { private static final long serialVersionUID = -1; @Override public void actionPerformed(ActionEvent ae) { m_fTreeScale *= 1.2; m_fTreeOffset = m_fHeight - m_fHeight / m_fTreeScale; a_zoomouttree.setEnabled(true); calcLines(); m_Panel.clearImage(); makeDirty(); m_jStatusBar.setText("Zooming in tree height"); } }; // class ActionZoomInTree Action a_zoomouttree = new MyAction("Zoom out height", "Zoom out tree height", "zoomouth", KeyEvent.VK_X | KeyEvent.ALT_DOWN_MASK) { private static final long serialVersionUID = -1; @Override public void actionPerformed(ActionEvent ae) { m_fTreeScale /= 1.2; if (m_fTreeScale <= 1.000001) { m_fTreeScale = 1.0f; a_zoomouttree.setEnabled(false); } m_fTreeOffset = m_fHeight - m_fHeight / m_fTreeScale; calcLines(); m_Panel.clearImage(); makeDirty(); m_jStatusBar.setText("Zooming out tree height"); } }; // class ActionZoomOutTree MyAction a_animateStart = new MyAction("Start", "Start Animation", "start", KeyEvent.VK_D|KeyEvent.ALT_DOWN_MASK) { private static final long serialVersionUID = -1L; @Override public void actionPerformed(ActionEvent ae) { if (m_viewMode == ViewMode.ANIMATE) { m_viewMode = ViewMode.BROWSE; a_animateStart.setIcon("start"); } else { if (m_viewMode != ViewMode.BROWSE) { m_iAnimateTree = 0; } m_viewMode = ViewMode.ANIMATE; a_animateStart.setIcon("stop"); } m_Panel.repaint(); } }; // class ActionAnimateStart Action a_drawtreeset = new MyAction("Draw Tree Set", "Draw Tree Set", "redraw", KeyEvent.VK_R) { private static final long serialVersionUID = -4L; @Override public void actionPerformed(ActionEvent ae) { setWaitCursor(); System.err.println("MODS=" + ae.getModifiers()); if (ae.getModifiers() == 18) { // when menu item is selected & ctrl key is pressed System.err.println("start recording: results in /tmp"); m_nFrameNr = 0; m_bRecord = true; } m_viewMode = ViewMode.DRAW; a_animateStart.setIcon("start"); if (m_bIsDirty) { System.err.println("calclines"); calcLines(); } m_Panel.clearImage(); repaint(); System.gc(); } // actionPerformed }; // class ActionDrawTreeSet Action a_selectAll = new MyAction("Select All", "Select All", "selectall", KeyEvent.VK_A) { private static final long serialVersionUID = 5L; @Override public void actionPerformed(ActionEvent ae) { for (int i = 0; i < m_bSelection.length; i++) { m_bSelection[i] = true; // m_bSelection[i] = m_fLatitude.get(i)==0 && // m_fLongitude.get(i)==0; } repaint(); } // actionPerformed };// class ActionSelectAll Action a_unselectAll = new MyAction("Unselect All", "Unselect All", "unselectall", KeyEvent.VK_U) { private static final long serialVersionUID = 5L; @Override public void actionPerformed(ActionEvent ae) { for (int i = 0; i < m_bSelection.length; i++) { m_bSelection[i] = false; } repaint(); } // actionPerformed };// class ActionUnSelectAll Action a_del = new MyAction("Delete", "Delete selected", "del", -1) { //KeyEvent.VK_DELETE) { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent ae) { int nDeleted = 0; for (int i = m_bSelection.length - 1; i >= 0 && m_nNrOfLabels > 2; i--) { if (m_bSelection[i]) { // delete node with nr i for (int j = 0; j < m_trees.length; j++) { m_trees[j] = deleteLeaf(m_trees[j], i); renumber(m_trees[j], i); m_trees[j].labelInternalNodes(m_nNrOfLabels - 1); } for (int j = 0; j < m_cTrees.length; j++) { m_cTrees[j] = deleteLeaf(m_cTrees[j], i); renumber(m_cTrees[j], i); m_cTrees[j].labelInternalNodes(m_nNrOfLabels - 1); } m_sLabels.remove(i); m_nNrOfLabels--; if (m_fLongitude != null && m_fLongitude.size() > i) { m_fLongitude.remove(i); m_fLatitude.remove(i); } int[] nOrder = new int[m_nOrder.length - 1]; int[] nRevOrder = new int[m_nRevOrder.length - 1]; int k = 0; for (int j = 0; j < nOrder.length; j++) { if (m_nOrder[k] == i) { k++; } nOrder[j] = (m_nOrder[k] < i ? m_nOrder[k] : m_nOrder[k] - 1); k++; } k = 0; for (int j = 0; j < nRevOrder.length; j++) { if (m_nRevOrder[k] == i) { k++; } nRevOrder[j] = (m_nRevOrder[k] < i ? m_nRevOrder[k] : m_nRevOrder[k] - 1); k++; } m_nOrder = nOrder; m_nRevOrder = nRevOrder; nDeleted++; } } System.err.println("ORDER:" + Arrays.toString(m_nOrder)); System.err.println("REVOR:" + Arrays.toString(m_nRevOrder)); m_bSelection = new boolean[m_bSelection.length - nDeleted]; for (int i = 0; i < m_bSelection.length; i++) { m_bSelection[i] = true; } fitToScreen(); calcPositions(); calcLines(); m_Panel.clearImage(); repaint(); } // actionPerformed };// class ActionDel void renumber(Node node, int iNodeNr) { if (node.isLeaf()) { if (node.getNr() > iNodeNr) { node.m_iLabel--; } } else { renumber(node.m_left, iNodeNr); renumber(node.m_right, iNodeNr); } } Node deleteLeaf(Node node, int iNodeNr) { if (node.isLeaf()) { if (node.getNr() == iNodeNr) { Node parent = node.getParent(); Node sibling = (parent.m_left == node ? parent.m_right : parent.m_left); if (parent.isRoot()) { // replace root by node's sibling sibling.m_Parent = null; return sibling; } // parent is not root. Link grandparent to sibling Node grandparent = parent.getParent(); if (grandparent.m_left == parent) { grandparent.m_left = sibling; } else { grandparent.m_right = sibling; } sibling.m_Parent = grandparent; sibling.m_fLength += parent.m_fLength; } } else { Node node2 = deleteLeaf(node.m_left, iNodeNr); if (node2.isRoot()) { return node2; } node2 = deleteLeaf(node.m_right, iNodeNr); if (node2.isRoot()) { return node2; } } return node; } // deleteLeaf /** index to current action on undo-stack **/ int m_iUndo = 0; Vector<DoAction> m_doActions = new Vector<DoAction>(); class DoAction { int[] m_nOrder2; int[] m_nRevOrder2; float[] m_fPosX; DoAction() { m_nOrder2 = m_nOrder.clone(); m_nRevOrder2 = m_nRevOrder.clone(); m_fPosX = new float[m_sLabels.size()]; getPosition(m_trees[0], m_fPosX); } void doThisAction() { m_nOrder = m_nOrder2.clone(); m_nRevOrder = m_nRevOrder2.clone(); for (int i = 0; i < m_trees.length; i++) { setPosition(m_trees[i], m_fPosX); positionRest(m_trees[i]); } for (int i = 0; i < m_cTrees.length; i++) { setPosition(m_cTrees[i], m_fPosX); positionRest(m_cTrees[i]); } calcLines(); makeDirty(); } // do } // class DoAction void addAction(DoAction action) { while (m_iUndo < m_doActions.size()) { m_doActions.remove(m_iUndo); } m_doActions.add(action); m_iUndo++; } // addAction Action a_undo = new MyAction("Undo", "Undo", "udno", KeyEvent.VK_Z) { private static final long serialVersionUID = -4L; @Override public void actionPerformed(ActionEvent ae) { if (m_iUndo > 0) { m_iUndo--; m_doActions.elementAt(m_iUndo - 1).doThisAction(); repaint(); } } // actionPerformed }; // class ActionUndo Action a_redo = new MyAction("Redo", "Redo", "reno", KeyEvent.VK_Y) { private static final long serialVersionUID = -4L; @Override public void actionPerformed(ActionEvent ae) { if (m_iUndo < m_doActions.size()) { m_iUndo++; m_doActions.elementAt(m_iUndo - 1).doThisAction(); repaint(); } } // actionPerformed }; // class ActionRedo Action a_moveup = new MyAction("Move labels up", "Move selected labels up", "moveup", KeyEvent.VK_M) { private static final long serialVersionUID = -4L; @Override public void actionPerformed(ActionEvent ae) { if (!moveSanityChek()) { return; } moveSelectedLabelsUp(); calcLines(); m_Panel.clearImage(); repaint(); } // actionPerformed }; // class ActionMoveUp Action a_movedown = new MyAction("Move labels down", "Move selected labels down", "movedown", KeyEvent.VK_M | KeyEvent.ALT_DOWN_MASK) { private static final long serialVersionUID = -4L; @Override public void actionPerformed(ActionEvent ae) { if (!moveSanityChek()) { return; } moveSelectedLabelsDown(); calcLines(); m_Panel.clearImage(); repaint(); } // actionPerformed }; // class ActionMoveDown Action a_browsefirst = new MyAction("Browse First", "Browse First", "browsefirst", -1) { private static final long serialVersionUID = 5L; @Override public void actionPerformed(ActionEvent ae) { m_viewMode = ViewMode.BROWSE; a_animateStart.setIcon("start"); m_iAnimateTree = 0; repaint(); } // actionPerformed }; // class ActionBrowseFirst Action a_browseprev = new MyAction("Browse Prev", "Browse Prev", "browseprev", KeyEvent.VK_P|KeyEvent.ALT_DOWN_MASK) { private static final long serialVersionUID = 5L; @Override public void actionPerformed(ActionEvent ae) { m_viewMode = ViewMode.BROWSE; a_animateStart.setIcon("start"); m_iAnimateTree--; if (m_iAnimateTree < 0) { m_iAnimateTree = 0; } repaint(); } // actionPerformed }; // class ActionBrowsePrev Action a_browsenext = new MyAction("Browse Next", "Browse Next", "browsenext", KeyEvent.VK_N|KeyEvent.ALT_DOWN_MASK) { private static final long serialVersionUID = 5L; @Override public void actionPerformed(ActionEvent ae) { m_viewMode = ViewMode.BROWSE; a_animateStart.setIcon("start"); m_iAnimateTree++; if (m_iAnimateTree == m_nTopologies) { m_iAnimateTree = m_nTopologies - 1; } repaint(); } // actionPerformed }; // class ActionBrowseNext Action a_browselast = new MyAction("Browse Last", "Browse Last", "browselast", -1) { private static final long serialVersionUID = 5L; @Override public void actionPerformed(ActionEvent ae) { m_viewMode = ViewMode.BROWSE; a_animateStart.setIcon("start"); m_iAnimateTree = m_nTopologies - 1; repaint(); } // actionPerformed }; // class ActionBrowse Action a_setfont = new MyAction("Set Font", "Set Font", "font", -1) { private static final long serialVersionUID = 5L; // @SuppressWarnings("deprecation") @Override public void actionPerformed(ActionEvent ae) { JFontChooser fontChooser = new JFontChooser(); // fontChooser.setFont(m_font); int result = fontChooser.showDialog(null); if (result == JFontChooser.OK_OPTION) { m_font = fontChooser.getSelectedFont(); repaint(); } } // actionPerformed }; // class SetFont SettingAction a_animationSpeedUp = new SettingAction("Animation Speed+", "Increase Animation Speed", "aspeedup", KeyEvent.VK_F); SettingAction a_animationSpeedDown = new SettingAction("Animation Speed-", "Decrease Animation Speed", "aspeeddown", KeyEvent.VK_F | KeyEvent.ALT_DOWN_MASK); SettingAction a_treeWidthUp = new SettingAction("Tree Width+", "Increase Width of Trees", "treewidthup", KeyEvent.VK_V); SettingAction a_treeWidthDown = new SettingAction("Tree Width-", "Decrease Width of Trees", "treewidthdown", KeyEvent.VK_V | KeyEvent.ALT_DOWN_MASK); SettingAction a_cTreeWidthUp = new SettingAction("Consensus Tree Width+", "Increase Width of Consensus Trees", "ctreewidthup", KeyEvent.VK_W); SettingAction a_cTreeWidthDown = new SettingAction("Consensus Tree Width-", "Decrease Width of Consensus Trees", "ctreewidthdown", KeyEvent.VK_W | KeyEvent.ALT_DOWN_MASK); SettingAction a_intensityUp = new SettingAction("Intensity+", "Increase Intensity of Trees", "intensityup", KeyEvent.VK_I); SettingAction a_intensityDown = new SettingAction("Intensity-", "Decrease Intensity of Trees", "intensitydown", KeyEvent.VK_I| KeyEvent.ALT_DOWN_MASK); SettingAction a_cIntensityUp = new SettingAction("Consensus Intensity+", "Increase Intensity of Consensus Trees", "cintensityup", KeyEvent.VK_C); SettingAction a_cIntensityDown = new SettingAction("Consensus Intensity-", "Decrease Intensity of Consensus Trees", "cintensitydown", KeyEvent.VK_C | KeyEvent.ALT_DOWN_MASK); SettingAction a_jitterUp = new SettingAction("Jitter+", "Increase Jitter on x-coordinate of Trees", "jitterup", KeyEvent.VK_J); SettingAction a_jitterDown = new SettingAction("Jitter-", "Decrease Jitter on x-coordinate of Trees", "jitterdown", KeyEvent.VK_J| KeyEvent.ALT_DOWN_MASK); SettingAction a_threadsUp = new SettingAction("Drawing Threads+", "Increase number of Drawing Threads", "threadsup", KeyEvent.VK_T); SettingAction a_threadsDown = new SettingAction("Drawing Threads-", "Decrease number of Drawing Threads", "threadsdown", KeyEvent.VK_T| KeyEvent.ALT_DOWN_MASK); SettingAction a_angleThresholdUp = new SettingAction("Angle Correction+", "Increase Threshold for angle correction", "angleup", KeyEvent.VK_N); SettingAction a_a_angleThresholdDown = new SettingAction("Angle Correction-", "Decrease Threshold for angle correction", "angledpown", KeyEvent.VK_N| KeyEvent.ALT_DOWN_MASK); public JCheckBoxMenuItem m_viewEditTree; public JCheckBoxMenuItem m_viewClades; void makeToolbar() { m_jTbTools.setFloatable(false); m_jTbTools.add(a_load); m_jTbTools.addSeparator(new Dimension(2, 2)); m_jTbTools.add(a_drawtreeset); m_jTbTools.addSeparator(new Dimension(2, 2)); m_jTbTools.add(a_browsefirst); m_jTbTools.add(a_browseprev); m_jTbTools.add(a_animateStart); m_jTbTools.add(a_browsenext); m_jTbTools.add(a_browselast); m_jTbTools.addSeparator(new Dimension(2, 2)); m_jTbTools.add(a_intensityUp); m_jTbTools.add(a_intensityDown); m_jTbTools.add(a_cIntensityUp); m_jTbTools.add(a_cIntensityDown); m_jTbTools.addSeparator(new Dimension(2, 2)); m_jTbTools.add(a_treeWidthUp); m_jTbTools.add(a_treeWidthDown); m_jTbTools.add(a_cTreeWidthUp); m_jTbTools.add(a_cTreeWidthDown); m_jTbTools.addSeparator(new Dimension(2, 2)); m_jTbTools.add(a_animationSpeedDown); m_jTbTools.add(a_animationSpeedUp); m_jTbTools.addSeparator(new Dimension(2, 2)); m_jTbTools.add(a_jitterUp); m_jTbTools.add(a_jitterDown); m_jTbTools.addSeparator(new Dimension(2, 2)); m_jTbTools.add(a_help); // Create an action with an icon Action action = new AbstractAction("Button Label", getIcon("modedefault")) { /** * */ private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { selectMode(0); } }; Action action3 = new AbstractAction("", getIcon("modestar")) { /** * */ private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { selectMode(1); } }; Action action4 = new AbstractAction("", getIcon("modecentralised")) { /** * */ private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { selectMode(2); } }; Action action5 = new AbstractAction("", getIcon("modeanglecorrected")) { /** * */ private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { selectMode(3); } }; if (viz.util.Util.isMac()) { B = 10; } JPanel panel = new JPanel(); panel.setBorder(new EmptyBorder(3, B, 5, B)); panel.setLayout(new GridLayout(0, 2)); panel.add(createToolBarButton(action)); panel.add(createToolBarButton(action3)); panel.add(createToolBarButton(action4)); panel.add(createToolBarButton(action5)); JPanel toolPanel = new JPanel(); toolPanel.setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridwidth = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.anchor = GridBagConstraints.PAGE_START; gbc.gridx = 0; gbc.gridy = 0; //m_jTbTools2.setLayout(new BoxLayout(m_jTbTools2, BoxLayout.Y_AXIS)); toolPanel.add(panel, gbc); Action action6 = new AbstractAction("", getIcon("stylestraight")) { /** * */ private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setStyle(0); } }; Action action7 = new AbstractAction("", getIcon("styleblock")) { /** * */ private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setStyle(1); } }; Action action8 = new AbstractAction("", getIcon("stylearced")) { /** * */ private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setStyle(2); } }; Action action9 = new AbstractAction("", getIcon("stylesteep")) { /** * */ private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { setStyle(3); } }; panel = new JPanel(); panel.setBorder(new EmptyBorder(3, B, 3, B)); panel.setLayout(new GridLayout(0, 2)); panel.add(createToolBarButton(action6)); panel.add(createToolBarButton(action7)); panel.add(createToolBarButton(action8)); panel.add(createToolBarButton(action9)); gbc.gridy++; toolPanel.add(panel, gbc); gbc.gridy++; toolPanel.add(new ExpandablePanel("Show", new ShowPanel(this)), gbc); gbc.gridy++; toolPanel.add(new ExpandablePanel("Grid", new GridPanel(this)), gbc); gbc.gridy++; toolPanel.add(new ExpandablePanel("Label", new LabelPanel(this)), gbc); gbc.gridy++; toolPanel.add(new ExpandablePanel("Geography", new GeoPanel(this)), gbc); gbc.gridy++; toolPanel.add(new ExpandablePanel("Line Width", new LineWidthPanel(this)), gbc); gbc.gridy++; toolPanel.add(new ExpandablePanel("Line Color", new ColorPanel(this)), gbc); gbc.gridy++; toolPanel.add(new ExpandablePanel("Burn in", new BurninPanel(this)), gbc); gbc.gridy++; toolPanel.add(new ExpandablePanel("Clades", new CladePanel(this)), gbc); m_jTbTools2.add(toolPanel); // for (int i = 0; i < 100; i++) { // gbc.gridy++; // m_jTbTools2.add(Box.createVerticalGlue(), gbc); // } m_cladelist = new JList<String>(m_cladelistmodel); m_cladelist.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (m_bAllowCladeSelection) { m_cladeSelection.clear(); for (int i : m_cladelist.getSelectedIndices()) { if (m_cladeWeight.get(i) > 0.01 && ((m_Xmode == 1 && m_clades.get(i).length > 1) || (m_Xmode == 2 && m_clades.get(i).length == 1))) { m_cladeSelection.add(i); } } resetCladeSelection(); System.err.println(m_cladelist.getSelectedValuesList()); System.err.println(m_cladelist.getSelectedValuesList().size() + " items selected"); repaint(); } } }); JScrollPane scrollingList = new JScrollPane(m_cladelist); //scrollingList.setPreferredSize(new Dimension(1200,600)); //scrollingList.setMinimumSize(scrollingList.getPreferredSize()); m_jTbCladeTools.setLayout(new BorderLayout()); m_jTbCladeTools.add(scrollingList, BorderLayout.CENTER); m_jTbCladeTools.setFloatable(false); m_jTbCladeTools.setVisible(false); } // makeToolbar private JButton createToolBarButton(Action action) { // Add a button to the toolbar; remove the label and margin before adding JButton c1 = new JButton(action); c1.setText(null); c1.setMargin(new Insets(0, 0, 0, 0)); return c1; } void setIcon(JCheckBoxMenuItem item, String sIcon) { java.net.URL tempURL = ClassLoader.getSystemResource(ICONPATH + sIcon + ".png"); if (tempURL != null) { item.setIcon(new ImageIcon(tempURL)); } else { item.setIcon(new ImageIcon(new BufferedImage(20, 20, BufferedImage.TYPE_4BYTE_ABGR))); } } // setIcon protected void makeMenuBar() { m_menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); fileMenu.setMnemonic('F'); // ---------------------------------------------------------------------- // File menu */ m_menuBar.add(fileMenu); fileMenu.add(a_new); fileMenu.add(a_load); fileMenu.add(a_saveas); fileMenu.add(a_loadimage); fileMenu.addSeparator(); fileMenu.add(a_print); // fileMenu.add(a_export); fileMenu.add(a_export); if (!viz.util.Util.isMac()) { fileMenu.addSeparator(); fileMenu.add(a_quit); } // ---------------------------------------------------------------------- // Edit menu */ JMenu editMenu = new JMenu("Edit"); editMenu.setMnemonic('E'); m_menuBar.add(editMenu); editMenu.add(a_undo); editMenu.add(a_redo); editMenu.add(a_selectAll); editMenu.add(a_unselectAll); editMenu.add(a_del); editMenu.add(a_paste); editMenu.add(a_moveup); editMenu.add(a_movedown); m_viewEditTree = new JCheckBoxMenuItem("Show Edit Tree", m_bViewEditTree); m_viewEditTree.setIcon(new ImageIcon(new BufferedImage(20, 20, BufferedImage.TYPE_4BYTE_ABGR))); m_viewEditTree.addActionListener(ae -> { boolean bPrev = m_bViewEditTree; m_bViewEditTree = m_viewEditTree.getState(); if (bPrev != m_bViewEditTree) { makeDirty(); } }); editMenu.add(m_viewEditTree); m_viewClades = new JCheckBoxMenuItem("Show Clades", m_bViewClades); m_viewClades.setIcon(new ImageIcon(new BufferedImage(20, 20, BufferedImage.TYPE_4BYTE_ABGR))); m_viewClades.addActionListener(ae-> { boolean bPrev = m_bViewClades; m_bViewClades = m_viewClades.getState(); if (bPrev != m_bViewClades) { makeDirty(); } }); m_viewClades.setEnabled(false); editMenu.add(m_viewClades); JMenu shuffleMenu = new JMenu("Shuffle"); shuffleMenu.setIcon(new ImageIcon(new BufferedImage(20, 20, BufferedImage.TYPE_4BYTE_ABGR))); shuffleMenu .add(new ShuffleAction("Most Frequent", "Use most frequent tree order", "", -1, NodeOrderer.DEFAULT)); shuffleMenu.add(new ShuffleAction("Closest Outside First", "Order closest to outside leaf first", "", KeyEvent.VK_S|InputEvent.ALT_DOWN_MASK, NodeOrderer.CLOSEST_OUTSIDE_FIRST)); shuffleMenu.add(new ShuffleAction("Optimised root canal tree", "Use root canal tree, then optimise", "", KeyEvent.VK_O|InputEvent.ALT_DOWN_MASK, NodeOrderer.OPTIMISE)); shuffleMenu.add(new ShuffleAction("Closest First", "Order closest leaf first", "", KeyEvent.VK_1|InputEvent.ALT_DOWN_MASK, NodeOrderer.CLOSEST_FIRST)); shuffleMenu.add(new ShuffleAction("Single link", "Single link hierarchical clusterer", "", KeyEvent.VK_2|InputEvent.ALT_DOWN_MASK, NodeOrderer.SINGLE)); shuffleMenu.add(new ShuffleAction("Complete link", "Complete link hierarchical clusterer", "", KeyEvent.VK_3|InputEvent.ALT_DOWN_MASK, NodeOrderer.COMPLETE)); shuffleMenu.add(new ShuffleAction("Average link", "Average link hierarchical clusterer", "", KeyEvent.VK_4|InputEvent.ALT_DOWN_MASK, NodeOrderer.AVERAGE)); shuffleMenu.add(new ShuffleAction("Mean link", "Mean link hierarchical clusterer", "", KeyEvent.VK_5|InputEvent.ALT_DOWN_MASK, NodeOrderer.MEAN)); shuffleMenu.add(new ShuffleAction("Adjusted complete link", "Adjusted complete link hierarchical clusterer", "", KeyEvent.VK_6|InputEvent.ALT_DOWN_MASK, NodeOrderer.ADJCOMLPETE)); // RRB: not for public release shuffleMenu.addSeparator(); shuffleMenu.add(new ShuffleAction("Manual", "Manual", "", -1, NodeOrderer.MANUAL)); shuffleMenu.add(new ShuffleAction("By Geography", "By Geography", "", -1, NodeOrderer.GEOINFO)); shuffleMenu.add(new ShuffleAction("By meta data, all", "By meta data, show all paths", "", KeyEvent.VK_7|InputEvent.ALT_DOWN_MASK, NodeOrderer.META_ALL)); shuffleMenu.add(new ShuffleAction("By meta data, sum", "By meta data, sum over paths", "", KeyEvent.VK_8|InputEvent.ALT_DOWN_MASK, NodeOrderer.META_SUM)); shuffleMenu.add(new ShuffleAction("By meta data, mean", "By meta data, average over paths", "", KeyEvent.VK_9|InputEvent.ALT_DOWN_MASK, NodeOrderer.META_AVERAGE)); editMenu.addSeparator(); editMenu.add(shuffleMenu); // ---------------------------------------------------------------------- // Draw all menu */ JMenu drawallMenu = new JMenu("Draw All"); drawallMenu.setMnemonic('D'); m_menuBar.add(drawallMenu); final JCheckBoxMenuItem autoRefresh = new JCheckBoxMenuItem("Automatically refresh", m_bAutoRefresh); autoRefresh.addActionListener(ae-> { m_bAutoRefresh = autoRefresh.getState(); if (m_bAutoRefresh && m_bIsDirty) { fitToScreen(); // makeDirty(); } }); drawallMenu.add(autoRefresh); drawallMenu.add(a_drawtreeset); // ---------------------------------------------------------------------- // Browse menu */ JMenu browseMenu = new JMenu("Browse"); browseMenu.setMnemonic('B'); m_menuBar.add(browseMenu); browseMenu.add(a_browsefirst); browseMenu.add(a_browseprev); browseMenu.add(a_animateStart); browseMenu.add(a_browsenext); browseMenu.add(a_browselast); browseMenu.addSeparator(); final JCheckBoxMenuItem animateOverWrite = new JCheckBoxMenuItem("Over write", m_bAnimateOverwrite); animateOverWrite.addActionListener(ae-> { m_bAnimateOverwrite = animateOverWrite.getState(); }); browseMenu.add(animateOverWrite); // ---------------------------------------------------------------------- // Settings menu */ JMenu settingsMenu = new JMenu("Settings"); settingsMenu.setMnemonic('S'); m_menuBar.add(settingsMenu); settingsMenu.addSeparator(); settingsMenu.add(a_intensityUp); settingsMenu.add(a_intensityDown); settingsMenu.add(a_cIntensityUp); settingsMenu.add(a_cIntensityDown); settingsMenu.addSeparator(); settingsMenu.add(a_treeWidthUp); settingsMenu.add(a_treeWidthDown); settingsMenu.add(a_cTreeWidthUp); settingsMenu.add(a_cTreeWidthDown); settingsMenu.addSeparator(); settingsMenu.add(a_animationSpeedDown); settingsMenu.add(a_animationSpeedUp); settingsMenu.addSeparator(); settingsMenu.add(a_jitterUp); settingsMenu.add(a_jitterDown); settingsMenu.addSeparator(); settingsMenu.add(a_threadsUp); settingsMenu.add(a_threadsDown); // ---------------------------------------------------------------------- // Window menu */ JMenu windowMenu = new JMenu("Window"); windowMenu.setMnemonic('W'); m_menuBar.add(windowMenu); windowMenu.add(a_viewstatusbar); windowMenu.add(a_viewtoolbar); windowMenu.add(a_viewtoolbar2); windowMenu.add(a_viewcladetoolbar); windowMenu.addSeparator(); windowMenu.add(a_zoomin); windowMenu.add(a_zoomout); windowMenu.add(a_zoomintree); windowMenu.add(a_zoomouttree); // ---------------------------------------------------------------------- // Help menu */ JMenu helpMenu = new JMenu("Help"); helpMenu.setMnemonic('H'); m_menuBar.add(helpMenu); helpMenu.add(a_help); helpMenu.add(a_viewClades); if (!Util.isMac()) { helpMenu.add(a_about); } } // makeMenuBar public static DensiTree startNew(String [] args) { final DensiTree a = new DensiTree(new String[]{}); if (viz.util.Util.isMac()) { try { // call viz.maconly.OSXAdapter.registerMacOSXApplication(a); // through reflection // Class<?> osx = Class.forName("viz.maconly.OSXAdapter"); // Method method = osx.getMethod("registerMacOSXApplication", DensiTree.class); // method.invoke(null, a); URL url = ClassLoader.getSystemResource("viz/icons/" + "DensiTree.png"); Icon icon = new ImageIcon(url); jam.framework.Application application = new jam.framework.Application(null, "DensiTree", "about" , icon) { @Override public void initialize() { } @Override protected JFrame getDefaultFrame() { return null; } @Override public void doQuit() { a.a_quit.actionPerformed(null); } @Override public void doAbout() { a.a_about.actionPerformed(null); } @Override public DocumentFrame doOpenFile(File file) { return null; } @Override public DocumentFrame doNew() { return null; } }; jam.mac.Utils.macOSXRegistration(application); } catch (Exception e) { // ignore } } JFrame f; f = new JFrame(FRAME_TITLE); a.frame = f; f.setVisible(true); a.parseArgs(args); JMenuBar menuBar = a.getMenuBar(); f.setJMenuBar(menuBar); f.add(a.m_jTbTools, BorderLayout.NORTH); f.add(a.m_jTbTools2, BorderLayout.EAST); JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, a, a.m_jTbCladeTools); splitPane.setDividerLocation(0.9); // JSplitPane splitPane2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, a.m_jTbTools2, splitPane); f.add(splitPane, BorderLayout.CENTER); f.add(a.m_jStatusBar, BorderLayout.SOUTH); f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); Dimension dim = a.getSize(); f.setSize(dim.width + 31, dim.height + 40 + 84); f.setLocation(DensiTree.instances * 10 , DensiTree.instances * 10); a.fitToScreen(); java.net.URL tempURL = ClassLoader.getSystemResource(DensiTree.ICONPATH + "DensiTree.png"); try { f.setIconImage(ImageIO.read(tempURL)); } catch (Exception e) { // ignore } a.m_Panel.setFocusable(true); return a; // a.fitToScreen(); } // startNew List<ChangeListener> m_changeListeners = new ArrayList<ChangeListener>(); public void addChangeListener(ChangeListener changeListener) { m_changeListeners.add(changeListener); } /** * Main method */ public static void main(String[] args) { viz.util.Util.loadUIManager(); startNew(args); } } // class DensiTree