documentationfor yFiles for HTML 2.6

Multi-page Layout

This section presents the multi-page layout concept.

About the Concept

Multi-page layout enables the presentation of large graphs in an easily navigable and clear manner. It means breaking apart a given graph into a set of smaller graphs so that each layout of a small graph fits into a given width and height.

A multi-page layout with its set of small graphs avoids common presentation problems of large graphs with many nodes and edges. Problems, like, e.g., graph elements that are hardly discernible because of a small zoom level when viewing/printing the entire graph, or long edge paths that are hard to follow over numerous poster pages in a poster print-out of a graph.

The algorithm aims to find small graphs whose layout utilizes the specified width and height to the best extent. It puts as many as possible elements from the original graph into each small graph, thus minimizing the number of resulting small graphs.

Multi-page layout sample
Original diagram
Four (out of eight) small graphs that result from running multi-page layout. Blue nodes are directly from the original diagram, yellow nodes are additional proxy elements (see text).

Breaking apart the original graph is done by sub-dividing it and, for each connection between two nodes that is cut, introducing additional nodes and edges as proxy elements in both involved smaller graphs.

The proxy elements in either small graph stand in for the original edge and can be used as a means to get to the corresponding other small graph.

Sub-division can furthermore also take place at the node level, where nodes with many connections are split up into multiple “parts” that are distributed to different small graphs. This also introduces additional nodes and edges as proxy elements in the involved smaller graphs.

Carefully observe that, in contrast to the major layout algorithms of the yFiles library, multi-page layout produces not a single graph as its result, but instead a set of graphs.

Terminology

The original graph that is given to the algorithm is also called the model graph The smaller graphs that result from sub-dividing and augmenting the model graph are referred to as the page graphs.

The additional nodes and edges that are introduced to represent an edge from the original graph between two nodes which are in different page graphs after sub-division, are called connector nodes and connector edges, respectively, or also often simply connectors.

Replacing an edge and introducing connector nodes and connector edges
Connected nodes A and B with their neighbors in the original graph
A and B in different page graphs. Both nodes have an additional connector node (emphasized, smaller) and an additional connector edge (emphasized) that stand in for the connecting edge of the original graph.

Further nodes and edges are added when a node with many connections needs to be split up into multiple parts in order to be able to assign these parts to different page graphs. Each of the parts gets an equal share of the original neighbor nodes (roughly).

The additional new parts of the original node are called proxy nodes, all edges incident to them are called proxy edges. They represent the connections between the original node and its neighbors.

For each of the proxy nodes in other page graphs, the original node gets a so-called proxy reference node as a new neighbor. The connecting edge to this new neighbor is called proxy reference edge.

Splitting up a node and introducing a proxy node, proxy edges, a proxy reference node, and a proxy reference edge
Original node with its neighbors
The node (conceptually) before the split
The node split up into two “parts,” A and A′, in different page graphs, each having (roughly) one half of the original neighbors. A′ (emphasized, normal size) is a proxy node of the original node A. It is connected to the original node’s neighbors by means of proxy edges (emphasized) that stand in for the connecting edges of the original graph. A has an additional proxy reference node (emphasized, smaller) that references proxy node A′. The connecting edge is a proxy reference edge (emphasized).

Relevant Classes

The following table lists the relevant classes and interfaces for multi-page layout:

Relevant classes for this algorithm
Classname Description
MultiPageLayoutMain algorithm. See the description below.
MultiPageLayoutResultThe container to hold the results of a multi-page layout, i.e., the page graphs. See Obtaining the Result from a Multi-Page Layout.

Running a Multi-Page Layout

The MultiPageLayout class is the main class for calculating multi-page layouts. It generates a set of so-called page graphs whose layouts each fit into a given width and height.

To calculate the actual layouts of the page graphs, it relies on a core layout algorithm.

MultiPageLayout uses a scheme that is different from that of the yFiles major layout algorithms in several ways:

  • The result of the layout is a set of graphs, the page graphs, bundled in MultiPageLayoutResult.
  • The original graph that is given to MultiPageLayout is not modified in any way.
  • The page graphs contain graph elements from the original graph plus additional nodes and edges.
  • The result has to be retrieved in callback which has to be set to layoutCallback before running the layout.

The result of the algorithm is returned in a Querying the MultiPageLayoutResult container object. The original graph that is given to MultiPageLayout is not modified in any way.

