PGF/TikZ Manual

The TikZ and PGF Packages
Manual for version 3.1.10

Graph Drawing

39The Binding Layer

39.1Overview

This section explains how the binding of the graph drawing system to a particular display layer works. Let me stress that all of this is important only for readers who

  • • either wish to write new display system (see Section 38)

  • • or wish to know more about how the graph drawing system works on the pure pgf layer (this is were the binding occurs).

Bindings are used to encapsulate the details of the communication between the graph drawing system and a display system (see Section 38 for an introduction to display systems).

Consider a display system that communicates with the graph drawing system. At some point, the display system would like to run an algorithm to lay out a graph. To achieve this, it will call different functions from the class InterfaceToDisplay and the effect of this is that a representation of the to-be-drawn graph is constructed internally and that the appropriate algorithms are run. All of this is in some sense independent of the actual display system, the class InterfaceToDisplay offers the same standard interface to all display systems.

At some point, however, the graph drawing system may need to “talk back” to the display system. For instance, once the graph has been laid out, to trigger the actual rendering of the graph, the graph drawing system must “tell” the display layer where the vertices lie. For some display systems this is easy: if the display system itself is written in Lua, it could just access the syntactic digraph directly. However, for systems like TikZ or systems written in another language, the graph drawing system needs a set of functions that it can call that will tell the display system what is going on. This is were bindings come in.

The class Binding is an interface that defines numerous methods that will be called by the graph drawing system in different situations (see the documentation below for details). For instance, there is a function renderVertex that is called by the graph drawing system whenever a vertex should be rendered by the display system. The class is really just an interface in the sense of object-oriented programming. For each display system you need to create a subclass of Binding like BindingToPGF or BindingToASCII that implement the methods declared by Binding. The number of methods that need to be implemented depends on the display system.

In the following, you will find the documentation of the Binding class in Section 39.2. Following this, we first have a quick look at how the BindingToPGF works and then go over a simple example of a binding to a more or less imaginary display system. This example should help readers interested in implementing their own bindings.

