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 local unpack
= unpack
or table.unpack
18 if string.find(_VERSION
, "5.1") then
19 module("graph", package
.seeall
)
21 _ENV
= setmetatable(graph
, {
26 -- Overloaded graph.open(), graph.read()
31 --==============================================================================
33 --==============================================================================
47 {format = "canon", descr
= "DOT pretty"},
48 {format = "dot", descr
= "DOT"},
49 {format = "xdot", descr
= "extended DOT"},
50 {format = "cmap", descr
= "Client-side imagemap (deprecated)"},
51 {format = "dia", descr
= "Dia format"},
52 {format = "eps", descr
= "Encapsulated PostScript"},
53 {format = "fig", descr
= "FIG"},
54 {format = "gd", descr
= "Graphics Draw format"},
55 {format = "gd2", descr
= "Graphics Draw 2 format"},
56 {format = "gif", descr
= "Graphics Interchage Format"},
57 {format = "gtk", descr
= "Antialiased image using a GTK 2.0 canvas"},
58 {format = "hpgl", descr
= "Hewlett Packard Graphics Language HP-GL/2"},
59 {format = "ico", descr
= "Icon Image File Format"},
60 {format = "imap", descr
= "Server-side and client-side imagemaps"},
61 {format = "imap_np", descr
= "Server-side and client-side imagemaps"},
62 {format = "cmapx", descr
= "Server-side and client-side imagemaps"},
63 {format = "cmapx_np", descr
= "Server-side and client-side imagemaps"},
64 {format = "ismap", descr
= "Server-side imagemap (deprecated)"},
65 {format = "jpg", descr
= "JPEG (deprecated - 8 May 2006 - will no longer be supported)"},
66 {format = "jpeg", descr
= "JPEG (deprecated - 8 May 2006 - will no longer be supported)"},
67 {format = "mif", descr
= "FrameMaker MIF format"},
68 {format = "mp", descr
= "MetaPost"},
69 {format = "pcl", descr
= "Hewlett Packard Printer Control"},
70 {format = "pdf", descr
= "Portable Document Format (PDF)"},
71 {format = "pic", descr
= "Autodesk PIC"},
72 {format = "plain", descr
= "Simple text format"},
73 {format = "plain-ext", descr
= "Extended text format"},
74 {format = "png", descr
= "Portable Network Graphics format"},
75 {format = "ps", descr
= "PostScript"},
76 {format = "ps2", descr
= "PostScript for PDF"},
77 {format = "svg", descr
= "Scalable Vector Graphics"},
78 {format = "svgz", descr
= "Scalable Vector Graphics"},
79 {format = "vml", descr
= "Vector Markup Language (VML)"},
80 {format = "vmlz", descr
= "Vector Markup Language (VML) - compressed"},
81 {format = "vrml", descr
= "VRML"},
82 {format = "vtx", descr
="Visual Thought format"},
83 {format = "wbmp", descr
= "Wireless BitMap format"},
84 {format = "xlib", descr
= "Xlib canvas"},
86 --==============================================================================
88 --==============================================================================
90 --------------------------------------------------------------------------------
91 -- Iterator over non-numeric indices of a table
92 --------------------------------------------------------------------------------
93 local function npairs(t
)
94 return function(t
, prev
)
96 while type(k
) == "number" do
104 --------------------------------------------------------------------------------
105 -- Copy attributes from parameter table
106 --------------------------------------------------------------------------------
107 local function attribs(params
)
109 for k
,v
in npairs(params
) do
115 --------------------------------------------------------------------------------
116 -- Get default attributes for the given object (type)
117 --------------------------------------------------------------------------------
118 local function getattrib(self
)
120 local defined
= self
.graph
:defaults()[self
:type()]
121 for k
,v
in pairs(defined
) do
122 t
[k
] = self
:rawget(k
)
127 --------------------------------------------------------------------------------
128 -- Add a method to an object (node, graph, edge)
129 --------------------------------------------------------------------------------
130 local function addmethod(self
, name
, func
)
131 local mt
= getmetatable(self
)
132 if not mt
or mt
[name
] then return end
136 -- A forward declaration
139 --==============================================================================
140 -- Advanced implementations
141 --==============================================================================
142 --------------------------------------------------------------------------------
144 -- sg = g:subgraph{"name", ATTR, ..., ATTR, nocreate}
145 -- sg = g:subgraph("name", {ATTR, ..., ATTR}, nocreate)
146 --------------------------------------------------------------------------------
147 local function _subgraph(self
, ...)
151 if type(arg
[1]) == "table" then
154 for k
,v
in npairs(arg
[1]) do
155 if type(v
) == "table" then
161 elseif type(arg
[1]) == "string" then
166 error("missing subgraph name")
168 local g
, err
= self
:__subgraph(name
)
172 for k
,v
in pairs(attr
) do
173 if type(v
) == "table" then
182 --------------------------------------------------------------------------------
184 -- c = g:cluster{"name", ATTR, ..., ATTR, nocreate}
185 -- c = g:cluster("name", {ATTR, ..., ATTR}, nocreate}
186 --------------------------------------------------------------------------------
187 local function _cluster(self
, ...)
189 if type(arg
[1]) == "table" then
190 arg
[1][1] = "cluster_"..arg
[1][1]
191 elseif type(arg
[1]) == "string" then
192 arg
[1] = "cluster_"..arg
[1]
194 error("missing cluster name")
196 return _subgraph(self
, unpack(arg
))
199 --------------------------------------------------------------------------------
201 -- e = g:edge{node, .., "node", ATTR, ..., ATTR}
202 -- with ATTR: key = value
203 -- node: object or node name
204 -- e = g:edge(tail, head, "label", nocreate)
205 -- e = g:edge("tail", "head", "label", nocreate)
206 --------------------------------------------------------------------------------
207 local function _edge(self
, ...)
209 local nodes
, edges
= {}, {}
213 if type(arg
[1]) == "table" then
214 attr
= attribs(arg
[1])
216 for i
, v
in ipairs(arg
[1]) do
217 -- we must care about ports here:
219 if type(v
) == "string" then
220 string.gsub(v
, "^(%w+):*(%w*):*(%w*)", function(u
, v
, w
)
225 node
.node
= self
:__node(node
.name
)
226 elseif type(v
) == "userdata" then
229 error("wrong node type")
231 table.insert(nodes
, node
.node
)
233 -- Create edges and set attributes to each edge
234 local e
, tail_or_err
, head
= self
:__edge(last
.node
, node
.node
)
238 if last
.port
then e
.tailport
= last
.port
end
239 if node
.port
then e
.headport
= node
.port
end
240 addmethod(e
, "getattrib", getattrib
)
241 for k
,v
in pairs(attr
) do
244 table.insert(edges
, e
)
249 elseif type(arg
[1]) == "string" or type(arg
[1]) == "userdata" then
250 local node
= {[1]={},[2]={}}
253 -- we must care about ports here:
254 if type(v
) == "string" then
255 string.gsub(v
, "^(%w+):*(%w*):*(%w*)", function(u
, v
, w
)
260 arg
[i
] = self
:__node(node
[i
].name
)
263 local e
, tail_or_err
, head
= self
:__edge(unpack(arg
))
264 if not e
then return e
, tail_or_err
end
265 if node
[1].port
then e
.tailport
=node
[1].port
end
266 if node
[2].port
then e
.headport
=node
[2].port
end
267 addmethod(e
, "getattrib", getattrib
)
268 return e
, tail_or_err
, head
270 error("invalid edge declaration")
274 --------------------------------------------------------------------------------
276 -- n = g:node{"name", ATTR, ..., ATTR, nocreate}
277 -- n = g:node("name", {ATTR, ..., ATTR}, nocreate)
278 -- with ATTR: key = value
279 --------------------------------------------------------------------------------
280 local function _node(self
, ...)
283 if type(arg
[1]) == "table" then
286 attr
= attribs(arg
[1])
287 elseif type(arg
[1]) == "string" then
292 error("missing node name")
294 local n
= self
:__node(name
, nocreate
)
295 for k
,v
in pairs(attr
) do
298 addmethod(n
, "getattrib", getattrib
)
302 --------------------------------------------------------------------------------
303 -- Returns a function that creates horizontal oriented fields
304 -- gr.hbox{"field", gr.vbox{...}, "field", gr.vbox{...}}
305 --------------------------------------------------------------------------------
309 for k
, v
in ipairs(t
) do
310 if type(v
) == "function" then
311 ss
= ss
.. v("h") .. "|"
312 elseif type(v
) == "string" then
316 ss
= string.sub(ss
, 1, -2)
317 if dir
~= "h" then ss
= "{" .. ss
.. "}" end
322 --------------------------------------------------------------------------------
323 -- Returns a function that creates vertical oriented fields
324 -- gr.vbox{"field", gr.hbox{...}, "field", gr.hbox{...}}
325 --------------------------------------------------------------------------------
329 for k
, v
in ipairs(t
) do
330 if type(v
) == "function" then
331 ss
= ss
.. v("v") .. "|"
332 elseif type(v
) == "string" then
336 ss
= string.sub(ss
, 1, -2)
337 if dir
~= "v" then ss
= "{" .. ss
.. "}" end
342 --------------------------------------------------------------------------------
344 -- n = g:record{"n1", vbox{...}, ATTR, ...,ATTR, nocreate}
345 -- n = g:record("n1", vbox{"name", hbox{...}, ...}, {ATTR, ..., ATTR}, nocreate)
346 --------------------------------------------------------------------------------
347 local function _record(self
, ...)
348 local name
, attr
, labelfunc
350 if type(arg
[1]) == "table" then
354 attr
= attribs(arg
[1])
355 elseif type(arg
[1]) == "string" then
361 error("missing record name")
363 assert(type(lfunc
) == "function", "missing record struct")
364 local label
= lfunc("h")
365 local n
= self
:__node(name
, nocreate
)
368 for k
,v
in pairs(attr
) do
371 addmethod(n
, "getattrib", getattrib
)
375 --==============================================================================
376 -- Additional graph methods
377 --==============================================================================
378 --------------------------------------------------------------------------------
380 --------------------------------------------------------------------------------
381 local function findnode(self
, name
)
383 return nil, "not found"
385 return self
:__node(name
, true)
388 --------------------------------------------------------------------------------
390 --------------------------------------------------------------------------------
391 local function findedge(self
, tail
, head
, label
)
392 return self
:_findedge(tail
, head
, label
)
395 --------------------------------------------------------------------------------
397 --------------------------------------------------------------------------------
398 local function findsubgraph(self
, tail
, head
)
399 return self
:_subgraph(tail
, head
, false)
402 --------------------------------------------------------------------------------
403 -- Show graph with dotty
404 --------------------------------------------------------------------------------
405 local function showdotty(self
, doit
)
409 print("SYSTEM:", self
._SYSTEM
)
410 if graph
._SYSTEM
== "Win32" then
413 fn
= os
.tmpname()..".dot"
415 local rv
= self
:write(fn
)
416 print("result: "..rv
)
417 if graph
._SYSTEM
== "Win32" then
418 rv
= os
.execute("gvedit "..fn
)
420 rv
= os
.execute("xdot "..fn
)
427 --------------------------------------------------------------------------------
428 -- Show graph with dotty
429 --------------------------------------------------------------------------------
430 local function show(self
, printdot
)
431 local printdot
= printdot
or false
433 if printdot
== true then
434 self
.render(nil, "dot", "out.dot")
440 return io
.popen('dot -V 2>&1 | cut -d " " -f 5'):read()
442 --------------------------------------------------------------------------------
443 -- Partially overload C-defined metamethods
444 -- Note: keep this function behind the overloading ones
445 --------------------------------------------------------------------------------
446 local function overload(g
)
447 local mt
= getmetatable(g
)
448 if mt
.overloaded
== true then return end
451 mt
.__subgraph
= mt
.subgraph
454 mt
.subgraph
= _subgraph
455 mt
.cluster
= _cluster
457 mt
._findedge
= mt
.findedge
458 mt
.findedge
= findedge
459 mt
.findnode
= findnode
460 mt
.findsubgraph
= findsubgraph
461 mt
.showdotty
= showdotty
466 -- resolve forward declaration
469 --------------------------------------------------------------------------------
470 -- Advanced implementation of graph.open() - Main entry
471 --------------------------------------------------------------------------------
477 if type(arg
[1]) == "string" then
478 -- Syntax 1: graph.open("NAME","directed", {graph={ATTR,..}, edge={ATTR,..}, node={ATTR,..}})
483 elseif type(arg
[1]) == "table" then
484 -- Syntax 2: graph.open{"NAME", "kind", ATTR, ATTR}
487 for k
,v
in npairs(arg
[1]) do
488 if type(v
) == "table" then
496 -- Create the graph and declare attributes
497 g
= _open(name
, kind
)
507 --------------------------------------------------------------------------------
508 -- Advanced implementation of graph.read(file, doscan) - Main entry
509 --------------------------------------------------------------------------------
510 local function getedge(n
, elist
)
511 for e
in n
:walkedges() do
513 table.insert(elist
, e
)
518 local function getnode(g
, nlist
, elist
)
519 for n
in g
:walknodes() do
523 table.insert(nlist
, n
)
527 local function getsubg(g
, glist
)
528 for sg
in g
:walk() do
529 local t
= {sg
, graph
= {}, node
= {}, edge
= {}}
530 table.insert(glist
, t
)
532 getnode(sg
, t
.node
, t
.edge
)
536 function read(fname
, doscan
)
537 local g
, err
= _read(fname
)
538 if not g
then return g
, err
end
540 if doscan
== true then
548 getnode(g
, t
.node
, t
.edge
)
555 --==============================================================================
556 -- Utilities to create a graph as Lua table.
557 -- Each of the following functions returns a constructor function for
558 -- the corresponding object.
559 --==============================================================================
561 --------------------------------------------------------------------------------
562 -- Create a directed graph.
563 --------------------------------------------------------------------------------
564 local function _graph(t
, typ
)
565 local name
= t
.name
or t
[1]
566 if type(t
[1]) == "string" then
569 assert(name
, "missing name")
570 local g
= open(name
, typ
)
571 for k
, v
in npairs(t
) do
572 if type(v
) == "table" then
574 elseif type(v
) == "string" then
577 error("invalid attribute type")
580 for k
,v
in ipairs(t
) do
581 if type(v
) == "function" then
584 error("invalid graph attribute")
591 return _graph(t
, "directed")
594 function strictdigraph(t
)
595 return _graph(t
, "strictdirected")
598 function graph
.graph(t
)
599 return _graph(t
, "undirected")
602 function strictgraph(t
)
603 return _graph(t
, "strict")
606 --------------------------------------------------------------------------------
608 --------------------------------------------------------------------------------
611 assert(type(t
[1]) == "string", "missing subgraph name")
612 local sg
= g
:subgraph(t
[1])
614 for k
, v
in npairs(t
) do
615 if type(v
) == "string" then
618 error("invalid attribute type")
621 for k
,v
in ipairs(t
) do
622 if type(v
) == "function" then
625 error("invalid attribute type")
632 --------------------------------------------------------------------------------
634 --------------------------------------------------------------------------------
636 assert(type(t
[1]) == "string", "missing cluster name")
637 t
[1] = "cluster_"..t
[1]
641 --------------------------------------------------------------------------------
643 --------------------------------------------------------------------------------
650 --------------------------------------------------------------------------------
652 --------------------------------------------------------------------------------
658 --------------------------------------------------------------------------------
660 --------------------------------------------------------------------------------
664 for k
,v
in pairs(t
) do
665 if type(v
) == "function" then
666 table.insert(p
, v(g
))
667 elseif type(v
) == "string" or type(v
) == "userdata" then
670 error("invalid parameter")