Layout Component Interfaces

This section explains the Layout interfaces that are used to integrate Layout algorithm implementations in Tensegrity Graph Framework.

These interfaces have been designed to fulfill the following requirements:

The Tensegrity Graph Framework provides the interface Layout as the general interface for all Layout algorithm implementations. We also distinguish between node and edge layout algorithms. It is common that a NodeLayout implementation is responsible for arranging both nodes and edges of a VisualGraph while an EdgeLayout is responsible for arranging edges only.

The results of routed edges performed by a NodeLayout algorithm is usually better than a Layout performed by an EdgeLayout algorithm alone. This is because each node layout algorithm uses a specific graph structure when positioning the nodes.

Once a NodeLayout algorithm has been applied, an EdgeLayout algorithm is subsequently used to refine all edge paths, taking the previous route into consideration. Our default implementation, for example, helps ensure that these edge paths are drawn with as few crossings as possible. When lines must cross, however, a bridge will be drawn at that point. This results in graphs which are more stuctured and therefore easier to read.

Interface Layout

This is the general interface for any Layout class which uses a graph drawing algorithm to perform a layout operation on the visual elements of a graph.

This interface consolidates the common methods that would have been duplicated in interfaces NodeLayout and EdgeLayout.

Layout specifies one particular part of the contract to a LayoutController by providing methods that deal with life cycle issues, such as initialization and configuration by attribute. Moreover, this interface specifies methods which allows implementations to be notified before and after a layout operation. Finally, Layout classes should provide a report of the last performed layout operation.

A Layout instance holds the nodes and edges of the graph that is to be arranged. Those nodes and edges implement type EdgeLayout and NodeLayout respectively. A Layout is only allowed to move nodes, reassign ports and change the edge path according to its implemented layout algorithm.

The interface Layout defines the common requirements for a NodeLayout and an EdgeLayout. Below you will read about some of the most important methods of this interface:

Layout Initialization:

  • void init()

    An instance of this class may be used to arrange different graphs. Since the constructor of this class is called only once, this method must be called to allow a Layout instance to do initialization work before a new graph is assigned.

Layout Identification:

  • String getName()

    Returns the name of the Layout. This name will be used to identify a layout and must be unique.

Layout Callbacks (Before and After):

  • void afterLayoutHandler()

    This method will be called after the arrangement of the graph is completed. It allows the layout class to do post-initialization work.

  • void beforeLayoutHandler()

    This method will be called before performing layout to allow the layout class to do initialization work.

Error handling and Layout Report:

  • boolean error()

    Returns true if an error has occurred; otherwise false. You can use getErrorMessage() to obtain a description text about the last occurred error.

  • String getErrorMessage()

    Returns the error-message if an error has occurred. You can use error() to find out whether an error has occurred.

  • AttributeSet getReport()

    This method submits a report about the last layout process. It provides detail information accordingly.A report contains typically the calculation durations of different processing steps.

Interface NodeLayout

The NodeLayout interface specifies the contract for classes responsible for laying out both node and edge elements of a graph. Client code may selectively add only those nodes and edges that should be positioned by the layout algorithm. The additional minor responsibilities include layout duration estimation, layout progress notification as well as two methods which can be implemented to efficiently allocate memory.

An implementation class should perform its layout using a layout algorithm that arranges nodes and edges according to some conceptual arrangement model.

The Layout classes may be used individually or by a LayoutController. To ensure that a Layout is always used properly, the following process flow is stipulated:

  1. init()

  2. getLayoutAttributesTemplate()

  3. useAttribute(AttributeSet)

  4. beforeAddNodeHandler(int)

  5. addNode(LayoutableNode)

  6. beforeAddEdgeHandler(int)

  7. addEdge(LayoutableEdge)

  8. isGraphSuitable()

  9. getEstimatedArrangingTime()

  10. beforeLayoutHandler()

  11. layout(int, int, boolean)

  12. afterLayoutHandler()

 
    ________________________________________ 
   |   + getLayoutAttributesTemplate        |
   |________________________________________|
   |   _____________________________________|
   |  |  # init                             |
   |  |_____________________________________|
   |  |  #  beforeAddNodeHandler            |
   |  |_____________________________________|
   |  |    _________________________________|
   |  |   |  + addNode                      |
   |  |   |_________________________________|
   |  |_____________________________________|
   |  |  #  beforeAddEdgeHandler            |
   |  |_____________________________________|
   |  |    _________________________________|
   |  |   |  + addEdge                      |
   |  |   |_________________________________|
   |  |_____________________________________|
   |  |   __________________________________|
   |  |  |   +  useAttribute                |
   |  |  |__________________________________|
   |  |  |\  + isGraphSuitable?       __.. |
   |  |  | \                    __..-      |
   |  |  |  \             __..-            |
   |  |  |NO \     __..--      YES         |
   |  |  |____\____________________________|
   |  |  |     |+ getEstimatedArrangingTime |
   |  |  |     |____________________________|
   |  |  |     |   _________________________|
   |  |  |     |  | # beforeLayoutHandler   |
   |  |  |     |  | # layout     |
   |  |  |     |  | # afterLayoutHandler    |
   |  |  |     |  |_________________________|
   |  |  |     |  | + getReport()           |
   |  |  |     |  |_________________________|
   |  |  |     |____________________________|
   |  |  |__________________________________|
   |  |_____________________________________|
   |________________________________________|
 
 

Below you will read about some of the most important methods of this interface:

Edge Arrangement:

  • boolean isGraphSuitable()

    This method checks whether the layout is able to arrange the visual elements it contains.

  • boolean layout(int, int, boolean)

    Performs the layout process.

Special Node Configuration:

  • AttributeSet getEdgeAttributesTemplate()

    Returns the attribute set of edges which is initialized with default values. Note: The default edge attributes are not to be confused with layout attributes. The layout-attributes configure the layout as a whole. But some layout classes allow also configuration of edges to allow different treatment.

  • AttributeSet getNodeAttributesTemplate()

    Returns the attribute set of nodes which is initialized with default values. Note: The default node attributes are not to be confused with layout attributes. The layout attributes configure the layout as a whole. But some layout classes allow also configuration of nodes to allow different treatment.

Interface EdgeLayout

The EdgeLayout interface specifies the contract for classes responsible for laying out the visual edges of a graph only. An instance should take the current edge path into consideration and rearrange edges according to the layout configuration or context. In other words, the attributes used to configure an EdgeLayout instance should have an influence on the strategy used to set the edge paths.

An EdgeLayout is only allowed to arrange the edge elements of a given graph. It should take the current edge path into consideration and rearrange edges as far as it is necessary to satisfy its configuration.

An EdgeLayout class may be used individually or by a LayoutController. To ensure that it is always used properly, the following invocation order is stipulated:

  1. getLayoutAttributesTemplate()

  2. init()

  3. useAttribute(AttributeSet)

  4. storePreferEdgePoints(ArrayList)

  5. beforeLayoutHandler()

  6. relayout(ArrayList, ArrayList)

  7. afterLayoutHandler()

 
    _____________________________________ 
   |   + getLayoutAttributesTemplate     |
   |_____________________________________|
   |   __________________________________|
   |  |  # init                          |
   |  |__________________________________|
   |  |   _______________________________|
   |  |  |   + useAttribute              |
   |  |  |_______________________________|
   |  |  |   + storePreferEdgePoints     |
   |  |  |_______________________________|
   |  |  |     __________________________|
   |  |  |    | # beforeLayoutHandler    |
   |  |  |    | # relayout               |
   |  |  |    | # afterLayoutHandler     |
   |  |  |    |__________________________|
   |  |  |    | + getReport              |
   |  |  |    |__________________________|
   |  |  |_______________________________|
   |  |__________________________________|
   |_____________________________________|
 
 

Below you will read about some of the most important methods of this interface:

Layout Configuration:

  • AttributeSet getEdgeAttributesTemplate()

    Returns the attribute set for edges which is initialized with default values. Notice: The default edge attributes are not to be confused with layout attributes. The layout attributes configure the layout as a whole. But some layout classes also allow the configuration of edges for alternate treatment.

Layout Invocation:

  • boolean relayout(ArrayList, ArrayList, Boundary)

    Arranges all specified edges, which may be arranged completely or simply readjusted, taking the current edge paths into consideration.

  • boolean storePreferEdgePoints(ArrayList)

    Call this method to store additional edge points. An EdgeLayout should take the current edge paths into consideration because in most cases the edge paths will have already been arranged by a NodeLayout.

Interface Layoutable

This is the general interface for any graph element visualization that can and will be arranged by a Layout class and satisfies its configuration requirements.