39.2The Binding Class and the Interface Core

  • function Binding:declareCallback(t)

  • Declare a new key. This callback is called by declare. It is the job of the display layer to make the parameter t.key available to the parsing process. Furthermore, if t.initial is not nil, the display layer must convert it into a value that is stored as the initial value and call InterfaceToDisplay.setOptionInitial.

    Parameters: 1. t See InterfaceToAlgorithms.declare for details.

  • function Binding:renderStart()

  • This function and, later on, renderStop are called whenever the rendering of a laid-out graph starts or stops. See InterfaceToDisplay.render for details.

  • function Binding:renderStop()

  • See renderStart.

  • function Binding:renderCollectionStartKind(kind, layer)

  • This function and the corresponding ...Stop... functions are called whenever a collection kind should be rendered. See InterfaceToDisplay.render_collections for details.

    Parameters: 1. kind The kind (a string). 2. layer The kind’s layer (a number).

  • function Binding:renderCollectionStopKind(kind, layer)

  • The counterpart to renderCollectionStartKind.

    Parameters: 1. kind The kind. 2. layer The kind’s layer.

  • function Binding:renderCollection(collection)

  • Renders a single collection, see renderCollectionStartKind for details.

    Parameters: 1. collection The collection object.

  • function Binding:renderVerticesStart()

  • This function and the corresponding ...Stop... functions are called whenever a vertex should be rendered. See InterfaceToDisplay.render_vertices for details.

  • function Binding:renderVerticesStop()

  • The counterpart to renderVerticesStop.

  • function Binding:renderVertex(vertex)

  • Renders a single vertex, see renderVertexStartKind for details.

    Parameters: 1. vertex The Vertex object.

  • function Binding:everyVertexCreation(v)

  • This method is called by the interface to the display layer after the display layer has called createVertex to create a new vertex. After having done its internal bookkeeping, the interface calls this function to allow the binding to perform further bookkeeping on the node. Typically, this will be done using the information stored in Binding.infos.

    Parameters: 1. v The vertex.

  • function Binding:renderEdgesStart()

  • This function and the corresponding ...Stop... functions are called whenever an edge should be rendered. See InterfaceToDisplay.render_edges for details.

  • function Binding:renderEdgesStop()

  • The counterpart to renderEdgesStop.

  • function Binding:renderEdge(edge)

  • Renders a single vertex, see renderEdgeStartKind for details.

    Parameters: 1. edge The Edge object.

  • function Binding:everyEdgeCreation(e)

  • Like everyVertexCreation, only for edges.

    Parameters: 1. e The edge.

  • function Binding:createVertex(init)

  • Generate a new vertex. This method will be called when the algorithm layer wishes to trigger the creation of a new vertex. This call will be made while an algorithm is running. It is now the job of the binding to cause the display layer to create the node. This is done by calling the yield method of the scope’s coroutine.

    Parameters: 1. init A table of initial values for the node. The following fields will be used:

      name If present, this name will be given to the node. If not present, an internal name is generated. Note that, unless the node is a subgraph node, this name may not be the name of an already present node of the graph; in this case an error results. • shape If present, a shape of the node. • generated_options A table that is passed back to the display layer as a list of key–value pairs. • text The text of the node, to be passed back to the higher layer. This is what should be displayed as the node’s text.

  • Lua table InterfaceCore (declared in pgf.gd.interface.InterfaceCore)

  • This class provides the core functionality of the interface between all the different layers (display layer, binding layer, and algorithm layer). The two classes InterfaceToAlgorithms and InterfaceToDisplay use, in particular, the data structures provided by this class.

    • Field binding

    • This field stores the “binding”. The graph drawing system is “bound” to the display layer through such a binding (a subclass of Binding). Such a binding can be thought of as a “driver” in operating systems terminology: It is a small set of functions needed to adapt the functionality to one specific display system. Note that the whole graph drawing scope is bound to exactly one display layer; to use several bindings you need to setup a completely new Lua instance.

    • Field scopes

    • This is a stack of graph drawing scopes. All interface methods refer to the top of this stack.

    • Field collection_kinds

    • This table stores which collection kinds have been defined together with their properties.

    • Field algorithm_classes

    • A table that maps algorithm keys (like tree layout to class objects).

    • Field keys

    • A lookup table of all declared keys. Each entry of this table consists of the original entry passed to the declare method. Each of these tables is both index at a number (so you can iterate over it using ipairs) and also via the key’s name.

    Alphabetical method summary:

    function InterfaceCore.convert (s,t)

    function InterfaceCore.topScope ()

  • function InterfaceCore.topScope()

  • Returns the top scope

    Returns: 1. The current top scope, which is the scope in which everything should happen right now.

  • function InterfaceCore.convert(s,t)

  • Converts parameters types. This method is used by both the algorithm layer as well as the display layer to convert strings into the different types of parameters. When a parameter is pushed onto the option stack, you can either provide a value of the parameter’s type; but you can also provide a string. This string can then be converted by this function to a value of the correct type.

    Parameters: 1. s A parameter value or a string. 2. t The type of the parameter

    Returns: 1. If s is not a string, it is just returned. If it is a string, it is converted to the type t.

39.3The Binding To PGF
  • Lua table BindingToPGF (declared in pgf.gd.bindings.BindingToPGF)

  • This class, which is a subclass of Binding, binds the graph drawing system to the pgf display system by overriding (that is, implementing) the methods of the Binding class. As a typical example, consider the implementation of the function renderVertex:


    function BindingToPGF:renderVertex(v)
    local info = assert(self.infos[v], "thou shalt not modify the syntactic digraph")
    tex.print(
    string.format(
    "\\pgfgdcallbackrendernode{%s}{%fpt}{%fpt}{%fpt}{%fpt}{%fpt}{%fpt}{%s}",
    'not yet positionedPGFINTERNAL' .. v.name,
    info.x_min,
    info.x_max,
    info.y_min,
    info.y_max,
    v.pos.x,
    v.pos.y,
    info.box_count))
    end

    As can be seen, the main job of this function is to call a function on the layer that is called \pgfgdcallbackrendernode, which gets several parameters like the name of the to-be-rendered node or the (new) position for the node. For almost all methods of the Binding class there is a corresponding “callback” macro on the layer, all of which are implemented in the pgf library graphdrawing. For details on these callbacks, please consult the code of that file and of the class BindingToPGF (they are not documented here since they are local to the binding and should not be called by anyone other than the binding class).

