package org.chesmapper.view.gui.table; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Graphics; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import org.chesmapper.map.data.DistanceUtil; import org.chesmapper.map.dataInterface.CompoundData; import org.chesmapper.map.dataInterface.CompoundProperty; import org.chesmapper.map.dataInterface.NominalProperty; import org.chesmapper.map.main.ScreenSetup; import org.chesmapper.map.main.Settings; import org.chesmapper.view.cluster.Cluster; import org.chesmapper.view.cluster.ClusterController; import org.chesmapper.view.cluster.Clustering; import org.chesmapper.view.cluster.Compound; import org.chesmapper.view.cluster.CompoundFilterImpl; import org.chesmapper.view.cluster.Clustering.SelectionListener; import org.chesmapper.view.gui.ComponentSize; import org.chesmapper.view.gui.GUIControler; import org.chesmapper.view.gui.ViewControler; import org.mg.javalib.dist.NonNullSimilartiy; import org.mg.javalib.dist.SimilarityMeasure; import org.mg.javalib.dist.SimpleMatchingSimilartiy; import org.mg.javalib.dist.TanimotoSimilartiy; import org.mg.javalib.gui.BlockableFrame; import org.mg.javalib.util.ArrayUtil; import org.mg.javalib.util.CountedSet; import org.mg.javalib.util.DoubleArraySummary; import org.mg.javalib.util.ListUtil; import org.mg.javalib.util.ObjectUtil; import org.mg.javalib.util.StringUtil; import org.mg.javalib.util.SwingUtil; public class TreeView extends BlockableFrame { private static int ICON_W = 50; private static int ICON_H = 50; protected ViewControler viewControler; protected Clustering clustering; protected ClusterController clusterControler; protected GUIControler guiControler; protected boolean selfUpdate; HashMap<Compound, MyNode> map = new HashMap<Compound, TreeView.MyNode>(); HashMap<Cluster, MyNode> mapCluster = new HashMap<Cluster, TreeView.MyNode>(); JTree tree; boolean tanimotoWorking; public static enum Crit { size, uniform, activity, nullRatio; } // static HashMap<Cluster, Double> weightedTox = new HashMap<>(); public class MyNode extends DefaultMutableTreeNode { Cluster cluster; List<Compound> leafs; List<MyNode> clusterNodes; private CompoundProperty compoundProperty; DoubleArraySummary avgUniformity; DoubleArraySummary avgActivity; DoubleArraySummary avgNullRatio; Double uniformity; Double activity; Double nullRatio; int numAllNull = -1; String feature; public MyNode(Object o, String feature) { super(o); if (o instanceof Compound) map.put((Compound) o, this); this.feature = feature; } public MyNode(String s, String feature, CompoundProperty p) { this(s, feature); this.compoundProperty = p; } public Cluster getCluster() { if (leafs == null) getLeafs(); return cluster; } @SuppressWarnings("unchecked") public List<Compound> getLeafs() { if (leafs == null) { leafs = new ArrayList<Compound>(); for (int i = 0; i < getChildCount(); i++) if (getChildAt(i).isLeaf()) leafs.add((Compound) ((MyNode) getChildAt(i)).getUserObject()); else leafs = ListUtil.concat(leafs, ((MyNode) getChildAt(i)).getLeafs()); cluster = clustering.getUniqueClusterForCompounds(ArrayUtil.toArray(leafs)); if (cluster != null && cluster.getNumCompounds() != leafs.size()) cluster = null; if (cluster != null) mapCluster.put(cluster, this); } return leafs; } TanimotoSimilartiy tanimotoSim = new TanimotoSimilartiy(); SimpleMatchingSimilartiy simpleMatchingSim = new SimpleMatchingSimilartiy(); NonNullSimilartiy nonNullSim = new NonNullSimilartiy(); public Double getStructuralSimilarity() { if (tanimotoWorking) return getSimilarity(clustering.getFeatures(), tanimotoSim); List<Double> distances = new ArrayList<Double>(); List<Compound> compounds = getLeafs(); for (int i = 0; i < compounds.size() - 1; i++) for (int j = i + 1; j < compounds.size(); j++) distances.add(clustering.getFeatureDistance(compounds.get(i).getOrigIndex(), compounds.get(j) .getOrigIndex())); return DoubleArraySummary.create(distances).getMean(); } public Double getToxSimilarity() { return getSimilarity(activityProps, simpleMatchingSim); } public Double getWeightedTox() { // if (!weightedTox.containsKey(cluster)) // { // List<Double> toxWeight = new ArrayList<Double>(); // for (Cluster c : clustering.getClusters()) // toxWeight.add(mapCluster.get(c).getToxNonMissing()); // if (toxWeight.contains(null)) // throw new IllegalStateException(); // Double normalizedToxWeight[] = ArrayUtil.normalize(ArrayUtil.toArray(toxWeight), false); // // int i = 0; // for (Cluster c : clustering.getClusters()) // if (mapCluster.get(c).getToxSimilarity() == null) // weightedTox.put(c, null); // else // weightedTox.put(c, normalizedToxWeight[i++] * mapCluster.get(c).getToxSimilarity()); // } // return weightedTox.get(cluster); if (getToxSimilarity() == null) return null; else return getToxSimilarity() * getToxNonMissing(); } public Double getToxNonMissing() { return getSimilarity(activityProps, nonNullSim); } private Double getSimilarity(List<CompoundProperty> props, SimilarityMeasure<Boolean> sim) { List<CompoundData> cd = new ArrayList<CompoundData>(); for (Compound c : getLeafs()) cd.add(c.getCompoundData()); return DistanceUtil.similarity(cd, props, sim); } public double getUniformity() { if (uniformity == null) { double sumU = 0; double sumA = 0; int count = 0; int sumInclNull = 0; int countNull = 0; numAllNull = 0; for (CompoundProperty p : activityProps) { //if (p.toString().endsWith("_filled")) CountedSet<String> set = new CountedSet<String>(); for (Compound comp : getLeafs()) set.add(comp.getStringValue((NominalProperty) p)); if (set.getNumValues() != 3 && set.getNumValues() != 2 && set.getNumValues() != 1) throw new Error(set.toString()); countNull += set.getNullCount(); sumInclNull += set.getSum(true); set.remove(null); if (set.getNumValues() > 0) { // double variance; // if (set.size() == 1) // variance = 0; // else // { // Variance v = new Variance(); // variance = v.evaluate(new double[] { set.getCount(set.values().get(0)), // set.getCount(set.values().get(1)) }); // } double uniformity = set.getMaxCount(true) / (double) set.sum(); sumU += uniformity; if (set.values().get(0).equals("1")) sumA += uniformity; else if (set.values().get(0).equals("0")) sumA += 1 - uniformity; else throw new Error(); count++; } else numAllNull++; } nullRatio = countNull / (double) sumInclNull; uniformity = sumU / (double) count; activity = sumA / (double) count; } return uniformity; } public List<MyNode> getClusterNodes() { return getClusterNodes(null); } @SuppressWarnings("unchecked") public List<MyNode> getClusterNodes(final Integer size) { if (clusterNodes == null) { clusterNodes = new ArrayList<MyNode>(); for (int i = 0; i < getChildCount(); i++) if (getChildAt(i).isLeaf()) { //do nothing } else if (((MyNode) getChildAt(i)).getCluster() != null) clusterNodes.add(((MyNode) getChildAt(i))); else clusterNodes = ListUtil.concat(clusterNodes, ((MyNode) getChildAt(i)).getClusterNodes()); } if (size == null) return clusterNodes; else { return ListUtil.filter(clusterNodes, new ListUtil.Filter<MyNode>() { @Override public boolean accept(MyNode n) { return n.getCluster().getNumCompounds() == size; } }); } } public DoubleArraySummary getAvg(Crit crit) { return getAvg(crit, null); } public DoubleArraySummary getAvg(Crit crit, Integer clusterSize) { List<MyNode> nodes = getClusterNodes(clusterSize); if (nodes.size() == 0) return null; else { double d[] = new double[nodes.size()]; for (int i = 0; i < d.length; i++) { MyNode n = nodes.get(i); switch (crit) { case size: d[i] = n.getCluster().getNumCompounds(); break; case uniform: d[i] = n.getUniformity(); case activity: d[i] = n.getActivity(); case nullRatio: d[i] = n.getNullRatio(); default: break; } } return DoubleArraySummary.create(d); } } public double getActivity() { if (activity == null) getUniformity(); return activity; } public double getNullRatio() { if (nullRatio == null) getUniformity(); return nullRatio; } public int getNumAllNull() { if (numAllNull == -1) getUniformity(); return numAllNull; } public int getNumOffspring() { return getLeafs().size(); } public CompoundProperty getCompoundProperty() { return compoundProperty; } public String getName() { return super.toString(); } List<String> features; public List<String> getFeatures() { if (features != null) return features; List<String> f; if (isRoot()) f = new ArrayList<String>(); else if (isLeaf()) f = ((MyNode) getParent()).getFeatures(); else { f = ((MyNode) getParent()).getFeatures(); if (feature == null) throw new IllegalStateException(); f.add(feature); } if (getCluster() != null) features = f; return f; } @Override public String toString() { if (isLeaf()) return super.toString(); else { // if (this == getRoot()) // { // for (Integer cSize : new Integer[] { null, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }) // { // System.out.print(cSize + ","); // System.out.print(getAvg(Crit.size, cSize) + ","); // System.out.print(getClusterNodes(cSize).size() + ","); // System.out.print(getAvg(Crit.activity, cSize) + ","); // System.out.print(getAvg(Crit.uniform, cSize) + ","); // System.out.print(getAvg(Crit.nullRatio, cSize)); // System.out.println(); // } // System.out.println(); // // } String simStruct = ""; if (getCluster() != null) { Double d = getStructuralSimilarity(); if (tanimotoWorking) simStruct = " simStruct:" + StringUtil.formatDouble(d); else simStruct = " distStruct:" + StringUtil.formatDouble(d); } return //"<html>" //+ super.toString() + " " + (cluster != null ? (cluster.getName() + " ") : "") //+ (cluster != null ? ("<b>" + cluster.getName() + "</b> ") : "") + "(#" + getNumOffspring() + ") active:" + StringUtil.formatDouble(getActivity()) //+ " uniform:"+ StringUtil.formatDouble(getUniformity()) + " missing:" + StringUtil.formatDouble(getNullRatio()) // + " #all-missing:" // + getNumAllNull() + simStruct + (getCluster() != null ? (" simTox:" + StringUtil.formatDouble(getToxSimilarity())) : "") + (getCluster() != null ? (" wTox:" + StringUtil.formatDouble(getWeightedTox())) : ""); // + (getClusterNodes().size() > 0 ? (" num:" + getClusterNodes().size() + " avg-act:" // + getAvg(Crit.activity) + " avg-unif:" + getAvg(Crit.uniform) + " avg-null:" // + getAvg(Crit.nullRatio) + " avg-size:" + getAvg(Crit.size)) : "") + "</html>"; } } } private List<CompoundProperty> activityProps; public TreeView(ViewControler viewControler, ClusterController clusterControler, Clustering clustering, GUIControler guiControler) { super(true); this.viewControler = viewControler; this.clusterControler = clusterControler; this.clustering = clustering; this.guiControler = guiControler; tanimotoWorking = DistanceUtil.isBoolean(clustering.getFeatures()); HashSet<String> propNames = new HashSet<String>(); if (propNames.size() == 0) for (CompoundProperty p : clustering.getPropertiesAndFeatures()) propNames.add(p.toString()); activityProps = new ArrayList<CompoundProperty>(); for (CompoundProperty p : clustering.getPropertiesAndFeatures()) if (propNames.contains(p.toString() + "_real")) activityProps.add(p); buildTree(); addListener(); setLayout(new BorderLayout()); add(new JScrollPane(tree)); setTitle("CheS-Mapper Tree-View"); pack(); setSize((int) Math.min(getSize().getWidth() + 20, Settings.TOP_LEVEL_FRAME.getWidth()), Settings.TOP_LEVEL_FRAME.getHeight() - 50); setLocationRelativeTo(Settings.TOP_LEVEL_FRAME); setVisible(true); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { tree.requestFocus(); } }); } private void addListener() { clustering.addSelectionListener(new SelectionListener() { @Override public void compoundActiveChanged(Compound[] c) { if (!isVisible()) return; updateSelection(ArrayUtil.first(c), null); } @Override public void clusterActiveChanged(Cluster c) { if (!isVisible()) return; updateSelection(null, c); } }); tree.addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { if (selfUpdate) return; selfUpdate = true; TreeView.this.block("handle selection change"); Thread th = new Thread(new Runnable() { @Override public void run() { if (tree.getSelectionPath() == null) { clusterControler.clearClusterWatched(); clusterControler.clearCompoundActive(true); } else { MyNode node = (MyNode) tree.getSelectionPath().getLastPathComponent(); if (node.isRoot()) clusterControler.setCompoundFilter(null, true); else if (node.isLeaf()) { Compound c = (Compound) node.getUserObject(); if (clusterControler.getCompoundFilter() != null && !clusterControler.getCompoundFilter().accept(c)) clusterControler.setCompoundFilter(null, true); clusterControler.setCompoundActive(c, true); } else if (node.getCluster() != null) { if (node.getCluster().getNumCompounds() == 0) clusterControler.setCompoundFilter(null, true); clusterControler.setClusterActive(node.getCluster(), true, true); } else clusterControler.setCompoundFilter(new CompoundFilterImpl(clustering, node.getLeafs(), node.getName()), true); } SwingUtil.invokeAndWait(new Runnable() { @Override public void run() { TreeView.this.unblock("handle selection change"); selfUpdate = false; } }); } }); th.start(); } }); } private void updateSelection(Compound c, Cluster clust) { if (selfUpdate) return; selfUpdate = true; tree.setExpandsSelectedPaths(true); if (c != null) { TreePath p = new TreePath(map.get(c).getPath()); tree.setSelectionPath(p); tree.scrollPathToVisible(p); } else if (clust != null) { TreePath p = new TreePath(mapCluster.get(clust).getPath()); tree.setSelectionPath(p); tree.scrollPathToVisible(p); } else { tree.clearSelection(); } selfUpdate = false; } public static Icon NO_STRUCTURE = new Icon() { @Override public int getIconHeight() { return ICON_H; } @Override public int getIconWidth() { return ICON_W; } @Override public void paintIcon(Component c, Graphics g, int x, int y) { g.drawString("?", ICON_W / 2 - 5, ICON_H / 2 + 5); } }; private void buildTree() { MyNode root = new MyNode("Root", null); List<CompoundProperty> levels = new ArrayList<CompoundProperty>(); for (CompoundProperty p : clustering.getPropertiesAndFeatures()) if (p.getName().matches("(?i).*level.*")) levels.add(p); addNodes(root, levels, clustering.getCompounds(false)); root.getNumOffspring(); DefaultTreeModel m = new DefaultTreeModel(root); tree = new JTree(m); tree.setCellRenderer(new DefaultTreeCellRenderer() { @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean isLeaf, int row, boolean focused) { JLabel c = (JLabel) super.getTreeCellRendererComponent(tree, value, selected, expanded, isLeaf, row, focused); if (isLeaf && ((MyNode) value).getUserObject() instanceof Compound) { Compound comp = ((Compound) ((MyNode) value).getUserObject()); c.setText("<html>" + comp.getDisplayName().toString(true, comp.getHighlightColorText()) + "</html>"); int size = guiControler.getComponentMaxWidth(ComponentSize.ICON_2D.getValue() * 0.01); Icon icon = comp.getIcon(false, size, size, false); if (icon == null) icon = NO_STRUCTURE; setIcon(icon); } ((JComponent) c).setBorder(new EmptyBorder(3, 3, 3, 3)); setFont(getFont().deriveFont((float) ScreenSetup.INSTANCE.getFontSize())); return c; } }); for (MyNode n : map.values()) tree.expandPath(new TreePath(((MyNode) ((MyNode) n.getParent()).getParent()).getPath())); // FileUtil.CSVFile csv = new FileUtil.CSVFile(); // csv.content = new ArrayList<String[]>(); // String header[] = new String[] { "index", "num-compounds", "compounds", "num-features", "features", "active", // "missing", (tanimotoWorking ? "struct-similarity" : "struct-distance"), "tox-similarity", // "weighted-tox-similarity" }; // String props[] = new String[activityProps.size()]; // for (int i = 0; i < props.length; i++) // props[i] = activityProps.get(i).toString() + "-missing"; // header = ArrayUtil.concat(header, props); // // csv.content.add(header); // int i = 0; // for (Cluster c : clustering.getClusters()) // { // String cmpIdx = ""; // for (Compound comp : c.getCompounds()) // cmpIdx += comp.getOrigIndex() + ";"; // cmpIdx = cmpIdx.substring(0, cmpIdx.length() - 1); // MyNode n = mapCluster.get(c); // int idx = 0; // Object vals[] = new Object[header.length]; // for (Object object : new Object[] { (i + 1), c.size(), cmpIdx, n.getFeatures().size(), // ArrayUtil.toString(ArrayUtil.toArray(n.getFeatures()), ";", "", "", ""), n.getActivity(), // n.getNullRatio(), n.getStructuralSimilarity(), n.getToxSimilarity(), n.getWeightedTox() }) // vals[idx++] = object; // // for (CompoundProperty p : activityProps) // { // vals[idx++] = c.numMissingValues(p) / (double) c.size(); // } // csv.content.add(ArrayUtil.toStringArray(vals)); // i++; // } // FileUtil.writeCSV("/tmp/cluster.csv", csv, false); // // String json = JsonTreeNode.create(root).toJson(); // FileUtil.writeStringToFile("/home/martin/documents/tree/tree.json", json); // // for (int i = 0; i < tree.getRowCount(); i++) // // if (!((MyNode) tree.getPathForRow(i).getLastPathComponent()).isLeaf()) // // tree.expandRow(i); } private void addNodes(MyNode node, List<CompoundProperty> p, List<Compound> c) { if (p.size() == 0) { for (Compound compound : c) node.add(new MyNode(compound, null)); } else { List<CompoundProperty> pp = new ArrayList<CompoundProperty>(p); CompoundProperty prop = pp.remove(0); List<String> vals = new ArrayList<String>(); for (Compound cc : c) vals.add(cc.getStringValue((NominalProperty) prop)); CountedSet<String> set = CountedSet.create(vals); for (final String s : set.values()) { String featureWithoutIndex = s.substring(s.indexOf(" ") + 1).trim(); if (featureWithoutIndex.endsWith(" <= 0")) featureWithoutIndex = featureWithoutIndex.substring(0, featureWithoutIndex.length() - 5) + " no-match"; else if (featureWithoutIndex.endsWith(" > 0")) featureWithoutIndex = featureWithoutIndex.substring(0, featureWithoutIndex.length() - 4) + " match"; MyNode child = new MyNode(prop + "=" + featureWithoutIndex, featureWithoutIndex); List<Compound> ccc = new ArrayList<Compound>(); for (Compound cc : c) if (ObjectUtil.equals(cc.getStringValue((NominalProperty) prop), s)) ccc.add(cc); if (set.getNumValues() == 1) addNodes(node, pp, ccc); else { node.add(child); addNodes(child, pp, ccc); } } } } }