The first of these requirements mandates that an object which implements the Layoutable interface must be able to set a context name that identifies a layouter with a particular configuration. This is the layouter which arranges this graph element.

Some layouters require that nodes or edges be configured because these graph objects necessitate special treatment. For this reason it is possible to set these attributes for a node or edge directly.

This interface consolidates the common methods that would have been duplicated in interfaces LayoutableNode and LayoutEdge.

Below you will find some of the most important methods of this interface:

  • void setEdgeLayoutContext(String)

    Sets the edge layout context for this object. An edge layout context is a String identifier which is used to specify the EdgeLayout and its configuration, which is then responsible for arranging this Object.

  • AttributeSet getLayoutAttributeSet()

    Returns an AttributeSet which contains the specified Layout attributes for the graph object. This AttributeSet is usually provided by an associated Layout object to allow for special treatment of different graph objects.

  • void setLayoutAttributes(AttributeSet)

    This method allows client code to configure a Layoutable object with an AttributeSet that contains all necessary Layout attributes. This AttributeSet is usually provided by an associated Layout object to allow for special treatment of different graph objects. A implementation class of this interface should be able to store the given attribute set and provide it whenever the method getLayoutAttributeSet() is invoked.

  • void setNodeLayoutContext(String)

    Sets the layout context for this object. A node layout contex is a String identifier which is used to specify a NodeLayout and configuration that should be used to arrange this object whenever a Layout process is performed.

  • long getUniqueID()

    Returns the identifier of this object. This must be unique.

Interface LayoutableNode

This is the standard interface for any node which should be arranged by a NodeLayout. It specifies a contract with the NodeLayout interface by providing methods that deal with node location, dimension, bounding box as well as port location and its angular interval for valid incoming edges.

Below you will find the more important methods of this interface:

Location and Size:

  • Boundary getBoundingBox()

    Returns the boundary of the node.

  • BoundingBox getBoundingBox(int)

    Returns the bounding box of this composite. You can control the calculation by using the flags defined in the constants starting with BBOXMASK in the LayoutableNode interface.

  • Coordinate getLocation()

    Returns the current location of this node object.

  • void setLocation(Coordinate)

    Set the location of the node object.

  • void layout(int, int, int, int)

    Sets the bounding box of the node

Ports:

  • Coordinate getPortCoordinate(long)

    Gets the Coordinate of the port specified by portID.

  • double getPortDirection(long)

    Returns the angular interval which the identified port accepts for any valid incoming LayoutableEdge.

  • long getPorts()

    Returns the list of the provided ports by this node.

Interface LayoutableEdge

This is the general interface for any edge which should be arranged by a Layout. It specifies a contract with the Layout interface by providing methods that allow client code to retrieve, remove and insert coordinate points. Moreover, edge type, marker, port and label data is exposed by this interface.

Below you will find the more important methods of this interface:

Node Relationships:

  • LayoutableNode getSourceNode()

    Gets the LayoutableNode at the source end of this LayoutableEdge.

  • LayoutableNode getTargetNode()

    Gets the LayoutableNode at the target end of this LayoutableEdge.

Edge Point Manipulation:

  • Coordinate getCoordinateAt(int)

    Returns the Coordinate of the edge point at the index specified by index. If the index is less than zero or larger than the actual count of Coordinates an exception will be thrown.

  • void setCoordinateAt(int, Coordinate)

    Sets the Coordinate given by coordinate at the index given by index of the edge current coordinates. If the index is less than zero or larger than the actual count of Coordinates an exception will be thrown.

  • int getCoordinateCount()

    Returns the current number of Coordinates of which this edge consist.

  • Coordinate getCoordinates()

    Returns the list of the current Coordinates of which this edge consist.

  • void setCoordinates(Coordinate[])

    Uses the new points specified by coordinates. The old points will be removed and the edge will be consist of the given Coordinates

  • void insertCoordinate(int, Coordinate)

    Inserts the Coordinate given by coordinate at the index given by index. If the first index is less than zero it is adjusted to zero. If the second index is larger than the actual count of Coordinates it is adjusted to the actual count of Coordinates

  • void insertCoordinate(int, Coordinate, Coordinate[])

    Inserts the Coordinate given by coordinate at the index given by index. If the first index is less than zero it is adjusted to zero. If the second index is larger than the actual count of Coordinates it is adjusted to the actual count of Coordinates

  • void removeCoordinate(int)

    Removes the Coordinate at the index given by index from the CompositeLines coordinates. If the index is less than zero or larger than the actual count of Coordinates an exception will be thrown.

  • void removeCoordinates(int, int)

    Removes all Coordinates between the two indices given by index1 and index2 from the CompositeLines actual Coordinates. If the first index is less than zero it is adjusted to zero. If the second index is larger than the actual count of Coordinates it is adjusted to the actual count of Coordinates

