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

org.biopax.paxtools.io.sbgn.SBGNLayoutManager Maven / Gradle / Ivy

package org.biopax.paxtools.io.sbgn;

import java.util.*;

import org.ivis.layout.*;
import org.ivis.layout.sbgn.SbgnPDNode;
import org.ivis.layout.sbgn.SbgnProcessNode;
import org.ivis.layout.sbgn.SbgnPDLayout;
import org.sbgn.bindings.Arc;
import org.sbgn.bindings.Port;
import org.sbgn.bindings.Glyph;
import org.sbgn.bindings.Sbgn;
import org.sbgn.bindings.Bbox;

/**
 * SBGNLayoutManager Class: Applies layout by using ChiLay to Sbgn graph generated by paxtools.
 * @author: Istemi Bahceci
 * */
public class SBGNLayoutManager
{
    // Layout and root objects
    private Layout layout;
    private VCompound root;

    // mapping between view and layout level
    private HashMap  viewToLayout;
    private HashMap  layoutToView;
    private HashMap   glyphToVNode;
    private HashMap  idToGLyph;
    private HashMap  idToCompartmentGlyphs;
    private HashMap  portIDToOwnerGlyph;
    private HashMap  idToArcs;

    /**
     * Applies CoSE layout to the given SBGN model.
     *
     * @param sbgn Sbgn object to which layout will be applied
     * @return Laid out sbgn object
     */
    public Sbgn createLayout(Sbgn sbgn)
    {
        viewToLayout = new HashMap();
        glyphToVNode = new HashMap();
        idToGLyph = new HashMap();
        idToCompartmentGlyphs = new HashMap();
        portIDToOwnerGlyph = new HashMap();
        layoutToView = new HashMap();
        idToArcs = new HashMap();

        // Using Compound spring  embedder layout
        this.layout = new SbgnPDLayout();

        // This list holds the glyphs that will be deleted after corresponding glyph is added to child glyph of another glyph.
        ArrayList  deletedList = new ArrayList();

        LGraphManager graphMgr = this.layout.getGraphManager();
        LGraph lRoot = graphMgr.addRoot();
        this.root = new VCompound(new Glyph());

        // Detect compartment glyphs and put them in a hashmap, also set compartment glyphs of members of complexes.
        for (Glyph g: sbgn.getMap().getGlyph())
        {
            if(g.getClazz().equals("compartment"))
            {
                idToCompartmentGlyphs.put(g.getId(), g);
            }

            //Add compartment ref to the all children of this node.
            if(g.getCompartmentRef() != null)
            {
                Glyph compartment = (Glyph)g.getCompartmentRef();
                setCompartmentRefForComplexMembers(g, compartment);
            }
        }


        // Add glyphs to the compartment glyphs according to their "compartmentRef" field.
        for (Glyph g: sbgn.getMap().getGlyph())
        {
            if(g.getCompartmentRef() != null)
            {
                Glyph containerCompartment = (Glyph)g.getCompartmentRef();
                idToCompartmentGlyphs.get(containerCompartment.getId()).getGlyph().add(g);
                deletedList.add(g);
            }
        }

        // Delete the duplicate glyphs, after they are moved to corresponding compartment glyph.
        for (Glyph g: deletedList)
        {
            sbgn.getMap().getGlyph().remove(g);
        }

        // initialize the map for keeping ports and their owner glyphs
        // with entries like: 
        initPortIdToGlyphMap(sbgn.getMap().getGlyph());

        //Remove ports from source and target field of ports
        //replace them with owner glyphs of these ports
        removePortsFromArcs(sbgn.getMap().getArc());

        // Assign logical operator and Process nodes to compartment
        assignProcessAndLogicOpNodesToCompartment(sbgn);

        // Create Vnodes for ChiLay layout component
        createVNodes(root, sbgn.getMap().getGlyph());

        for (VNode vNode: this.root.children)
        {
            this.createLNode(vNode, null, this.layout);
        }

        // Create LEdges for ChiLay layout component
        createLEdges(sbgn.getMap().getArc(), this.layout);
        graphMgr.updateBounds();

        // Apply layout
        this.layout.runLayout();
        graphMgr.updateBounds();

        // Here if any SbgnProcessNode node is returned from SBGNPD Layout
        // this means that we will have two additional port info. We should
        // add this information to libSbgn objects
        for(Object lNode: this.layout.getAllNodes())
        {
            if( (LNode)lNode instanceof SbgnProcessNode)
            {
                //Set geometry of corresponding node
                VNode vNode = layoutToView.get(((SbgnProcessNode) lNode).label);
                Bbox tempBbox = vNode.glyph.getBbox();
                tempBbox.setX((float)(((SbgnProcessNode) lNode).getLeft()));
                tempBbox.setY((float) (((SbgnProcessNode) lNode).getTop()));
                vNode.glyph.setBbox(tempBbox);

                //Created port objects in layout level
                SbgnPDNode inputLPort = ((SbgnProcessNode) lNode).getInputPort();
                SbgnPDNode outputLPort = ((SbgnProcessNode) lNode).getOutputPort();

                // New port objects
                Port inputPort = new Port();
                Port outputPort = new Port();

                // Set port attributes
                inputPort.setX((float) (inputLPort.getCenterX()));
                inputPort.setY((float)(inputLPort.getCenterY()));
                inputPort.setId(inputLPort.label);

                outputPort.setX((float)(outputLPort.getCenterX()));
                outputPort.setY((float) (outputLPort.getCenterY()));
                outputPort.setId((outputLPort.label));

                //Clear existing ports !
                vNode.glyph.getPort().clear();
                //Connect existing arcs to newly created ports
                //These ports are created by ChiLay and SBGNPD Layout
                connectArcToPort(inputLPort, inputPort);
                connectArcToPort(outputLPort, outputPort);

                //Add ports to the corresponding glyph
                vNode.glyph.getPort().add(inputPort);
                vNode.glyph.getPort().add(outputPort);
            }
        }

        // Update the bounds
        for (VNode vNode: this.root.children)
        {
            updateCompoundBounds(vNode.glyph, vNode.glyph.getGlyph());
        }

        // Clear inside of the compartmentGlyphs
        for (Glyph compGlyph: idToCompartmentGlyphs.values())
        {
            //Again add the members of compartments
            for(Glyph memberGlyph:compGlyph.getGlyph() )
            {
                sbgn.getMap().getGlyph().add(memberGlyph);
            }
            compGlyph.getGlyph().clear();
        }

        return sbgn;
    }

    /**
     * This method connects the existing arcs to the newly created
     * ports which are created by ChiLay and SBGNPD Layout.
     * @param lPort l level port object.
     * @param vPort v level port object.
     * */
    public void connectArcToPort(SbgnPDNode lPort, Port vPort)
    {
        //Iterate over the edges of l level port
        for  (Object e: (lPort.getEdges()))
        {
            //Ignore rigid edges
            if(((LEdge)e).type.equals("rigid edge"))
                continue;

            //Determine the if vPort is source or target
            Arc arc = idToArcs.get(((LEdge)e).label);
            if( lPort.label.equals(((LEdge)e).getSource().label ))
            {
                arc.setSource(vPort);
            }
            else if ( lPort.label.equals(((LEdge)e).getTarget().label ) )
            {
                arc.setTarget(vPort);
            }
        }
    }

    /**
     * This method finds process nodes and logical operator nodes in sbgn map and assigns them to a compartment by using majority rule.
     * @param sbgn Given Sbgn map.
     * */
    public void assignProcessAndLogicOpNodesToCompartment(Sbgn sbgn)
    {
        // Create a hashmap for keeping a node( generally logical operators and process nodes ) and its neighbours.
        // TreeMap value of the hash map keeps track of compartment nodes that includes neighbours of the node by String id and
        // Integer value holds the number of occurences of that compartment among the neighbours of the node as parent.
        HashMap >  nodetoNeighbours = new HashMap>();
        List glyphList = sbgn.getMap().getGlyph();
        List   arcList   = sbgn.getMap().getArc();

        // Keeps track of process and logical operator nodes that will be assigned to a compartment.
        ArrayList targetNodes = new ArrayList();

        //Iterate over glyphs of sbgn map
        for(Glyph glyph: glyphList)
        {
            // Here logical operator nodes and process nodes are interested !
            if(glyph.getClazz().equals("process") ||glyph.getClazz().equals("omitted process")||glyph.getClazz().equals( "uncertain process") || glyph.getClazz().equals("phenotype")||
                    glyph.getClazz().equals("association") || glyph.getClazz().equals("dissociation") ||glyph.getClazz().equals("and")||glyph.getClazz().equals("or")||glyph.getClazz().equals("not"))
            {
                // Add a new value to hash map and also store the node as target node
                String processGlyphID = glyph.getId();
                String rootID = "root";
                nodetoNeighbours.put(processGlyphID, new HashMap());
                targetNodes.add(glyph);

                // Iterate over arc list
                for(Arc arc: arcList)
                {
                    Glyph target = null;
                    Glyph source = null;

                    // If source and target of node is port find its owner glyph ! else just assign it.
                    if(arc.getSource() instanceof Port)
                        source = portIDToOwnerGlyph.get(((Port)arc.getSource()).getId());
                    else
                        source = (Glyph)arc.getSource();

                    if(arc.getTarget() instanceof Port)
                        target = portIDToOwnerGlyph.get(((Port)arc.getTarget()).getId());
                    else
                        target = (Glyph)arc.getTarget();

                    // If source of any arc is our node, then target must be neighbour of this node !
                    if(source.getId().equals(processGlyphID))
                    {
                        populateCompartmentOccurencesMap(target, nodetoNeighbours.get(processGlyphID));
                    }
                    // same as target part !!
                    else if(target.getId().equals(processGlyphID))
                    {
                        populateCompartmentOccurencesMap(source, nodetoNeighbours.get(processGlyphID));
                    }
                }
            }
        }

        //Finally assign nodes to compartments by majority rule
        for(Glyph glyph: targetNodes)
        {
            String id = glyph.getId();
            HashMap compartmentsOfTargetNode = nodetoNeighbours.get(id);

            // Finally sort the hashmap to obtain the compartment that includes majority of the neighbours of the targetNode 
            List> entries = new LinkedList>(compartmentsOfTargetNode.entrySet());
            Collections.sort(entries, new Comparator>() {
                @Override
                public int compare(Map.Entry o1, Map.Entry o2)
                {
                    return -(o1.getValue().compareTo(o2.getValue()));
                }
            });

            if(entries.size() > 0)
            {
                // if the process belongs to root do not make any changes
                if(entries.get(0).getKey().equals("root"))
                    continue;

                Glyph compartment = idToCompartmentGlyphs.get(entries.get(0).getKey());
                //Set compartmentRef of process glyph also!
                glyph.setCompartmentRef(compartment);
                compartment.getGlyph().add(glyph);

                //Remove it from sbgn also
                sbgn.getMap().getGlyph().remove(glyph);
            }
        }
    }

    /**
     * Updates a hashmap by incrementing the number of nodes in the compartment glyph that includes targetGlyph.
     * It is assumed that given hashmap includes compartment ids' as keys and number of nodes as values.This method 
     * is an utility method that will be used to populate a hashmap while determining the compartment node of a process
     * node by majority rule.
     *
     * @param targetGlyph glyph whose occurence will be updated in the given hashmap.
     * @param compartmentIDandOccurenceMap  Map that references number of nodes in a compartment by compartment ids .
     * */
    public void populateCompartmentOccurencesMap(Glyph targetGlyph,  HashMap compartmentIDandOccurenceMap)
    {
        String rootID = "root";

        // if compartment ref of targetGlyph node is not null, increment its occurence by 1
        if(targetGlyph.getCompartmentRef() != null)
        {
            Glyph  containerCompartment = (Glyph)targetGlyph.getCompartmentRef();
            String compartmentID = containerCompartment.getId();
            Integer compartmentOccurrenceValue = compartmentIDandOccurenceMap.get(compartmentID);

            if( compartmentOccurrenceValue != null)
            {
                compartmentIDandOccurenceMap.put(compartmentID, compartmentOccurrenceValue + 1);
            }
            else
                compartmentIDandOccurenceMap.put(compartmentID, 1);
        }
        // else targetGlyph is in root graph so increment root graphs counter value by 1
        else
        {
            Integer compartmentOccurrenceValue = compartmentIDandOccurenceMap.get(rootID);

            if( compartmentOccurrenceValue != null)
            {
                compartmentIDandOccurenceMap.put(rootID, compartmentOccurrenceValue + 1);
            }
            else
                compartmentIDandOccurenceMap.put(rootID, 1);
        }
    }

    /**
     * Updates bounds of a compound node ( i.e. complex glyph ) from its children .
     * @param parent compound glyph.
     * @param childGlyphs related children of parent .
     * */
    public void updateCompoundBounds(Glyph parent,List childGlyphs)
    {
        float PAD = (float) 2.0;
        float minX = Float.MAX_VALUE; float minY = Float.MAX_VALUE;
        float maxX = Float.MIN_VALUE; float maxY = Float.MIN_VALUE;

        for (Glyph tmpGlyph:childGlyphs)
        {
            if(!tmpGlyph.getClazz().equals("unit of information") && !tmpGlyph.getClazz().equals("state variable") )
            {
                if(tmpGlyph.getGlyph().size() > 0)
                    updateCompoundBounds(tmpGlyph, tmpGlyph.getGlyph());

                float w = tmpGlyph.getBbox().getW();
                float h = tmpGlyph.getBbox().getH();

                // Verify MIN and MAX x/y again:
                minX = Math.min(minX, (tmpGlyph.getBbox().getX()));
                minY = Math.min(minY, (tmpGlyph.getBbox().getY()));
                maxX = Math.max(maxX, (tmpGlyph.getBbox().getX())+w);
                maxY = Math.max(maxY, (tmpGlyph.getBbox().getY())+h);

                if (minX == Float.MAX_VALUE) minX = 0;
                if (minY == Float.MAX_VALUE) minY = 0;
                if (maxX == Float.MIN_VALUE) maxX = 0;
                if (maxY == Float.MIN_VALUE) maxY = 0;

                parent.getBbox().setX(minX - PAD);
                parent.getBbox().setY(minY - PAD);
                parent.getBbox().setW(maxX -  parent.getBbox().getX() + PAD);
                parent.getBbox().setH(maxY -  parent.getBbox().getY() + PAD);
            }
        }
    }

    /**
     * Recursively creates VNodes from Glyphs of Sbgn.
     *
     * @param parent Parent of the glyphs that are passed as second arguement.
     * @param glyphs Glyphs that are child of parent which is passed as first arguement.
     *
     * */
    public void createVNodes(VCompound parent,List glyphs)
    {
        for(Glyph glyph: glyphs )
        {
            if (!glyph.getClazz().equals("state variable") && !glyph.getClazz().equals("unit of information")  )
            {
                if(glyph.getClazz().equals("process"))
                {
                    VCompound v = new VCompound(glyph);
                }

                if(!this.isChildless(glyph))
                {
                    VCompound v = new VCompound(glyph);

                    idToGLyph.put(glyph.getId(), glyph);
                    glyphToVNode.put(glyph, v);
                    parent.children.add(v);
                    createVNodes(v, glyph.getGlyph());
                }

                else
                {
                    VNode v = new VNode(glyph);
                    idToGLyph.put(glyph.getId(), glyph);
                    glyphToVNode.put(glyph, v);
                    parent.children.add(v);
                }
            }
        }
    }

    /**
     * Creates LNodes from Arcs of Sbgn and adds it to the passed layout object.
     *
     * @param arcs List of arc objects from which the LEdges will be constructed for ChiLay Layout component.
     * @param layout layout object to which the created LEdges added.
     *
     * */
    public void createLEdges(List arcs, Layout layout)
    {
        for(Arc arc: arcs )
        {
            LEdge lEdge = layout.newEdge(null);
            lEdge.type = arc.getClazz();
            lEdge.label = arc.getId();
            LNode sourceLNode = this.viewToLayout.get(glyphToVNode.get(arc.getSource()));
            LNode targetLNode = this.viewToLayout.get(glyphToVNode.get(arc.getTarget()));
            idToArcs.put(arc.getId(), arc);

            // Add edge to the layout
            this.layout.getGraphManager().add(lEdge, sourceLNode, targetLNode);
        }
    }

