All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.maizegenetics.pangenome.gui.CreateStreamGraph Maven / Gradle / Ivy

There is a newer version: 1.10
Show newest version
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();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy