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:
Equal treatment of all Layout algorithm implementions.
Afford implementation of layout algorithms without knowlage of the other parts of Tensegrity Graph Framework
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.
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.
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:
init()
getLayoutAttributesTemplate()
useAttribute(AttributeSet)
beforeAddNodeHandler(int)
addNode(LayoutableNode)
beforeAddEdgeHandler(int)
addEdge(LayoutableEdge)
isGraphSuitable()
getEstimatedArrangingTime()
beforeLayoutHandler()
layout(int, int, boolean)
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.
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:
getLayoutAttributesTemplate()
init()
useAttribute(AttributeSet)
storePreferEdgePoints(ArrayList)
beforeLayoutHandler()
relayout(ArrayList, ArrayList)
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.
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.
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.
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.
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);
}
}
}
© 2004, 2005 Tensegrity Software GmbH