1 --=========================================================================*\
3 -- Graph support for Lua.
5 -- 30-7-2006, 30-12-2016, 01/2017
7 -- Lua module "graph.lua"
8 --=========================================================================*\
9 local graph
= require
"graph.core"
10 local string = require
"string"
11 local table = require
"table"
13 local type, getmetatable
, pairs
, ipairs
, assert =
14 _G
.type, _G
.getmetatable
, _G
.pairs
, _G
.ipairs
, _G
.assert
16 module("graph", package
.seeall
)
18 -- Overloaded graph.open(), graph.read()
23 --==============================================================================
25 --==============================================================================
39 {format = "canon", descr
= "DOT pretty"},
40 {format = "dot", descr
= "DOT"},
41 {format = "xdot", descr
= "extended DOT"},
42 {format = "cmap", descr
= "Client-side imagemap (deprecated)"},
43 {format = "dia", descr
= "Dia format"},
44 {format = "eps", descr
= "Encapsulated PostScript"},
45 {format = "fig", descr
= "FIG"},
46 {format = "gd", descr
= "Graphics Draw format"},
47 {format = "gd2", descr
= "Graphics Draw 2 format"},
48 {format = "gif", descr
= "Graphics Interchage Format"},
49 {format = "gtk", descr
= "Antialiased image using a GTK 2.0 canvas"},
50 {format = "hpgl", descr
= "Hewlett Packard Graphics Language HP-GL/2"},
51 {format = "ico", descr
= "Icon Image File Format"},
52 {format = "imap", descr
= "Server-side and client-side imagemaps"},
53 {format = "imap_np", descr
= "Server-side and client-side imagemaps"},
54 {format = "cmapx", descr
= "Server-side and client-side imagemaps"},
55 {format = "cmapx_np", descr
= "Server-side and client-side imagemaps"},
56 {format = "ismap", descr
= "Server-side imagemap (deprecated)"},
57 {format = "jpg", descr
= "JPEG (deprecated - 8 May 2006 - will no longer be supported)"},
58 {format = "jpeg", descr
= "JPEG (deprecated - 8 May 2006 - will no longer be supported)"},
59 {format = "mif", descr
= "FrameMaker MIF format"},
60 {format = "mp", descr
= "MetaPost"},
61 {format = "pcl", descr
= "Hewlett Packard Printer Control"},
62 {format = "pdf", descr
= "Portable Document Format (PDF)"},
63 {format = "pic", descr
= "Autodesk PIC"},
64 {format = "plain", descr
= "Simple text format"},
65 {format = "plain-ext", descr
= "Extended text format"},
66 {format = "png", descr
= "Portable Network Graphics format"},
67 {format = "ps", descr
= "PostScript"},
68 {format = "ps2", descr
= "PostScript for PDF"},
69 {format = "svg", descr
= "Scalable Vector Graphics"},
70 {format = "svgz", descr
= "Scalable Vector Graphics"},
71 {format = "vml", descr
= "Vector Markup Language (VML)"},
72 {format = "vmlz", descr
= "Vector Markup Language (VML) - compressed"},
73 {format = "vrml", descr
= "VRML"},
74 {format = "vtx", descr
="Visual Thought format"},
75 {format = "wbmp", descr
= "Wireless BitMap format"},
76 {format = "xlib", descr
= "Xlib canvas"},
78 --==============================================================================
80 --==============================================================================
82 --------------------------------------------------------------------------------
83 -- Iterator over non-numeric indices of a table
84 --------------------------------------------------------------------------------
85 local function npairs(t
)
86 return function(t
, prev
)
88 while type(k
) == "number" do
96 --------------------------------------------------------------------------------
97 -- Copy attributes from parameter table
98 --------------------------------------------------------------------------------
99 local function attribs(params
)
101 for k
,v
in npairs(params
) do
107 --------------------------------------------------------------------------------
108 -- Get default attributes for the given object (type)
109 --------------------------------------------------------------------------------
110 local function getattrib(self
)
112 local defined
= self
.graph
:defaults()[self
:type()]
113 for k
,v
in pairs(defined
) do
114 t
[k
] = self
:rawget(k
)
119 --------------------------------------------------------------------------------
120 -- Add a method to an object (node, graph, edge)
121 --------------------------------------------------------------------------------
122 local function addmethod(self
, name
, func
)
123 local mt
= getmetatable(self
)
124 if not mt
or mt
[name
] then return end
128 -- A forward declaration
131 --==============================================================================
132 -- Advanced implementations
133 --==============================================================================
134 --------------------------------------------------------------------------------
136 -- sg = g:subgraph{"name", ATTR, ..., ATTR, nocreate}
137 -- sg = g:subgraph("name", {ATTR, ..., ATTR}, nocreate)
138 --------------------------------------------------------------------------------
139 local function _subgraph(self
, ...)
143 if type(arg
[1]) == "table" then
146 for k
,v
in npairs(arg
[1]) do
147 if type(v
) == "table" then
153 elseif type(arg
[1]) == "string" then
158 error("missing subgraph name")
160 local g
, err
= self
:__subgraph(name
)
164 for k
,v
in pairs(attr
) do
165 if type(v
) == "table" then
174 --------------------------------------------------------------------------------
176 -- c = g:cluster{"name", ATTR, ..., ATTR, nocreate}
177 -- c = g:cluster("name", {ATTR, ..., ATTR}, nocreate}
178 --------------------------------------------------------------------------------
179 local function _cluster(self
, ...)
181 if type(arg
[1]) == "table" then
182 arg
[1][1] = "cluster_"..arg
[1][1]
183 elseif type(arg
[1]) == "string" then
184 arg
[1] = "cluster_"..arg
[1]
186 error("missing cluster name")
188 return _subgraph(self
, unpack(arg
))
191 --------------------------------------------------------------------------------
193 -- e = g:edge{node, .., "node", ATTR, ..., ATTR}
194 -- with ATTR: key = value
195 -- node: object or node name
196 -- e = g:edge(tail, head, "label", nocreate)
197 -- e = g:edge("tail", "head", "label", nocreate)
198 --------------------------------------------------------------------------------
199 local function _edge(self
, ...)
201 local nodes
, edges
= {}, {}
205 if type(arg
[1]) == "table" then
206 attr
= attribs(arg
[1])
208 for i
, v
in ipairs(arg
[1]) do
209 -- we must care about ports here:
211 if type(v
) == "string" then
212 string.gsub(v
, "^(%w+):*(%w*):*(%w*)", function(u
, v
, w
)
217 node
.node
= self
:__node(node
.name
)
218 elseif type(v
) == "userdata" then
221 error("wrong node type")
223 table.insert(nodes
, node
.node
)
225 -- Create edges and set attributes to each edge
226 local e
, tail_or_err
, head
= self
:__edge(last
.node
, node
.node
)
230 if last
.port
then e
.tailport
= last
.port
end
231 if node
.port
then e
.headport
= node
.port
end
232 addmethod(e
, "getattrib", getattrib
)
233 for k
,v
in pairs(attr
) do
236 table.insert(edges
, e
)
241 elseif type(arg
[1]) == "string" or type(arg
[1]) == "userdata" then
242 local node
= {[1]={},[2]={}}
245 -- we must care about ports here:
246 if type(v
) == "string" then
247 string.gsub(v
, "^(%w+):*(%w*):*(%w*)", function(u
, v
, w
)
252 arg
[i
] = self
:__node(node
[i
].name
)
255 local e
, tail_or_err
, head
= self
:__edge(unpack(arg
))
256 if not e
then return e
, tail_or_err
end
257 if node
[1].port
then e
.tailport
=node
[1].port
end
258 if node
[2].port
then e
.headport
=node
[2].port
end
259 addmethod(e
, "getattrib", getattrib
)
260 return e
, tail_or_err
, head
262 error("invalid edge declaration")
266 --------------------------------------------------------------------------------
268 -- n = g:node{"name", ATTR, ..., ATTR, nocreate}
269 -- n = g:node("name", {ATTR, ..., ATTR}, nocreate)
270 -- with ATTR: key = value
271 --------------------------------------------------------------------------------
272 local function _node(self
, ...)
275 if type(arg
[1]) == "table" then
278 attr
= attribs(arg
[1])
279 elseif type(arg
[1]) == "string" then
284 error("missing node name")
286 local n
= self
:__node(name
, nocreate
)
287 for k
,v
in pairs(attr
) do
290 addmethod(n
, "getattrib", getattrib
)
294 --------------------------------------------------------------------------------
295 -- Returns a function that creates horizontal oriented fields
296 -- gr.hbox{"field", gr.vbox{...}, "field", gr.vbox{...}}
297 --------------------------------------------------------------------------------
301 for k
, v
in ipairs(t
) do
302 if type(v
) == "function" then
303 ss
= ss
.. v("h") .. "|"
304 elseif type(v
) == "string" then
308 ss
= string.sub(ss
, 1, -2)
309 if dir
~= "h" then ss
= "{" .. ss
.. "}" end
314 --------------------------------------------------------------------------------
315 -- Returns a function that creates vertical oriented fields
316 -- gr.vbox{"field", gr.hbox{...}, "field", gr.hbox{...}}
317 --------------------------------------------------------------------------------
321 for k
, v
in ipairs(t
) do
322 if type(v
) == "function" then
323 ss
= ss
.. v("v") .. "|"
324 elseif type(v
) == "string" then
328 ss
= string.sub(ss
, 1, -2)
329 if dir
~= "v" then ss
= "{" .. ss
.. "}" end
334 --------------------------------------------------------------------------------
336 -- n = g:record{"n1", vbox{...}, ATTR, ...,ATTR, nocreate}
337 -- n = g:record("n1", vbox{"name", hbox{...}, ...}, {ATTR, ..., ATTR}, nocreate)
338 --------------------------------------------------------------------------------
339 local function _record(self
, ...)
340 local name
, attr
, labelfunc
342 if type(arg
[1]) == "table" then
346 attr
= attribs(arg
[1])
347 elseif type(arg
[1]) == "string" then
353 error("missing record name")
355 assert(type(lfunc
) == "function", "missing record struct")
356 local label
= lfunc("h")
357 local n
= self
:__node(name
, nocreate
)
360 for k
,v
in pairs(attr
) do
363 addmethod(n
, "getattrib", getattrib
)
367 --==============================================================================
368 -- Additional graph methods
369 --==============================================================================
370 --------------------------------------------------------------------------------
372 --------------------------------------------------------------------------------
373 local function findnode(self
, name
)
375 return nil, "not found"
377 return self
:__node(name
, true)
380 --------------------------------------------------------------------------------
382 --------------------------------------------------------------------------------
383 local function findedge(self
, tail
, head
, label
)
384 return self
:_findedge(tail
, head
, label
)
387 --------------------------------------------------------------------------------
389 --------------------------------------------------------------------------------
390 local function findsubgraph(self
, tail
, head
)
391 return self
:_subgraph(tail
, head
, false)
394 --------------------------------------------------------------------------------
395 -- Show graph with dotty
396 --------------------------------------------------------------------------------
397 local function showdotty(self
, doit
)
400 local fn
= os
.tmpname()..".dot"
401 local rv
= self
:write(fn
)
402 print("result: "..rv
)
403 rv
= os
.execute("dotty "..fn
)
409 --------------------------------------------------------------------------------
410 -- Show graph with dotty
411 --------------------------------------------------------------------------------
412 local function show(self
, printdot
)
413 local printdot
= printdot
or false
415 if printdot
== true then
416 self
.render(nil, "dot", "out.dot")
422 return io
.popen('dot -V 2>&1 | cut -d " " -f 5'):read()
424 --------------------------------------------------------------------------------
425 -- Partially overload C-defined metamethods
426 -- Note: keep this function behind the overloading ones
427 --------------------------------------------------------------------------------
428 local function overload(g
)
429 local mt
= getmetatable(g
)
430 if mt
.overloaded
== true then return end
433 mt
.__subgraph
= mt
.subgraph
436 mt
.subgraph
= _subgraph
437 mt
.cluster
= _cluster
439 mt
._findedge
= mt
.findedge
440 mt
.findedge
= findedge
441 mt
.findnode
= findnode
442 mt
.findsubgraph
= findsubgraph
443 mt
.showdotty
= showdotty
448 -- resolve forward declaration
451 --------------------------------------------------------------------------------
452 -- Advanced implementation of graph.open() - Main entry
453 --------------------------------------------------------------------------------
459 if type(arg
[1]) == "string" then
460 -- Syntax 1: graph.open("NAME","directed", {graph={ATTR,..}, edge={ATTR,..}, node={ATTR,..}})
465 elseif type(arg
[1]) == "table" then
466 -- Syntax 2: graph.open{"NAME", "kind", ATTR, ATTR}
469 for k
,v
in npairs(arg
[1]) do
470 if type(v
) == "table" then
478 -- Create the graph and declare attributes
479 g
= _open(name
, kind
)
489 --------------------------------------------------------------------------------
490 -- Advanced implementation of graph.read(file, doscan) - Main entry
491 --------------------------------------------------------------------------------
492 local function getedge(n
, elist
)
493 for e
in n
:walkedges() do
495 table.insert(elist
, e
)
500 local function getnode(g
, nlist
, elist
)
501 for n
in g
:walknodes() do
505 table.insert(nlist
, n
)
509 local function getsubg(g
, glist
)
510 for sg
in g
:walk() do
511 local t
= {sg
, graph
= {}, node
= {}, edge
= {}}
512 table.insert(glist
, t
)
514 getnode(sg
, t
.node
, t
.edge
)
518 function read(fname
, doscan
)
519 local g
, err
= _read(fname
)
520 if not g
then return g
, err
end
522 if doscan
== true then
530 getnode(g
, t
.node
, t
.edge
)
537 --==============================================================================
538 -- Utilities to create a graph as Lua table.
539 -- Each of the following functions returns a constructor function for
540 -- the corresponding object.
541 --==============================================================================
543 --------------------------------------------------------------------------------
544 -- Create a directed graph.
545 --------------------------------------------------------------------------------
546 local function _graph(t
, typ
)
547 local name
= t
.name
or t
[1]
548 if type(t
[1]) == "string" then
551 assert(name
, "missing name")
552 local g
= open(name
, typ
)
553 for k
, v
in npairs(t
) do
554 if type(v
) == "table" then
556 elseif type(v
) == "string" then
559 error("invalid attribute type")
562 for k
,v
in ipairs(t
) do
563 if type(v
) == "function" then
566 error("invalid graph attribute")
573 return _graph(t
, "directed")
576 function strictdigraph(t
)
577 return _graph(t
, "strictdirected")
580 function graph
.graph(t
)
581 return _graph(t
, "undirected")
584 function strictgraph(t
)
585 return _graph(t
, "strict")
588 --------------------------------------------------------------------------------
590 --------------------------------------------------------------------------------
593 assert(type(t
[1]) == "string", "missing subgraph name")
594 local sg
= g
:subgraph(t
[1])
596 for k
, v
in npairs(t
) do
597 if type(v
) == "string" then
600 error("invalid attribute type")
603 for k
,v
in ipairs(t
) do
604 if type(v
) == "function" then
607 error("invalid attribute type")
614 --------------------------------------------------------------------------------
616 --------------------------------------------------------------------------------
618 assert(type(t
[1]) == "string", "missing cluster name")
619 t
[1] = "cluster_"..t
[1]
623 --------------------------------------------------------------------------------
625 --------------------------------------------------------------------------------
632 --------------------------------------------------------------------------------
634 --------------------------------------------------------------------------------
640 --------------------------------------------------------------------------------
642 --------------------------------------------------------------------------------
646 for k
,v
in pairs(t
) do
647 if type(v
) == "function" then
648 table.insert(p
, v(g
))
649 elseif type(v
) == "string" or type(v
) == "userdata" then
652 error("invalid parameter")