Although the MultiPageLayout is applied as described in Applying an Automatic Layout just like every other layout algorithm, the result of the layout calculation is not accessible without further setup.

MultiPageLayout’s applyLayout method does not modify the original graph.

Instead, the MultiPageLayoutResult container object needs to be retrieved through an implementation of interface ILayoutCallback. Its callback method layoutDone is called at the end of applyLayout. The implementation needs to be registered with MultiPageLayout using property

layoutCallback
Sets the ILayoutCallback implementation. After a multi-page layout has completed, the implementation gets called and receives the MultiPageLayoutResult container object holding the resulting page graphs.

Example ILayoutCallback implementation
class LayoutCallback extends BaseClass(ILayoutCallback) {
  $graphs = null

  /**
   * @param {!MultiPageLayoutResult} result
   */
  layoutDone(result) {
    // delegate to a factory to create IGraph implementations from the results
    const builder = new MultiPageIGraphBuilder(result)
    this.$graphs = builder.createGraphs()
  }

  /**
   * @type {?Array.<IGraph>}
   */
  get graphs() {
    return this.$graphs
  }
}class LayoutCallback extends BaseClass(ILayoutCallback) implements ILayoutCallback {
  private $graphs: IGraph[] | null = null

  layoutDone(result: MultiPageLayoutResult): void {
    // delegate to a factory to create IGraph implementations from the results
    const builder = new MultiPageIGraphBuilder(result)
    this.$graphs = builder.createGraphs()
  }

  get graphs(): IGraph[] | null {
    return this.$graphs
  }
}

Note that the resulting page graphs in the MultiPageLayoutResult container object are all of type LayoutGraph. The helper class MultiPageIGraphBuilder in the example is custom code. The Obtaining the Result from a Multi-Page Layout section shows how to implement such a class. Also, a more comprehensive example is shown in the tutorial sample application Multi-Page Layout demo.

Setup for Layout

To calculate a multi-page layout, a MultiPageLayout instance needs

  • a core layout algorithm to actually compute the layouts of the page graphs
  • a configured instance of MultiPageLayoutData to provide unique IDs for all nodes, edges, node labels, and edge labels of the model graph
  • an ILayoutCallback implementation

The core layout algorithm has to be passed to the constructor.

MultiPageLayout uses unique IDs for the elements from the original graph to relate page graph elements to their originals from the model graph. In particular, the IDs are necessary to collect the information that is returned for connectors and proxy elements as part of the MultiPageLayoutResult container. For most use cases it is sufficient if the IDs are simply the references to the original elements themselves.

Unique IDs for nodes and edges can be specified using the MultiPageLayoutData's properties nodeIds and edgeIds, respectively, unique IDs for node labels and edge labels using the properties nodeLabelIds and edgeLabelIds, respectively.

The following code shows the canonical approach to provide unique IDs for graph elements. This scheme is also used in tutorial demo application Multi-Page Layout demo.

Canonical setup of mandatory unique IDs for graph elements
/**
 * @returns {!MultiPageLayoutData}
 */
function setupCanonicalUniqueIDs() {
  return new MultiPageLayoutData({
    nodeIds: (node) => node,
    edgeIds: (edge) => edge,
    nodeLabelIds: (label) => label,
    edgeLabelIds: (label) => label
  })
}function setupCanonicalUniqueIDs(): MultiPageLayoutData {
  return new MultiPageLayoutData({
    nodeIds: (node: INode) => node,
    edgeIds: (edge: IEdge) => edge,
    nodeLabelIds: (label: ILabel) => label,
    edgeLabelIds: (label: ILabel) => label
  })
}

In summary, the setup of MultiPageLayout looks like this:

Setup of MultiPageLayout
// Create the core layout algorithm.
const hierarchicLayout = new HierarchicLayout()

// Create multi-page layout algorithm with the core layout
const multiPageLayout = new MultiPageLayout({
  coreLayout: hierarchicLayout,
  layoutCallback: new LayoutCallback()
})

// Set up unique IDs for graph elements.
const data = setupCanonicalUniqueIDs()

// Calculate multi-page layout.
// Afterwards, the LayoutCallback's LayoutDone method
// will be called with the result
graph.applyLayout(multiPageLayout, data)

Options

Page Size
maximumPageSize
Specifies the page dimensions, i.e., the width and height into which the layout of a page graph needs to fit.
Maximum Duration
maximumDuration
Sets the preferred maximum duration of the layout process in milliseconds. By default, the algorithm runs without time restriction.Higher values enable the algorithm to find a smaller set of page graphs, where each makes better use of the available page size. Also, there are fewer connectors and proxy elements in the page graphs.

By default, MultiPageLayout distributes the nodes from the original graph to the page graphs automatically. MultiPageLayoutData allows for assigning an object that serves as ID to each node using property nodeClusterIds. MultiPageLayout then tries to distribute nodes with the same ID to the same page graph.

Obtaining the Result from a Multi-Page Layout

MultiPageLayoutResult is the container to hold the results of the multi-page layout. It is passed to the layoutDone method of the layoutCallback.

MultiPageLayoutResult provides access to all page graphs from the layout result and enables identification of original graph elements, connectors, and proxy elements. This information is encapsulated in INodeInfo and IEdgeInfo objects, respectively INodeLabelInfo and IEdgeLabelInfo objects.

Note that the page graphs in MultiPageLayoutResult are LayoutGraph instances. Therefore they have to be copied into IGraph instances to be used with a view.

The example Building page graphs from the MultiPageLayoutResult shows a basic class which gets a result and provides a method to create IGraph instances for the pages.

Building page graphs from the MultiPageLayoutResult
class MultiPageGraphBuilder {
  result
  // remember which pageNode corresponds to which created INode
  pageNodeToINode = new HashMap()
  // remember which port in the original graph corresponds to which port in the page graph
  modelPortToIPort = new HashMap()

  /**
   * The created instance handles the given result
   * @param {!MultiPageLayoutResult} result
   */
  constructor(result) {
    this.result = result
  }

  /**
   * Create a page graph from a page of the result
   * @param {!MultiPageLayoutResult} result
   * @param {number} pageIndex
   * @returns {!DefaultGraph}
   */
  createPageGraph(result, pageIndex) {
    if (pageIndex < 0 || pageIndex >= result.pageCount()) {
      throw new Error('index out of bounds')
    }

    // get the resulting page graph
    const page = result.getPage(pageIndex)
    // create the IGraph instance to copy the result to
    const graph = new DefaultGraph()

    // copy all nodes
    page.nodes.forEach((pageNode) => {
      // get the NodeInfo object for the current node.
      const nodeInfo = result.getNodeInfo(pageNode)
      // create an INode instance for it
      const node = this.createNode(graph, page, pageNode, nodeInfo)
      this.pageNodeToINode.set(pageNode, node)
    })

    // copy all edges
    page.edges.forEach((pageEdge) => {
      const edgeInfo = result.getEdgeInfo(pageEdge)
      this.createEdge(page, graph, pageEdge, edgeInfo)
    })

    return graph
  }

  /**
   * Create an INode for the given page node
   * @param {!IGraph} graph
   * @param {!LayoutGraph} page
   * @param {!YNode} pageNode
   * @param {?INodeInfo} nodeInfo
   * @returns {!INode}
   */
  createNode(graph, page, pageNode, nodeInfo) {
    /* ... */
  }

  /**
   * Create an IEdge for the given edge info
   * @param {!LayoutGraph} page
   * @param {!IGraph} graph
   * @param {!Edge} pageEdge
   * @param {?IEdgeInfo} edgeInfo
   * @returns {!IEdge}
   */
  createEdge(page, graph, pageEdge, edgeInfo) {
    /* ... */
  }

  /**
   * Get a default node style for the given node type
   * @param {!NodeType} nodeInfoType
   * @returns {!INodeStyle}
   */
  getDefaultNodeStyle(nodeInfoType) {
    /* ... */
  }

  /**
   * Get a default edge style for the given edge type
   * @param {!EdgeType} edgeInfoType
   * @returns {!IEdgeStyle}
   */
  getDefaultEdgeStyle(edgeInfoType) {
    /* ... */
  }

  /**
   * Create a port on the given node which is a copy of the given port
   * @param {!INode} node
   * @param {!IPort} port
   * @returns {!IPort}
   */
  copyPort(node, port) {
    /* ... */
  }

  /**
   * Create a label on the given node which is a copy of the given label
   * @param {!INode} node
   * @param {!ILabel} label
   * @returns {!ILabel}
   */
  copyLabel(node, label) {
    /* ... */
  }
}class MultiPageGraphBuilder {
  result: MultiPageLayoutResult
  // remember which pageNode corresponds to which created INode
  pageNodeToINode: HashMap<YNode, INode> = new HashMap()
  // remember which port in the original graph corresponds to which port in the page graph
  modelPortToIPort: HashMap<IPort, IPort> = new HashMap()

  /**
   * The created instance handles the given result
   */
  constructor(result: MultiPageLayoutResult) {
    this.result = result
  }

  /**
   * Create a page graph from a page of the result
   */
  createPageGraph(result: MultiPageLayoutResult, pageIndex: number): DefaultGraph {
    if (pageIndex < 0 || pageIndex >= result.pageCount()) {
      throw new Error('index out of bounds')
    }

    // get the resulting page graph
    const page = result.getPage(pageIndex)
    // create the IGraph instance to copy the result to
    const graph = new DefaultGraph()

    // copy all nodes
    page.nodes.forEach((pageNode) => {
      // get the NodeInfo object for the current node.
      const nodeInfo = result.getNodeInfo(pageNode)
      // create an INode instance for it
      const node = this.createNode(graph, page, pageNode, nodeInfo)
      this.pageNodeToINode.set(pageNode, node)
    })

    // copy all edges
    page.edges.forEach((pageEdge) => {
      const edgeInfo = result.getEdgeInfo(pageEdge)
      this.createEdge(page, graph, pageEdge, edgeInfo)
    })

    return graph
  }

  /**
   * Create an INode for the given page node
   */
  // @ts-ignore ... because the return type doesn't match
  createNode(graph: IGraph, page: LayoutGraph, pageNode: YNode, nodeInfo: INodeInfo | null): INode {
    /* ... */
  }

  /**
   * Create an IEdge for the given edge info
   */
  // @ts-ignore ... because the return type doesn't match
  createEdge(page: LayoutGraph, graph: IGraph, pageEdge: Edge, edgeInfo: IEdgeInfo | null): IEdge {
    /* ... */
  }

  /**
   * Get a default node style for the given node type
   */
  // @ts-ignore ... because the return type doesn't match
  getDefaultNodeStyle(nodeInfoType: NodeType): INodeStyle {
    /* ... */
  }

  /**
   * Get a default edge style for the given edge type
   */
  // @ts-ignore ... because the return type doesn't match
  getDefaultEdgeStyle(edgeInfoType: EdgeType): IEdgeStyle {
    /* ... */
  }

  /**
   * Create a port on the given node which is a copy of the given port
   */
  // @ts-ignore ... because the return type doesn't match
  copyPort(node: INode, port: IPort): IPort {
    /* ... */
  }

  /**
   * Create a label on the given node which is a copy of the given label
   */
  // @ts-ignore ... because the return type doesn't match
  copyLabel(node: INode, label: ILabel): ILabel {
    /* ... */
  }
}

The example Creating nodes for a page graph shows how to create a node based on a node in the page (layout) graph. Some of the nodes might be a representation of a node in the original graph. Such nodes get the original node’s style, tag, but also labels and ports. Other nodes might get a style based on its type or a custom label, which, e.g., might show to which node that node is linked to.

Creating nodes for a page graph
/**
 * Create an INode for the given page node
 * @param {!IGraph} graph
 * @param {!LayoutGraph} page
 * @param {!YNode} pageNode
 * @param {!INodeInfo} nodeInfo
 * @returns {!INode}
 */
createNode(graph, page, pageNode, nodeInfo) {
  // representedNode is the node in the original graph which is represented by pageNode
  // it might be null if the pageNode is a dummy or proxy
  const originalLayoutGraph = nodeInfo.representedNode.graph
  const representedNode = originalLayoutGraph.getOriginalNode(nodeInfo.representedNode)

  const style = representedNode !== null ? representedNode.style.clone() : this.getDefaultNodeStyle(nodeInfo.type)

  // create the copied node with the layout assigned by MultiPageLayout
  const nodeLayout = page.getLayout(pageNode)
  const node = graph.createNode(
    new Rect(nodeLayout.x, nodeLayout.y, nodeLayout.width, nodeLayout.height),
    style,
    representedNode !== null ? representedNode.tag : null
  )

  switch (nodeInfo.type) {
    // ... type specific modifications
    case NodeType.NORMAL:
      // ...
      break
  }

  // copy the appearance of the represented node
  if (representedNode !== null) {
    for (const label of representedNode.labels) {
      this.copyLabel(node, label)
    }
    for (const port of representedNode.ports) {
      const copiedPort = this.copyPort(node, port)
      this.modelPortToIPort.set(port, copiedPort)
    }
  }

  return node
}