    /**
     * Helper function for creating LNode objects from VNode objects and adds them to the given layout.
     *
     * @param vNode  VNode object from which a corresponding LNode object will be created.
     * @param parent parent of vNode, if not null vNode will be added to layout as child node.
     * @param layout layout object to which the created LNodes added.
     * */

    public void createLNode(VNode vNode,VNode parent,Layout layout)
    {
        LNode lNode = ((SbgnPDLayout)layout).newNode(vNode);
        lNode.type  = vNode.glyph.getClazz();
        lNode.label = vNode.glyph.getId();
        LGraph rootLGraph = layout.getGraphManager().getRoot();

        //Add corresponding nodes to corresponding maps
        this.viewToLayout.put(vNode, lNode);
        layoutToView.put(lNode.label,vNode);

        // if the vNode has a parent, add the lNode as a child of the parent l-node.
        // otherwise, add the node to the root graph.
        if (parent != null)
        {
            LNode parentLNode = this.viewToLayout.get(parent);
            parentLNode.getChild().add(lNode);
        }

        else
        {
            rootLGraph.add(lNode);
        }

        lNode.setLocation(vNode.glyph.getBbox().getX(),vNode.glyph.getBbox().getY());

        if (vNode instanceof VCompound)
        {
            VCompound vCompound = (VCompound) vNode;
            // add new LGraph to the graph manager for the compound node
            layout.getGraphManager().add(layout.newGraph(null), lNode);
            // for each VNode in the node set create an LNode
            for (VNode vChildNode: vCompound.getChildren())
            {
                this.createLNode(vChildNode, vCompound, layout);
            }
        }

        else
        {
            lNode.setWidth(vNode.glyph.getBbox().getW());
            lNode.setHeight(vNode.glyph.getBbox().getH());
        }
    }

    /**
     * This method recursively set compartmentRef fields of members of any complex glyphs
     * as same as complex's compartmentRef
     *
     * @param glyph target glyph whose compartmentRef(compartment parameter) will be set.
     * @param compartment compartmentRef value that will be set.
     * */
    public void setCompartmentRefForComplexMembers(Glyph glyph, Glyph compartment)
    {
        glyph.setCompartmentRef(compartment);
        if(glyph.getCompartmentRef() != null && glyph.getGlyph().size() > 0)
        {
            for(Glyph g: glyph.getGlyph() )
            {
                setCompartmentRefForComplexMembers(g, compartment);
            }
        }
    }

    /**
     * This method replaces ports of arc objects with their owners.
     * @param arcs Arc list of SBGN model
     * */
    public void removePortsFromArcs(List arcs)
    {
        for(Arc arc: arcs )
        {
            // If source is port, first clear port indicators else retrieve it from hashmaps
            if (arc.getSource() instanceof Port )
            {
                Glyph source = portIDToOwnerGlyph.get(((Port)arc.getSource()).getId());
                arc.setSource(source);
            }

            // If target is port, first clear port indicators else retrieve it from hashmaps
            if (arc.getTarget() instanceof Port)
            {
                Glyph target = portIDToOwnerGlyph.get(((Port)arc.getTarget()).getId());
                arc.setTarget(target);
            }
        }
    }

    /**
     * This method initializes map for glyphs and their respective ports.
     * @param glyphs Glyph list of SBGN model
     * */
    public void initPortIdToGlyphMap(List glyphs)
    {
        for(Glyph glyph: glyphs)
        {
            for(Port p: glyph.getPort())
            {
                portIDToOwnerGlyph.put(p.getId(), glyph );
            }
            if(glyph.getGlyph().size() > 0)
                initPortIdToGlyphMap(glyph.getGlyph());
        }
    }

    /**
     * Returns true if a glyph includes child glyphs (state and info glyphs are out of count!)
     * @param targetGlyph target glyph that will be queried.
     * @return true/false
     * */
    public boolean isChildless(Glyph targetGlyph)
    {
        boolean checker = true;
        for(Glyph glyph: targetGlyph.getGlyph() )
        {
            if ( !glyph.getClazz().equals("state variable") && !glyph.getClazz().equals("unit of information")  )
            {
                checker = false;
                break;
            }
        }
        return checker;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy