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
)
401 print("SYSTEM:", self
._SYSTEM
)
402 if graph
._SYSTEM
== "Win32" then
405 fn
= os
.tmpname()..".dot"
407 local rv
= self
:write(fn
)
408 print("result: "..rv
)
409 if graph
._SYSTEM
== "Win32" then
410 rv
= os
.execute("gvedit "..fn
)
412 rv
= os
.execute("dotty "..fn
)
419 --------------------------------------------------------------------------------
420 -- Show graph with dotty
421 --------------------------------------------------------------------------------
422 local function show(self
, printdot
)
423 local printdot
= printdot
or false
425 if printdot
== true then
426 self
.render(nil, "dot", "out.dot")
432 return io
.popen('dot -V 2>&1 | cut -d " " -f 5'):read()
434 --------------------------------------------------------------------------------
435 -- Partially overload C-defined metamethods
436 -- Note: keep this function behind the overloading ones
437 --------------------------------------------------------------------------------
438 local function overload(g
)
439 local mt
= getmetatable(g
)
440 if mt
.overloaded
== true then return end
443 mt
.__subgraph
= mt
.subgraph
446 mt
.subgraph
= _subgraph
447 mt
.cluster
= _cluster
449 mt
._findedge
= mt
.findedge
450 mt
.findedge
= findedge
451 mt
.findnode
= findnode
452 mt
.findsubgraph
= findsubgraph
453 mt
.showdotty
= showdotty
458 -- resolve forward declaration
461 --------------------------------------------------------------------------------
462 -- Advanced implementation of graph.open() - Main entry
463 --------------------------------------------------------------------------------
469 if type(arg
[1]) == "string" then
470 -- Syntax 1: graph.open("NAME","directed", {graph={ATTR,..}, edge={ATTR,..}, node={ATTR,..}})
475 elseif type(arg
[1]) == "table" then
476 -- Syntax 2: graph.open{"NAME", "kind", ATTR, ATTR}
479 for k
,v
in npairs(arg
[1]) do
480 if type(v
) == "table" then
488 -- Create the graph and declare attributes
489 g
= _open(name
, kind
)
499 --------------------------------------------------------------------------------
500 -- Advanced implementation of graph.read(file, doscan) - Main entry
501 --------------------------------------------------------------------------------
502 local function getedge(n
, elist
)
503 for e
in n
:walkedges() do
505 table.insert(elist
, e
)
510 local function getnode(g
, nlist
, elist
)
511 for n
in g
:walknodes() do
515 table.insert(nlist
, n
)
519 local function getsubg(g
, glist
)
520 for sg
in g
:walk() do
521 local t
= {sg
, graph
= {}, node
= {}, edge
= {}}
522 table.insert(glist
, t
)
524 getnode(sg
, t
.node
, t
.edge
)
528 function read(fname
, doscan
)
529 local g
, err
= _read(fname
)
530 if not g
then return g
, err
end
532 if doscan
== true then
540 getnode(g
, t
.node
, t
.edge
)
547 --==============================================================================
548 -- Utilities to create a graph as Lua table.
549 -- Each of the following functions returns a constructor function for
550 -- the corresponding object.
551 --==============================================================================
553 --------------------------------------------------------------------------------
554 -- Create a directed graph.
555 --------------------------------------------------------------------------------
556 local function _graph(t
, typ
)
557 local name
= t
.name
or t
[1]
558 if type(t
[1]) == "string" then
561 assert(name
, "missing name")
562 local g
= open(name
, typ
)
563 for k
, v
in npairs(t
) do
564 if type(v
) == "table" then
566 elseif type(v
) == "string" then
569 error("invalid attribute type")
572 for k
,v
in ipairs(t
) do
573 if type(v
) == "function" then
576 error("invalid graph attribute")
583 return _graph(t
, "directed")
586 function strictdigraph(t
)
587 return _graph(t
, "strictdirected")
590 function graph
.graph(t
)
591 return _graph(t
, "undirected")
594 function strictgraph(t
)
595 return _graph(t
, "strict")
598 --------------------------------------------------------------------------------
600 --------------------------------------------------------------------------------
603 assert(type(t
[1]) == "string", "missing subgraph name")
604 local sg
= g
:subgraph(t
[1])
606 for k
, v
in npairs(t
) do
607 if type(v
) == "string" then
610 error("invalid attribute type")
613 for k
,v
in ipairs(t
) do
614 if type(v
) == "function" then
617 error("invalid attribute type")
624 --------------------------------------------------------------------------------
626 --------------------------------------------------------------------------------
628 assert(type(t
[1]) == "string", "missing cluster name")
629 t
[1] = "cluster_"..t
[1]
633 --------------------------------------------------------------------------------
635 --------------------------------------------------------------------------------
642 --------------------------------------------------------------------------------
644 --------------------------------------------------------------------------------
650 --------------------------------------------------------------------------------
652 --------------------------------------------------------------------------------
656 for k
,v
in pairs(t
) do
657 if type(v
) == "function" then
658 table.insert(p
, v(g
))
659 elseif type(v
) == "string" or type(v
) == "userdata" then
662 error("invalid parameter")