/**
 * Create an INode for the given page node
 */
createNode(graph: IGraph, page: LayoutGraph, pageNode: YNode, nodeInfo: INodeInfo): INode {
  // representedNode is the node in the original graph which is represented by pageNode
  // it might be null if the pageNode is a dummy or proxy
  const originalLayoutGraph = nodeInfo.representedNode!.graph as CopiedLayoutGraph
  const representedNode = originalLayoutGraph.getOriginalNode(nodeInfo.representedNode!)

  const style =
    representedNode !== null ? (representedNode as INode).style.clone() : this.getDefaultNodeStyle(nodeInfo.type)

  // create the copied node with the layout assigned by MultiPageLayout
  const nodeLayout = page.getLayout(pageNode)
  const node = graph.createNode(
    new Rect(nodeLayout.x, nodeLayout.y, nodeLayout.width, nodeLayout.height),
    style,
    representedNode !== null ? (representedNode as INode).tag : null
  )

  switch (nodeInfo.type) {
    // ... type specific modifications
    case NodeType.NORMAL:
      // ...
      break
  }

  // copy the appearance of the represented node
  if (representedNode !== null) {
    for (const label of (representedNode as INode).labels) {
      this.copyLabel(node, label)
    }
    for (const port of (representedNode as INode).ports) {
      const copiedPort = this.copyPort(node, port)
      this.modelPortToIPort.set(port, copiedPort)
    }
  }

  return node
}

Creating edges is quite similar, as shown in the example Creating edges for a page graph.

Creating edges for a page graph
/**
 * Create an IEdge for the given edge info
 * @param {!LayoutGraph} page
 * @param {!IGraph} graph
 * @param {!Edge} pageEdge
 * @param {!IEdgeInfo} edgeInfo
 * @returns {!IEdge}
 */
createEdge(page, graph, pageEdge, edgeInfo) {
  // representedEdge is the edge in the original graph which is represented by pageEdge
  // it might be null if the pageNode is a dummy or proxy
  const originalLayoutGraph = edgeInfo.representedEdge.graph
  const representedEdge = originalLayoutGraph.getOriginalEdge(edgeInfo.representedEdge)

  const style = representedEdge !== null ? representedEdge.style.clone() : this.getDefaultEdgeStyle(edgeInfo.type)

  let edge
  if (representedEdge !== null) {
    // if the edge has a model edge: create the copied edge between
    // the copies of its source and target ports
    const viewSourcePort = this.modelPortToIPort.get(representedEdge.sourcePort)
    const viewTargetPort = this.modelPortToIPort.get(representedEdge.targetPort)
    edge = graph.createEdge(viewSourcePort, viewTargetPort, style, representedEdge.tag)
  } else {
    // otherwise create it between the copies of its source and target nodes
    const viewSource = this.pageNodeToINode.get(pageEdge.source)
    const viewTarget = this.pageNodeToINode.get(pageEdge.target)
    edge = graph.createEdge(viewSource, viewTarget)
  }

  // adjust the port location
  const newSourcePortLocation = page.getSourcePointAbs(pageEdge)
  const newTargetPortLocation = page.getTargetPointAbs(pageEdge)
  graph.setPortLocation(edge.sourcePort, newSourcePortLocation.toPoint())
  graph.setPortLocation(edge.targetPort, newTargetPortLocation.toPoint())

  // and copy the bends
  const edgeLayout = page.getLayout(pageEdge)
  for (let i = 0; i < edgeLayout.pointCount(); i++) {
    const bendLocation = edgeLayout.getPoint(i)
    graph.addBend(edge, new Point(bendLocation.x, bendLocation.y), i)
  }

  // also copy the labels (not shown here)

  switch (edgeInfo.type) {
    // ... type specific modifications
    case EdgeType.NORMAL:
      // ...
      break
  }

  return edge
}

