net.maizegenetics.pangenome.gui.CreateStreamGraph Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of phg Show documentation
Show all versions of phg Show documentation
PHG - Practical Haplotype Graph
package net.maizegenetics.pangenome.gui;
import com.google.common.collect.Multimap;
import net.maizegenetics.dna.map.Chromosome;
import net.maizegenetics.pangenome.api.HaplotypeEdge;
import net.maizegenetics.pangenome.api.HaplotypeGraph;
import net.maizegenetics.pangenome.api.HaplotypeNode;
import net.maizegenetics.pangenome.api.ReferenceRange;
import net.maizegenetics.taxa.TaxaList;
import net.maizegenetics.taxa.Taxon;
import org.apache.log4j.Logger;
import org.graphstream.graph.Edge;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.graph.implementations.SingleGraph;
import org.graphstream.ui.spriteManager.Sprite;
import org.graphstream.ui.spriteManager.SpriteManager;
import org.graphstream.ui.swingViewer.ViewPanel;
import org.graphstream.ui.view.Viewer;
import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.text.DecimalFormat;
import java.util.List;
import java.util.*;
/**
* @author Terry Casstevens Created August 25, 2017
*/
public class CreateStreamGraph {
private static final Logger myLogger = Logger.getLogger(CreateStreamGraph.class);
private static final int DEFAULT_NUM_RANGES = 10;
private static final int GU_HEIGHT = 800;
private static final int GU_WIDTH = 1000;
private static DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#0.000");
private final JFrame myFrame;
private int myCurrentStartRange = 0;
private int myNumRangesToDisplay = DEFAULT_NUM_RANGES;
private int myStepSize = myNumRangesToDisplay / 2;
private double myGuWidthStep = (double) GU_WIDTH / (double) (myNumRangesToDisplay - 1);
private boolean myTaxaLabels = false;
private boolean myShowWeights = false;
private boolean myShowIds = false;
private final HaplotypeGraph myHaplotypes;
private final List myRanges;
private final int myTotalNumRanges;
private final Graph myGraph;
private SpriteManager mySpriteManager;
private final Multimap myPaths;
private Taxon myCurrentTaxon = null;
private Collection myCurrentPath = null;
private CreateStreamGraph(HaplotypeGraph haplotypes, Multimap paths) {
myFrame = new JFrame();
myHaplotypes = haplotypes;
myPaths = paths;
myRanges = myHaplotypes.referenceRangeList();
myTotalNumRanges = myRanges.size();
myGraph = new SingleGraph("PHG");
mySpriteManager = new SpriteManager(myGraph);
updateGraph();
browseGraph(myGraph);
}
public static void view(HaplotypeGraph graph) {
new CreateStreamGraph(graph, null);
}
public static void view(HaplotypeGraph graph, Multimap paths) {
new CreateStreamGraph(graph, paths);
}
private void browseGraph(Graph graph) {
Container content = myFrame.getContentPane();
content.setLayout(new BorderLayout());
Viewer viewer = new Viewer(graph, Viewer.ThreadingModel.GRAPH_IN_ANOTHER_THREAD);
ViewPanel view = viewer.addDefaultView(false);
myFrame.add(view, BorderLayout.CENTER);
myFrame.add(controls(), BorderLayout.SOUTH);
myFrame.setSize(1200, 1200);
myFrame.setVisible(true);
}
private JPanel controls() {
JPanel result = new JPanel(new FlowLayout());
//
// Start Position
//
JLabel intervalLabel = new JLabel("Start (i.e. 1:123)");
result.add(intervalLabel);
JTextField interval = new JTextField(10);
interval.setText(myRanges.get(myCurrentStartRange).intervalString().split("-")[0]);
interval.addActionListener(e -> {
int temp = findRangeIndex(interval.getText());
if (temp < 0) {
interval.setForeground(Color.red);
} else {
interval.setForeground(null);
myCurrentStartRange = temp;
updateGraph();
}
});
interval.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
int temp = findRangeIndex(interval.getText());
if (temp < 0) {
interval.setForeground(Color.red);
} else {
interval.setForeground(null);
myCurrentStartRange = temp;
updateGraph();
}
}
});
result.add(interval);
//
// Number of Ranges Viewed
//
JLabel numRangesLabel = new JLabel("Number Ranges");
result.add(numRangesLabel);
JTextField numRanges = new JTextField(3);
numRanges.setText(String.valueOf(myNumRangesToDisplay));
numRanges.addActionListener(e -> {
try {
myNumRangesToDisplay = Integer.parseInt(numRanges.getText());
} catch (Exception ex) {
numRanges.setText(String.valueOf(myNumRangesToDisplay));
return;
}
myGuWidthStep = (double) GU_WIDTH / (double) (myNumRangesToDisplay - 1);
updateGraph();
});
numRanges.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
try {
myNumRangesToDisplay = Integer.parseInt(numRanges.getText());
} catch (Exception ex) {
numRanges.setText(String.valueOf(myNumRangesToDisplay));
return;
}
myGuWidthStep = (double) GU_WIDTH / (double) (myNumRangesToDisplay - 1);
updateGraph();
}
});
result.add(numRanges);
//
// Path to view
//
JLabel pathLabel = new JLabel("Path");
result.add(pathLabel);
JComboBox taxaPaths = new JComboBox<>();
taxaPaths.addItem("NONE");
if (myPaths != null) {
for (String current : myPaths.keySet()) {
taxaPaths.addItem(current);
}
}
taxaPaths.addItemListener(e -> {
changePath((String) e.getItem());
updateGraph();
});
result.add(taxaPaths);
//
// Whether to show taxa labels
//
JCheckBox taxaLabels = new JCheckBox("Taxa Labels");
taxaLabels.addActionListener(e -> {
myTaxaLabels = taxaLabels.isSelected();
updateGraph();
});
result.add(taxaLabels);
//
// Whether to show edge weights
//
JCheckBox showWeights = new JCheckBox("Show Edge Weights");
showWeights.addActionListener(e -> {
myShowWeights = showWeights.isSelected();
updateGraph();
});
result.add(showWeights);
//
// Whether to show hapids
//
JCheckBox showIDs = new JCheckBox("Show Hapids");
showIDs.addActionListener(e -> {
myShowIds = showIDs.isSelected();
updateGraph();
});
result.add(showIDs);
//
// Navigate left in graph
//
JButton left = new JButton("Left");
left.addActionListener(e -> {
myCurrentStartRange = Math.max(0, myCurrentStartRange - myStepSize);
interval.setText(myRanges.get(myCurrentStartRange).intervalString().split("-")[0]);
updateGraph();
});
result.add(left);
//
// Number of reference ranges to step when navigating
//
JLabel stepSizeLabel = new JLabel("Step Size");
result.add(stepSizeLabel);
JTextField stepSize = new JTextField(3);
stepSize.setText(String.valueOf(myStepSize));
stepSize.addActionListener(e -> {
try {
myStepSize = Integer.parseInt(stepSize.getText());
} catch (Exception ex) {
stepSize.setText(String.valueOf(myStepSize));
}
});
stepSize.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
try {
myStepSize = Integer.parseInt(stepSize.getText());
} catch (Exception ex) {
stepSize.setText(String.valueOf(myStepSize));
}
}
});
result.add(stepSize);
//
// Navigate right in graph
//
JButton right = new JButton("Right");
right.addActionListener(e -> {
myCurrentStartRange = Math.min(myTotalNumRanges - 1, myCurrentStartRange + myStepSize);
interval.setText(myRanges.get(myCurrentStartRange).intervalString().split("-")[0]);
updateGraph();
});
result.add(right);
//
// Exit graph viewer
//
JButton exit = new JButton("Exit");
exit.addActionListener(e -> myFrame.dispose());
result.add(exit);
return result;
}
private int findRangeIndex(String location) {
try {
String[] chrStart = location.split(":");
Chromosome chr = Chromosome.instance(chrStart[0].trim());
int start = 0;
if (chrStart.length > 1) {
start = Integer.parseInt(chrStart[1].trim());
}
for (int i = 0; i < myTotalNumRanges; i++) {
ReferenceRange range = myRanges.get(i);
int chrCompare = range.chromosome().compareTo(chr);
if (chrCompare > 0) {
return i;
} else if (chrCompare == 0) {
if (range.start() >= start) {
return i;
}
}
}
} catch (Exception e) {
// do nothing
}
// unable to find index
return -1;
}
private void updateGraph() {
System.setProperty("org.graphstream.ui.renderer", "org.graphstream.ui.j2dviewer.J2DGraphRenderer");
myGraph.clear();
mySpriteManager = new SpriteManager(myGraph);
StringBuilder styleSheet = new StringBuilder();
// node style
styleSheet.append("node {size-mode: dyn-size;");
if (myTaxaLabels || myShowIds) {
styleSheet.append(" text-color: black; text-alignment: under;");
} else {
styleSheet.append(" text-color: white; text-alignment: center;");
}
if (myCurrentTaxon != null) {
styleSheet.append(" fill-mode: dyn-plain; fill-color: grey, #066e15, red;");
}
styleSheet.append("}");
// sprite style
styleSheet.append(" sprite {size: 0px, 0px; text-alignment: center;}");
// edge style
styleSheet.append(" edge {shape: line; size-mode: dyn-size; arrow-shape: none; fill-mode: dyn-plain; text-alignment: along;");
if (myCurrentTaxon != null) {
styleSheet.append(" fill-color: #f0f2f0, #066e15;}");
} else {
styleSheet.append(" fill-color: #f0f2f0, grey, yellow, red;}");
}
myGraph.addAttribute("ui.stylesheet", styleSheet.toString());
Map previousNodeOrder = null;
for (int i = myCurrentStartRange; i < myCurrentStartRange + myNumRangesToDisplay; i++) {
ReferenceRange range = myRanges.get(i);
int nodeIndex = 0;
List nodes = myHaplotypes.nodes(range);
NodeWrapper[] wrappers = new NodeWrapper[nodes.size()];
for (HaplotypeNode node : nodes) {
String rightNodeName = nodeName(node);
wrappers[nodeIndex] = new NodeWrapper(myGraph.addNode(rightNodeName), node);
if (myCurrentTaxon != null) {
if (node.taxaList().contains(myCurrentTaxon)) {
if (nodeOnPath(node)) {
wrappers[nodeIndex].myNode.setAttribute("ui.color", 0.5);
} else {
wrappers[nodeIndex].myNode.setAttribute("ui.color", 1.0);
}
}
}
nodeIndex++;
if (i == myCurrentStartRange) {
continue;
}
List edges = myHaplotypes.leftEdges(node);
double totalEdgeProbability = 0.0;
for (HaplotypeEdge edge : edges) {
totalEdgeProbability += edge.edgeProbability();
}
// average location relative to previous nodes connected with edges
double aveLocation = 0.0;
for (HaplotypeEdge edge : edges) {
aveLocation += (double) previousNodeOrder.get(edge.leftHapNode()) * (edge.edgeProbability() / totalEdgeProbability);
}
wrappers[nodeIndex - 1].myLocationAve = aveLocation / (double) edges.size();
}
if (i != myCurrentStartRange) {
addEdges(range);
}
previousNodeOrder = setRangeProperties(wrappers, i - myCurrentStartRange);
}
}
private void addEdges(ReferenceRange range) {
int totalNumberOfEdges = 0;
for (HaplotypeNode node : myHaplotypes.nodes(range)) {
totalNumberOfEdges += myHaplotypes.numberOfLeftEdges(node);
}
HaplotypeEdge[] edges = new HaplotypeEdge[totalNumberOfEdges];
int count = 0;
for (HaplotypeNode node : myHaplotypes.nodes(range)) {
for (HaplotypeEdge edge : myHaplotypes.leftEdges(node)) {
edges[count++] = edge;
}
}
Arrays.sort(edges, (o1, o2) -> Double.compare(o2.edgeProbability(), o1.edgeProbability()));
for (HaplotypeEdge edge : edges) {
Edge resultEdge = myGraph.addEdge(edgeName(edge), nodeName(edge.leftHapNode()), nodeName(edge.rightHapNode()), true);
if (myCurrentTaxon == null) {
resultEdge.setAttribute("ui.color", edge.edgeProbability());
resultEdge.setAttribute("ui.size", "1gu");
} else if (nodeOnPath(edge.leftHapNode()) && nodeOnPath(edge.rightHapNode())) {
resultEdge.setAttribute("ui.color", 1.0);
resultEdge.setAttribute("ui.size", "1gu");
}
if (myShowWeights) {
resultEdge.setAttribute("ui.label", DECIMAL_FORMAT.format(edge.edgeProbability()));
}
}
}
private boolean nodeOnPath(HaplotypeNode node) {
return myCurrentPath.contains(node);
}
private void changePath(String taxon) {
if (taxon == null || taxon.equalsIgnoreCase("none")) {
myCurrentTaxon = null;
myCurrentPath = null;
} else {
myCurrentTaxon = new Taxon(taxon);
myCurrentPath = myPaths.get(taxon);
}
}
private class NodeWrapper implements Comparable {
private final Node myNode;
private final HaplotypeNode myHaplotype;
public double myLocationAve = 0.0;
public NodeWrapper(Node node, HaplotypeNode haplotype) {
myNode = node;
myHaplotype = haplotype;
}
@Override
public int compareTo(NodeWrapper o) {
int result = Double.compare(myLocationAve, o.myLocationAve);
if (result != 0) {
return result;
}
return Integer.compare(myHaplotype.numTaxa(), o.myHaplotype.numTaxa());
}
}
private Map setRangeProperties(NodeWrapper[] nodes, int columnIndex) {
String columnName = columnName(nodes[0].myHaplotype.referenceRange());
Sprite header = mySpriteManager.addSprite(columnName);
header.setPosition((double) columnIndex * myGuWidthStep, GU_HEIGHT, 0.0);
header.setAttribute("ui.label", columnName);
Arrays.sort(nodes);
double guStep = ((double) GU_HEIGHT - 40.0) / (double) (nodes.length - 1);
Map nodeOrder = new HashMap<>();
int nodeIndex = 0;
for (NodeWrapper node : nodes) {
nodeOrder.put(node.myHaplotype, nodeIndex);
Node resultNode = node.myNode;
int numTaxa = node.myHaplotype.numTaxa();
ReferenceRange range = node.myHaplotype.referenceRange();
if (nodes.length == 1) {
resultNode.setAttribute("xyz", (double) columnIndex * myGuWidthStep, (double) GU_HEIGHT / 2.0, 0);
} else {
resultNode.setAttribute("xyz", (double) columnIndex * myGuWidthStep, (double) nodeIndex * guStep, 0);
}
String nodeLabel = null;
if (myTaxaLabels) {
TaxaList taxa = node.myHaplotype.taxaList();
StringBuilder builder = new StringBuilder();
boolean first = true;
for (Taxon taxon : taxa) {
if (!first) {
builder.append(", ");
}
first = false;
builder.append(taxon.getName());
}
nodeLabel = builder.toString();
} else if (myShowIds) {
nodeLabel = String.valueOf(node.myHaplotype.id());
} else {
nodeLabel = String.valueOf(numTaxa);
}
resultNode.setAttribute("ui.label", nodeLabel);
resultNode.setAttribute("ui.size", (Math.round((Math.sqrt((double) numTaxa) * 5.0))) + "gu");
nodeIndex++;
}
return nodeOrder;
}
private static String nodeName(HaplotypeNode node) {
return node.referenceRange().intervalString() + "_node:" + node.taxaList().get(0).getName();
}
private static String edgeName(HaplotypeEdge edge) {
return nodeName(edge.leftHapNode()) + "_" + nodeName(edge.rightHapNode());
}
private static String columnName(ReferenceRange range) {
return range.intervalString();
}
}