package beast.app.beauti; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.io.File; import java.util.HashMap; import java.util.Map; import javax.swing.Box; import javax.swing.JCheckBox; import javax.swing.JDialog; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JViewport; import javax.swing.ListSelectionModel; import javax.swing.ScrollPaneConstants; import javax.swing.UIManager; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; import javax.swing.table.TableModel; import beast.evolution.alignment.Alignment; import beast.evolution.datatype.DataType; import beast.util.NexusParser; public class AlignmentViewer extends JPanel { private static final long serialVersionUID = 1L; Object[][] tableData; Object[] columnData; boolean useColor = false; // flag to indicate that the most frequently occurring character is shown as a dot boolean useDots = true; Alignment m_alignment; Map<Character, Color> m_customColorMap = new HashMap<>(); /** * define which character maps to which color * */ public void setCustomColorMap(Map<Character, Color> colorMap) { for (char c : m_customColorMap.keySet()) { m_customColorMap.put(c, colorMap.get(c)); } } /** * constructor processes alignment and sets up table with first column fixed * */ public AlignmentViewer(Alignment data) { m_alignment = data; int siteCount = data.getSiteCount(); int taxonCount = data.getTaxonCount(); tableData = new Object[taxonCount][siteCount + 1]; char[] headerChar = updateTableData(); // set up row labels for (int i = 0; i < taxonCount; i++) { tableData[i][0] = data.getTaxaNames().get(i); } // set up column labels columnData = new Object[siteCount + 1]; for (int i = 1; i < siteCount + 1; i++) { columnData[i] = "<html>.<br>" + headerChar[i - 1] + "</html>"; } columnData[0] = "<html><br>taxon name</html>"; columnData[1] = "<html>1<br>" + headerChar[0] + "</html>"; for (int i = 10; i < siteCount; i += 10) { String s = i + ""; for (int j = 0; j < s.length(); j++) { if (i+j < columnData.length) { columnData[i + j] = "<html>" + s.charAt(j) + "<br>" + headerChar[i - 1] + "</html>"; } } columnData[i - 5] = "<html>+<br>" + headerChar[i - 1] + "</html>"; } // create table in scrollpane with first column fixed final TableModel fixedColumnModel = new AbstractTableModel() { private static final long serialVersionUID = 1L; @Override public int getColumnCount() { return 1; } @Override public String getColumnName(int column) { return columnData[column] + ""; } @Override public int getRowCount() { return tableData.length; } @Override public Object getValueAt(int row, int column) { return tableData[row][column]; } }; final TableModel mainModel = new AbstractTableModel() { private static final long serialVersionUID = 1L; @Override public int getColumnCount() { return columnData.length - 1; } @Override public String getColumnName(int column) { return columnData[column + 1] + ""; } @Override public int getRowCount() { return tableData.length; } @Override public Object getValueAt(int row, int column) { return tableData[row][column + 1]; } }; JTable fixedTable = new JTable(fixedColumnModel); fixedTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); Font font = fixedTable.getFont(); font = new Font(font.getFontName(), font.getStyle(), font.getSize() * 2/3); fixedTable.setFont(font); TableColumn col = fixedTable.getColumnModel().getColumn(0); col.setPreferredWidth(200); fixedTable.getTableHeader().setFont(font); JTable mainTable = new JTable(mainModel); mainTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); mainTable.setFont(font); mainTable.getTableHeader().setFont(font); for (int i = 0; i < siteCount; i++) { col = mainTable.getColumnModel().getColumn(i); col.setPreferredWidth(6); } ListSelectionModel model = fixedTable.getSelectionModel(); mainTable.setSelectionModel(model); mainTable.setShowGrid(false); JScrollPane scrollPane = new JScrollPane(mainTable); Dimension fixedSize = fixedTable.getPreferredSize(); JViewport viewport = new JViewport(); viewport.setView(fixedTable); viewport.setPreferredSize(fixedSize); viewport.setMaximumSize(fixedSize); scrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER, fixedTable.getTableHeader()); scrollPane.setRowHeaderView(viewport); setLayout(new BorderLayout()); add(scrollPane, BorderLayout.CENTER); } private char[] updateTableData() { int siteCount = m_alignment.getSiteCount(); int taxonCount = m_alignment.getTaxonCount(); // set up table content DataType dataType = m_alignment.getDataType(); char[] headerChar = new char[siteCount]; Object[][] colorMap = setupColorMap(); Object[][] integerColorMap = setupIntegerColorMap(); try { for (int i = 0; i < siteCount; i++) { int patternIndex_ = m_alignment.getPatternIndex(i); int[] pattern = m_alignment.getPattern(patternIndex_); String patternString = dataType.state2string(pattern); if (patternString.contains(",")) { // We have a string of comma separated values. String[] patternAtTaxon = patternString.split(","); headerChar[i] = mostFrequentCharInPattern(patternAtTaxon); for (int j = 0; j < taxonCount; j++) { try { int code = Integer.valueOf(patternAtTaxon[j]); if (code + '0' == headerChar[i]) { // Prevent Exception when accessing integerColorMap code = Math.min(Math.max(code, 0), integerColorMap[0].length - 1); tableData[j][i + 1] = integerColorMap[0][code]; } else { code = Math.min(Math.max(code, 0), integerColorMap[0].length - 1); tableData[j][i + 1] = integerColorMap[1][code]; } } catch (NumberFormatException e) { // State cannot be interpreted as a number. // Assume it is a special (ambiguous) character. tableData[j][i + 1] = useColor ? ' ' : patternAtTaxon[j]; } } } else { headerChar[i] = mostFrequentCharInPattern(patternString); for (int j = 0; j < taxonCount; j++) { char c = patternString.charAt(j); if (c == headerChar[i]) { tableData[j][i + 1] = colorMap[0][c]; } else { tableData[j][i + 1] = colorMap[1][c]; } } } } } catch (Exception e) { // ignore } return headerChar; } /** * determine content of table cells. * Without color, only Characters are displayed, which can be a bit faster than using color * With color, the color is encoded in HTML * * @return an array of 2x256 where the first entry is for the most frequently occurring character, * and the second for the others * * */ private Object[][] setupColorMap() { if (useColor) { String[][] colorMap = new String[2][256]; for (int k = 'A'; k < 'Z'; k++) { int i = k - 'A'; int red = ((i & 0x80) >> 7) + ((i & 0x10) >> 4) + ((i & 0x2) << 1); int green = ((i & 0x40) >> 6) + ((i & 0x08) >> 2) + ((i & 0x4)); int blue = ((i & 0x20) >> 5) + ((i & 0x04) >> 1) + ((i & 0x1) << 2); int color = (red << 21 + (green << 18)) + (green << 13) + (blue << 10) + (blue << 5) + (red << 2); colorMap[0][k] = "<html><font color='#" + Integer.toString(color, 16) + "'><b>.</b></html>"; colorMap[1][k] = "<html><font color='#" + Integer.toString(color, 16) + "'><b>" + ((char) k) + "</font></html>"; } for (char c : m_customColorMap.keySet()) { Color color = m_customColorMap.get(c); colorMap[0][c] = "<html><font color='#" + Integer.toString(color.getRGB(), 16) + "'><b>.</b></html>"; colorMap[1][c] = "<html><font color='#" + Integer.toString(color.getRGB(), 16) + "'><b>" + c + "</font></html>"; } if (!this.useDots) { colorMap[0] = colorMap[1]; } return colorMap; } else { Character[][] colorMap = new Character[2][256]; for (int i = 0; i < 256; i++) { colorMap[0][i] = '.'; colorMap[1][i] = (char) i; } if (!this.useDots) { colorMap[0] = colorMap[1]; } return colorMap; } } private Object[][] setupIntegerColorMap() { if (useColor) { String[][] colorMap = new String[2][256]; for (int i = 0; i < 256; i++) { int red = ((i & 0x80) >> 7) + ((i & 0x10) >> 4) + ((i & 0x2) << 1); int green = ((i & 0x40) >> 6) + ((i & 0x08) >> 2) + ((i & 0x4)); int blue = ((i & 0x20) >> 5) + ((i & 0x04) >> 1) + ((i & 0x1) << 2); int color = (red << 21 + (green << 18)) + (green << 13) + (blue << 10) + (blue << 5) + (red << 2); colorMap[0][i] = "<html><font color='#" + Integer.toString(color, 16) + "'><b>.</b></html>"; colorMap[1][i] = "<html><font color='#" + Integer.toString(color, 16) + "'><b>" + i + "</font></html>"; } for (char c : m_customColorMap.keySet()) { Color color = m_customColorMap.get(c); colorMap[0][c] = "<html><font color='#" + Integer.toString(color.getRGB(), 16) + "'><b>.</b></html>"; colorMap[1][c] = "<html><font color='#" + Integer.toString(color.getRGB(), 16) + "'><b>" + (int) c + "</font></html>"; } if (!this.useDots) { colorMap[0] = colorMap[1]; } return colorMap; } else { String[][] colorMap = new String[2][256]; for (int i = 0; i < 256; i++) { colorMap[0][i] = "."; colorMap[1][i] = Integer.toString(i); } if (!this.useDots) { colorMap[0] = colorMap[1]; } return colorMap; } } private char mostFrequentCharInPattern(String pattern) { char[] counts = new char[256]; for (int i = 0; i < pattern.length(); i++) { counts[pattern.charAt(i)]++; } int maxIndex = 0, max = 0; for (int i = 0; i < counts.length; i++) { if (counts[i] > max) { maxIndex = i; max = counts[i]; } } return (char) maxIndex; } private char mostFrequentCharInPattern(String[] pattern) { Map<Character, Integer> stateToCount = new HashMap<>(); int maxCount = 0; char mostFrequentChar = 0; for (String str : pattern) { try { char c = (char) (Integer.parseInt(str) + '0'); if (stateToCount.containsKey(c)) { stateToCount.put(c, stateToCount.get(c) + 1); } else { stateToCount.put(c, 1); } if (stateToCount.get(c) > maxCount) { maxCount = stateToCount.get(c); mostFrequentChar = c; } } catch (NumberFormatException e) { // ignore the ambiguous characters } } return mostFrequentChar; } public void showInDialog() { JDialog dlg = new JDialog(); dlg.setName("AlignmentViewer"); dlg.add(this); Box buttonBox = Box.createHorizontalBox(); JCheckBox useDotsCheckBox = new JCheckBox("Use dots", true); useDotsCheckBox.addActionListener(e -> { JCheckBox _useDots = (JCheckBox) e.getSource(); useDots = _useDots.isSelected(); updateTableData(); repaint(); }); buttonBox.add(useDotsCheckBox); JCheckBox useColorCheckBox = new JCheckBox("Use Color"); useColorCheckBox.setName("UseColor"); useColorCheckBox.addActionListener(e -> { JCheckBox hasColor = (JCheckBox) e.getSource(); useColor = hasColor.isSelected(); updateTableData(); repaint(); }); buttonBox.add(useColorCheckBox); dlg.add(buttonBox, BorderLayout.SOUTH); int size = UIManager.getFont("Label.font").getSize(); dlg.setSize(1024 * size / 13, 600 * size / 13); dlg.setModal(true); dlg.setVisible(true); dlg.dispose(); } public static void main(String[] args) { try { NexusParser parser = new NexusParser(); parser.parseFile(new File(args[0])); Alignment data = parser.m_alignment; AlignmentViewer panel = new AlignmentViewer(data); panel.showInDialog(); } catch (Exception e) { e.printStackTrace(); } } }