Edges Reassignment:

  • long getSourcePortId()

    Returns the identifier of the port at the source node

  • long getTargetPortId()

    Returns the identifier of the port at the target node

  • boolean reassignSourcePortByID(long)

    Reassigns the source end of a edge to the desired port

  • Coordinate reassignSourceToPortByAngle(double)

    Reassigns the source end of the edge to the next reachable port using the given angle

  • boolean reassignTargetPortByID(long)

    Reassigns the target end of a edge to the desired port

  • Coordinate reassignTargetToPortByAngle(double)

    Reassigns the target end of the edge to the next reachable port using the given angle

  • void reassignToNextPort()

    Reassigns this edge to the next ports at the source and target node according to the angle between the center of the source node and the center of the target node.

Label Placement:

  • int getLabelAnchor()

    Returns the anchor of the labels position of this edge.

  • Coordinate getLabelLocation()

    Returns the location of the label

  • int getLabelPlacement()

    Returns the placement of the label of this edge.

  • int getLabelPositioning()

    Returns the positioning of the label of this edge.

  • Size getLabelSize()

    Returns the size of the label.

  • boolean hasLabel()

    Indicates whether this edge has a label

  • void layoutLabel(Coordinate)

    Set the position of the label of this edge to the given Coordinate.

Custom Layout Class Implementation

Example 4.2. Rundom Layout.

The following example shows the implementation of a simple Layout class. It can be used as a template for various custom Layout classes.

/**
 * This simple example shows how to implement a custom Layout class. 
 * It implements interface NodeLayout and arranges the nodes of a 
 * given graph randomly on a grid. 
 */
public class Ch04_CustomLayout implements NodeLayout
{
  	// the internal array to save nodes
    private LayoutableNode[] nodes;
    // the internal array to save edges
    private LayoutableEdge[] edges;  
    // to save last error message
    private String lastErrorMessage;     
    // the provided attributes
    private AttributeSet layoutAttributes;
    // the process duration to be reported
    private long processingTime;  
    // current number of nodes and edges
    private int nodeNum, edgeNum;      
    // the current progress bar
    private LayoutProgress progressBar;  

    public Ch04_CustomLayout()
    {
        // create the layout attribute set that should be 
        // used to configure this layout class 
        AttributeFactory factory = AttributeFactory.newInstance();
        this.layoutAttributes = factory.newAttributeSet();
        AttributeSet attributesCategory = factory.newAttributeSet();        
        try
        {
            attributesCategory.add(
                    factory.newAttribute(
                        "Horizontal distance", new Integer(1000)));
            attributesCategory.add(
                    factory.newAttribute(
                        "Vertical distance", new Integer(1000)));
            layoutAttributes.add(
                factory.newAttribute("Area", attributesCategory));
        }
        catch (Exception e)
        {            
            e.printStackTrace();
        }
    }     
    
    public void init()
    {
        nodeNum = edgeNum = 0;
        lastErrorMessage = "";
    }
    
    public String getName()
    {
        return "Random";
    }
    
    public  void updateAttributeSet(AttributeSet layoutAttributs){}    
    
    public boolean isGraphSuitable()
    {
      	// This layout is able to arrange any kind of graphs
        return true;
    }
    
    public void beforeAddNodeHandler(int nodeCount)
    {
      	// memory allocation
        nodes = new LayoutableNode[nodeCount]; 
    }
    
    public boolean addNode(LayoutableNode node)
    {
      	// Store node
        nodes[nodeNum++] = node;
        return true;
    }
    
    public void beforeAddEdgeHandler(int edgeCount)
    {
        edges = new LayoutableEdge[edgeCount];
    }

    public boolean addEdge(LayoutableEdge edge)
    {
      	// Store edge
        edges[edgeNum++] = edge; 
        return true;
    }