/**
 * Create an IEdge for the given edge info
 */
createEdge(page: LayoutGraph, graph: IGraph, pageEdge: Edge, edgeInfo: IEdgeInfo): IEdge {
  // representedEdge is the edge in the original graph which is represented by pageEdge
  // it might be null if the pageNode is a dummy or proxy
  const originalLayoutGraph = edgeInfo.representedEdge!.graph as CopiedLayoutGraph
  const representedEdge = originalLayoutGraph.getOriginalEdge(edgeInfo.representedEdge!)

  const style =
    representedEdge !== null ? (representedEdge as IEdge).style.clone() : this.getDefaultEdgeStyle(edgeInfo.type)

  let edge: IEdge
  if (representedEdge !== null) {
    // if the edge has a model edge: create the copied edge between
    // the copies of its source and target ports
    const viewSourcePort = this.modelPortToIPort.get((representedEdge as IEdge).sourcePort)!
    const viewTargetPort = this.modelPortToIPort.get((representedEdge as IEdge).targetPort)!
    edge = graph.createEdge(viewSourcePort, viewTargetPort, style, (representedEdge as IEdge).tag)
  } else {
    // otherwise create it between the copies of its source and target nodes
    const viewSource = this.pageNodeToINode.get(pageEdge.source)!
    const viewTarget = this.pageNodeToINode.get(pageEdge.target)!
    edge = graph.createEdge(viewSource, viewTarget)
  }

  // adjust the port location
  const newSourcePortLocation = page.getSourcePointAbs(pageEdge)
  const newTargetPortLocation = page.getTargetPointAbs(pageEdge)
  graph.setPortLocation(edge.sourcePort!, newSourcePortLocation.toPoint())
  graph.setPortLocation(edge.targetPort!, newTargetPortLocation.toPoint())

  // and copy the bends
  const edgeLayout = page.getLayout(pageEdge)
  for (let i = 0; i < edgeLayout.pointCount(); i++) {
    const bendLocation = edgeLayout.getPoint(i)
    graph.addBend(edge, new Point(bendLocation.x, bendLocation.y), i)
  }

  // also copy the labels (not shown here)

  switch (edgeInfo.type) {
    // ... type specific modifications
    case EdgeType.NORMAL:
      // ...
      break
  }

  return edge
}

Note that the layout, i.e. the node’s bounds and the edge’s source and target port locations as well as the bends are copied from the page (layout) graph. The sample application Multi-Page Layout demo shows a much more elaborate version of the example MultiPageGraphBuilder.

Tutorial Demo Code

The tutorial demo application Multi-Page Layout demo shows how to use class MultiPageLayout to sub-divide large graphs into smaller bits of navigable information.

Supplemental Layout Data

When using MultiPageLayout, supplemental layout data for a graph’s elements can be specified either by using class MultiPageLayoutData or by registering data providers with the graph using given look-up keys. The table Supplemental Layout Data lists all properties of MultiPageLayoutData and the corresponding look-up keys that MultiPageLayout tests during the layout process in order to query supplemental data.

Providing supplemental layout data is described in detail in Layout Data.

Supplemental Layout Data
MultiPageLayoutData PropertyData Provider KeyElement TypeValue TypeDescription
nodeIdsNODE_ID_DP_KEYnodeobjectFor each node an object that is used as a unique ID.
edgeIdsEDGE_ID_DP_KEYedgeobjectFor each edge an object that is used as a unique ID.
nodeLabelIdsNODE_LABEL_ID_DP_KEYINodeLabelLayoutobjectFor each INodeLabelLayout instance an Object that is used as a unique ID.
edgeLabelIdsEDGE_LABEL_ID_DP_KEYIEdgeLabelLayoutobjectFor each IEdgeLabelLayout instance an object that is used as a unique ID.
nodeClusterIdsNODE_CLUSTER_ID_DP_KEYnodeobjectFor each node an arbitrary object indicating its cluster ID. This ID is used to find nodes that preferably should be in the same page graph.
edgeTypesEDGE_TYPE_DP_KEYedgeobjectFor each edge an arbitrary object indicating its (application-specific) type.
abortHandlerABORT_HANDLER_DP_KEYgraphAbortHandlerAn AbortHandler instance that will be queried by the layout algorithm to determine whether layout calculation shall be terminated.