39.4An Example Binding Class

In the present section a complete binding is presented to an imaginary “ascii art display system” is presented. The idea is that this display system will depict graphs using just normal letters and spaces so that, when the text is typeset in a monospace font, a visualization of the graph results. For instance:

Graph rendered by BindingToPGF:

(-tikz- diagram)

Graph rendered by BindingToASCII:

 Alice
                   .......
                 .. . . .
            ... . . .
         ... .. . ..
      .. . . .
 Charly Bob . .
     .. . . .
        . . . .
         . . . .
          .. . . .
            .. . .
           Dave George .
               .. . ... .
                  . . .. .
                   . . ...
                    .. . . ...
                      .. . ..
                      Eve . ..
                         .. . ..
                            . . .
                             . . .
                               .. . ..
                                 ...
                                Fritz

The binding will reside in a file BindingToASCII.lua, whose contents is detailed below, and which is used by calling the bind function of InterfaceToDisplay, see its documentation for details.

The binding’s code starts with some initializations:


-- File BindingToASCII.lua

-- Imports
local lib = require "pgf.gd.lib"

-- Subclass the Binding class:
local BindingToASCII = lib.class { base_class = require "pgf.gd.bindings.Binding" }

The interesting code is the code for “rendering” a graph. The graph drawing system will invoke the binding’s methods renderStart and renderStop to signal that the graph drawing algorithms have finished and that the vertices and edges can now be drawn.

In our ascii renderer, we use a two-dimensional field holding characters that severs as the “drawing canvas”. At the beginning of the rendering, we initialize it with blanks:


local canvas

function BindingToASCII:renderStart()
canvas = {}
-- Clear the canvas
for x=-30,30 do
canvas [x] = {}
for y=-30,30 do
canvas[x][y] = ' '
end
end
end

In order to “render” a vertex, the graph drawing system will call the renderVertex method. The binding of TikZ does a lot of complicated things in this method to retrieve the underlying node’s box from internal table and to somehow reinstall the box in ’s output stream; for our ascii binding things are much simpler: We simply put the vertex’s name at the canvas position corresponding to the vertex’s pos coordinate. Note that this simple version of an ascii renderer does not try to scale things; thus, array out of bounds might occur here.


function BindingToASCII:renderVertex(v)
canvas [math.floor(v.pos.x)][math.floor(v.pos.y)] = v.name
end

The rendering of edges is a more complicated process. Given two vertices, we put dots at the canvas positions between them; provided there are no vertices (so edges are behind the nodes). Here is the essential part of the code (for the complete code, have a look at pgf/gd/examples/BindingToASCII.lua):


function BindingToASCII:renderEdge(e)
local function connect (p,q)
-- Connect the points p and q
local x1, y1, x2, y2 = math.floor(p.x+0.5), math.floor(p.y+0.5), math.floor(q.x+0.5), math.floor(q.y+0.5)
...
local delta_x = x2-x1
local delta_y = y2-y1
...
local slope = delta_y/delta_x
for i=x1,x2 do
local x,y = i, math.floor(y1 + (i-x1)*slope + 0.5)

if canvas[x][y] == " " then
canvas[x][y] = '.'
end
end
...
end

-- Iterate over all points on the path from tail to head:
local p = e.tail.pos
for i=1,#e.path do
connect(p, e.tail.pos + e.path[i])
p = e.tail.pos + e.path[i]
end
connect(p, e.head.pos)
end

The methods renderVertex and renderEdge will be called once for each vertex and edge of the to-be-rendered graph. At the end, the renderStop method is called. In our case, this method will output the canvas using print. A slight complication arises when node names are longer than just one character. In this case, the following code “centers” them on their coordinate and makes sure that they do not get overwritten by the dots forming edges:


function BindingToASCII:renderStop()
for y=10,-30,-1 do
local t = {}
for x=-30,30 do
local s = canvas[x][y]
for i=1,#s do
pos = x+30+i-math.floor(#s/2)
if not t[pos] or t[pos] == " " or t[pos] == "." then
t[pos] = string.sub(s,i,i)
end
end
end
print(table.concat(t))
end
end

At the end, we need to return the created object:


return BindingToASCII