    public void useAttribute(AttributeSet layoutAttributs)
    {
        if (layoutAttributs == null)
            lastErrorMessage = "useAttribute cannot be null";
        else
            this.layoutAttributes = layoutAttributs;
    }

    public void beforeLayoutHandler()
    {
        processingTime = System.currentTimeMillis();
        if (progressBar != null)
            progressBar.setPercentage(0.0);
    }
    
    public boolean layout(int x, int y, boolean reassignPorts)
    {
        if(error()) 
            return false;
        layoutNodes(x, y);        
        layoutEdges(reassignPorts);        
        return true;
    }
    
    public void afterLayoutHandler()
    {
        processingTime = System.currentTimeMillis() - processingTime;
        if (progressBar != null)
            progressBar.setPercentage(100.0);       
    }

    public AttributeSet getLayoutAttributesTemplate()
    {
        return layoutAttributes;
    }

    public AttributeSet getNodeAttributesTemplate()
    {
        return null;
    }

    public AttributeSet getEdgeAttributesTemplate()
    {
        return null;
    }

    public boolean error()
    {
        return lastErrorMessage.length() > 0;
    }
    
    public String getErrorMessage()
    {
        return lastErrorMessage;
    }

    public AttributeSet getReport()
    {
        AttributeFactory factory = AttributeFactory.newInstance();
        AttributeSet report = factory.newAttributeSet();
        try
        {
            report.add(
                factory.newAttribute(
                    "Number of arranged nodes", new Integer(nodeNum)));
            report.add(
                factory.newAttribute(
                    "Arranging time / ms", 
                    new Integer((int) processingTime)));
        }
        catch (ConstraintViolationException e)
        {
            lastErrorMessage = e.getMessage();
        }
        catch (AttributeException e)
        {
            lastErrorMessage = e.getMessage();
        }
        return report;
    }

    public long getEstimatedArrangingTime()
    {
        return (long) (nodeNum * 0.006);
    }

    public void setProgressBar(LayoutProgress progressBar)
    {
        this.progressBar = progressBar;
    }

    public int getPreferEdgeType(AttributeSet usingAttributes)
    {
       return LayoutableEdge.EDGE_TYPE_ORTHOGONAL;
    }
    //====================================================================
    // private methods
    

    /** This method distribute nodes on a grid.
     * @param x the grid left position. 
     * @param y the grid top  position.
     */ 
    private void layoutNodes(int x, int y)
    {
        Attribute attributeCategory = layoutAttributes.get("Area");
        AttributeSet attr = (AttributeSet)attributeCategory.getValue();
        // get current setting
        int mw =  
          Integer.parseInt(
              attr.get("Horizontal distance").getValue().toString());
        int mh =  
          Integer.parseInt(
              attr.get("Vertical distance").getValue().toString());        
        int maxY = 0, cx = x, cy = y;
        
        //calc the grid width
        int gridWidth = (int)Math.ceil(Math.sqrt(nodes.length));
        Coordinate location = new Coordinate();                
        // arrange nodes on a grid  
        ar:
        for (int r = 0, n; r < gridWidth; r++)
        {
            for (int c = 0; c < gridWidth; c++)
            {                
                n = r * gridWidth + c;
                if( n == nodes.length)
                    break ar;
	            location.setX( cx);
	            location.setY( cy);            
	            nodes[n].setLocation(location);
	            cx += 
	              nodes[n].getSize().extensions[Size.X_EXTENSION] + mw; 
	            maxY = 
	              Math.max(
	                  maxY, 
	                  nodes[n].getSize().extensions[Size.Y_EXTENSION]);
            }
            cx = x;
            cy += maxY + mh ;
            maxY = 0;
        }
    }
    
    /** This method arranges edges orthogonal. 
     * @param reassignPorts
     */ 
    private void layoutEdges(boolean reassignPorts){
        // layout edges
        for (int i = 0; i < edges.length; i++)
        {
            if(reassignPorts)
                edges[i].reassignToNextPort();
            Coordinate[] coords = new Coordinate[3];            
            coords[0] = edges[i].getCoordinateAt(0);
            coords[2] = 
              edges[i].getCoordinateAt(
                  edges[i].getCoordinateCount() - 1);
            coords[1] = 
              new Coordinate(
                  coords[0].getX() + (coords[2].getX() 
                      - coords[0].getX()) / 2, 
                  coords[0] .getY());
            edges[i].setCoordinates(coords);   
        }
    }
}